@sema-agent/core 7.42.1 → 7.43.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 +100 -0
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +5 -3
- package/dist/agents/teacher.js +1 -1
- package/dist/agents/verify.js +2 -0
- package/dist/core/allow-entry.d.ts +50 -31
- package/dist/core/allow-entry.js +100 -37
- package/dist/core/ask-origin.d.ts +16 -5
- package/dist/core/ask-origin.js +11 -3
- package/dist/core/ask-unresolvable-notice.d.ts +3 -2
- package/dist/core/ask-unresolvable-notice.js +1 -1
- package/dist/core/auto-mode.d.ts +5 -4
- package/dist/core/auto-mode.js +3 -1
- package/dist/core/checkpoint-store.d.ts +18 -7
- package/dist/core/checkpoint-store.js +6 -3
- package/dist/core/child-clamp-seats.d.ts +15 -1
- package/dist/core/child-clamp-seats.js +6 -2
- package/dist/core/classifier-unavailable-arm.d.ts +42 -23
- package/dist/core/classifier-unavailable-arm.js +14 -3
- package/dist/core/decision-receipt.js +2 -0
- package/dist/core/gate-lanes.js +37 -29
- package/dist/core/hooks.d.ts +6 -1
- package/dist/core/hooks.js +3 -0
- package/dist/core/mode-policy.d.ts +5 -4
- package/dist/core/mode-policy.js +4 -0
- package/dist/core/permission-rule-consent.d.ts +3 -2
- package/dist/core/permission-rule-consent.js +25 -20
- package/dist/core/permission-rule-model.d.ts +47 -7
- package/dist/core/permission-rule-model.js +80 -6
- package/dist/core/permission-rules.d.ts +8 -2
- package/dist/core/permission-rules.js +26 -9
- package/dist/core/runner/classifier-ladder.d.ts +10 -8
- package/dist/core/runner/classifier-ladder.js +3 -1
- package/dist/core/runner/denial-limit-arms.d.ts +25 -8
- package/dist/core/runner/denial-limit-arms.js +24 -10
- package/dist/core/runner/gate-exit.js +2 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +2 -2
- package/dist/core/runner/permission-rule-lanes.js +8 -2
- package/dist/core/runner/prepare-ask-lane.d.ts +3 -0
- package/dist/core/runner/prepare-ask-lane.js +2 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -3
- package/dist/core/runner/prepare-caps-and-workflow.js +3 -2
- package/dist/core/runner/prepare-config-doors.d.ts +3 -0
- package/dist/core/runner/prepare-config-doors.js +6 -5
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-inherited-gate.d.ts +7 -5
- package/dist/core/runner/prepare-inherited-gate.js +5 -3
- package/dist/core/runner/prepare-park-ask.d.ts +4 -4
- package/dist/core/runner/prepare-park-ask.js +2 -4
- package/dist/core/runner/prepare-policy-chain.d.ts +7 -1
- package/dist/core/runner/prepare-policy-chain.js +59 -38
- package/dist/core/runner/prepare-project-context.js +6 -2
- package/dist/core/runner/prepare-task.js +6 -6
- package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -5
- package/dist/core/runner/prepare-wiring-manifest.js +8 -9
- package/dist/core/runner/resume-checkpoint-screen.js +1 -1
- package/dist/core/runner/synthetic-tools.d.ts +3 -2
- package/dist/core/runner/synthetic-tools.js +1 -1
- package/dist/core/session-policy-store.d.ts +3 -3
- package/dist/core/session-policy-store.js +18 -4
- package/dist/core/skill-grant-binding.d.ts +44 -0
- package/dist/core/skill-grant-binding.js +22 -0
- package/dist/core/skill-grant.d.ts +59 -28
- package/dist/core/skill-grant.js +96 -23
- package/dist/core/task-spec.d.ts +14 -1
- package/dist/core/tighten-task-spec.d.ts +2 -0
- package/dist/core/tighten-task-spec.js +3 -0
- package/dist/core/tool-name-aliases.d.ts +35 -5
- package/dist/core/tool-name-aliases.js +29 -4
- package/dist/core/tool-policy.d.ts +10 -0
- package/dist/core/tool-policy.js +9 -2
- package/dist/core/tool-spec.d.ts +7 -0
- package/dist/core/wiring-manifest.d.ts +56 -14
- package/dist/core/wiring-manifest.js +29 -4
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +4 -0
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow-primitives.d.ts +3 -1
- package/dist/orchestration/workflow-primitives.js +6 -6
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +11 -1
- package/wire-changes/core@7.43.0.json +985 -0
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The ONE exit for an auto-mode classification that
|
|
3
|
-
*
|
|
4
|
-
* in the policy fold, so the rule is written once.
|
|
2
|
+
* The ONE exit for an auto-mode classification that did not RULE ({@link rungDidNotRule}: the classifier leg failed or its
|
|
3
|
+
* budget ran out — `error` / `timeout` — or it replied with no verdict at all — `parse_error`) — shared by both classifier
|
|
4
|
+
* stations, the leg's gate and an ancestor's frozen classifier in the policy fold, so the rule is written once.
|
|
5
5
|
*
|
|
6
|
-
* ONE fork, on the APPROVAL SEAT (does an ask this station hands on reach a person — a live approver, or a durable
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* handed-on ask is a person's, not the classifier's.
|
|
6
|
+
* ONE fork, on the APPROVAL SEAT (does an ask this station hands on reach a person — a live approver, or a durable park):
|
|
7
|
+
* with one, the ask goes on carrying why (`classifierNoRuling`, its text saying it too), and a person decides — never
|
|
8
|
+
* counted (a person's refusal of it is a person's); without one, an unavailable classification is refused with CC's `x1t`
|
|
9
|
+
* sentence (its FORM, `unavailable`, attested on the deny) and is never counted, while a reply with no verdict goes back to
|
|
10
|
+
* the station's ruling path (`counted`): refused with its own sentence and counted on the denial limit, so the bound falls
|
|
11
|
+
* back to a person (CC: `Cr` excludes unavailable; failureMode is counted). The handed-on ask carries `requiresRealApproval`:
|
|
12
|
+
* no automatic lane may answer it in the classifier's stead — not a blanket `onAsk:"allow"`, not the sandbox admission, not a
|
|
13
|
+
* budget resolver over its park. Asking where a person can be asked is a declared departure (CC refuses).
|
|
15
14
|
*
|
|
16
15
|
* The observer hears every such classification with where the call went, except while the call is being aborted — a halt,
|
|
17
|
-
* or the run's end
|
|
16
|
+
* or the run's end. It is observe-only: a throw never changes the arm.
|
|
18
17
|
*/
|
|
19
|
-
import { type AutoModeClassifierRound, type
|
|
20
|
-
import { type PermissionResult, type ToolCallRequest } from "./tool-policy.js";
|
|
21
|
-
/** Where
|
|
18
|
+
import { type AutoModeClassifierRound, type AutoModeDenialTracker, type AutoModeFallbackCause, type UnarmedDenialLimitFallback } from "./auto-mode.js";
|
|
19
|
+
import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "./tool-policy.js";
|
|
20
|
+
/** Where a classification that did not rule sent the call: `asked` = on to the approval seat, `denied` = refused. */
|
|
22
21
|
export type ClassifierUnavailableDisposition = "asked" | "denied";
|
|
23
|
-
/** What the stations' observer receives — the facts the `classifier.unavailable` notice is composed from.
|
|
22
|
+
/** What the stations' observer receives — the facts the `classifier.unavailable` notice is composed from. `cause` is why
|
|
23
|
+
* the classification did not rule ({@link AutoModeFallbackCause}: `error` / `timeout`, or `parse_error` for a reply with no
|
|
24
|
+
* verdict). */
|
|
24
25
|
export interface ClassifierUnavailableInfo {
|
|
25
26
|
toolName: string;
|
|
26
27
|
toolCallId: string;
|
|
27
|
-
cause:
|
|
28
|
+
cause: AutoModeFallbackCause;
|
|
28
29
|
disposition: ClassifierUnavailableDisposition;
|
|
29
30
|
}
|
|
30
31
|
export type ClassifierUnavailableObserver = (info: ClassifierUnavailableInfo) => void;
|
|
@@ -34,24 +35,42 @@ export interface ClassifierUnavailableSeat {
|
|
|
34
35
|
approvalSeat?: boolean;
|
|
35
36
|
onUnavailable?: ClassifierUnavailableObserver;
|
|
36
37
|
}
|
|
37
|
-
/** The arm the fork chose: the ask to hand on, or the
|
|
38
|
-
|
|
38
|
+
/** The arm the fork chose: the ask to hand on, the refusal, or — a reply with no verdict and no seat — the station's own
|
|
39
|
+
* counted ruling path. */
|
|
40
|
+
export type ClassifierNoRulingArm<A> = {
|
|
39
41
|
kind: "asked";
|
|
40
42
|
ask: A;
|
|
41
43
|
} | {
|
|
42
44
|
kind: "denied";
|
|
43
45
|
deny: PermissionResult;
|
|
46
|
+
} | {
|
|
47
|
+
kind: "counted";
|
|
44
48
|
};
|
|
45
|
-
export declare function
|
|
49
|
+
export declare function classifierNoRulingArm<A extends Extract<PermissionResult, {
|
|
46
50
|
action: "ask";
|
|
47
51
|
}>>(args: {
|
|
48
52
|
ask: A;
|
|
49
53
|
req: ToolCallRequest;
|
|
50
|
-
cause:
|
|
54
|
+
cause: AutoModeFallbackCause;
|
|
51
55
|
/** The ladder's attribution, for the refusal's sentence (it names both rungs when the ladder moved). */
|
|
52
56
|
attribution?: AutoModeClassifierRound;
|
|
53
57
|
seat: ClassifierUnavailableSeat | undefined;
|
|
54
58
|
/** The call is being aborted (its per-call signal — a halt, or the run's end): the observer is not told (the arm is chosen
|
|
55
59
|
* as ever). */
|
|
56
60
|
aborted: boolean;
|
|
57
|
-
}):
|
|
61
|
+
}): ClassifierNoRulingArm<A>;
|
|
62
|
+
/**
|
|
63
|
+
* The no-ruling card's settlement (both stations): a card THIS station's no-ruling arm handed on for this call (`mintedHere` —
|
|
64
|
+
* a policy's own `classifierNoRuling`, or a card another station minted, is not this station's classification and counts
|
|
65
|
+
* nothing here) that the seat's deployment POLICY refused (settlement `policy_refused` — a machine settled it, not a person)
|
|
66
|
+
* counts on the denial limit like a ruling refusal, because "handed to a person" means a person settled it. Returns the fallback the count minted when it reached the
|
|
67
|
+
* bound — the station then puts the same call to the same seat as the denial-limit fallback, and a deny nobody judged there
|
|
68
|
+
* stops the run through the existing headless arm. A person's answer (allow or refusal), a fallback card, and every other
|
|
69
|
+
* settlement count nothing.
|
|
70
|
+
*/
|
|
71
|
+
export declare function settleNoRulingAsk(opts: {
|
|
72
|
+
ask: PermissionResult;
|
|
73
|
+
mintedHere: boolean;
|
|
74
|
+
resolved: Pick<ResolvedAsk, "action" | "settlement">;
|
|
75
|
+
tracker: AutoModeDenialTracker | undefined;
|
|
76
|
+
}): UnarmedDenialLimitFallback | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { classifierNoRulingAskMessage, classifierUnavailableDenyMessage } from "./auto-mode.js";
|
|
2
2
|
import { attestClassifierDenyCause, carryEngineAttestations } from "./tool-policy.js";
|
|
3
|
-
export function
|
|
3
|
+
export function classifierNoRulingArm(args) {
|
|
4
4
|
const { ask, req, cause } = args;
|
|
5
5
|
const seated = args.seat?.approvalSeat === true;
|
|
6
6
|
if (!args.aborted) {
|
|
@@ -11,7 +11,18 @@ export function classifierUnavailableArm(args) {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
if (seated)
|
|
14
|
-
return { kind: "asked", ask: carryEngineAttestations(ask, { ...ask, message:
|
|
14
|
+
return { kind: "asked", ask: carryEngineAttestations(ask, { ...ask, message: classifierNoRulingAskMessage(ask.message ?? `approval required for "${req.toolName}"`, cause), requiresRealApproval: true, classifierNoRuling: cause }) };
|
|
15
|
+
if (cause === "parse_error")
|
|
16
|
+
return { kind: "counted" };
|
|
15
17
|
const deny = { action: "deny", message: classifierUnavailableDenyMessage(req.toolName, cause, args.attribution), decisionReason: "classifier" };
|
|
16
18
|
return { kind: "denied", deny: attestClassifierDenyCause(deny, "unavailable", req) };
|
|
17
19
|
}
|
|
20
|
+
export function settleNoRulingAsk(opts) {
|
|
21
|
+
const { ask, resolved } = opts;
|
|
22
|
+
if (!opts.mintedHere || ask.action !== "ask" || ask.classifierNoRuling === undefined || ask.denialLimitFallback !== undefined)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (resolved.action !== "deny" || resolved.settlement?.kind !== "policy_refused")
|
|
25
|
+
return undefined;
|
|
26
|
+
const tracked = opts.tracker?.recordBlock();
|
|
27
|
+
return tracked?.limitReached === true ? tracked.fallback : undefined;
|
|
28
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { types } from "node:util";
|
|
2
2
|
import { ASK_CLASSES, ASK_ORIGINS, isAskClass, isAskOrigin, isPersistedRuleMandate, PERSISTED_RULE_MANDATES } from "./ask-origin.js";
|
|
3
|
+
import { AUTO_MODE_FALLBACK_CAUSES, isAutoModeFallbackCause } from "./auto-mode.js";
|
|
3
4
|
import { thrownText } from "./thrown-value.js";
|
|
4
5
|
const ENGINE_REFUSALS = new WeakSet();
|
|
5
6
|
export const refusedOutOfContract = (message) => {
|
|
@@ -28,6 +29,7 @@ const CLOSED_MEMBERS = [
|
|
|
28
29
|
{ member: "guard", words: GUARDS, has: memberOf(GUARDS) },
|
|
29
30
|
{ member: "ruleUnreadable", words: RULE_UNREADABLE_WORDS, has: memberOf(RULE_UNREADABLE_WORDS) },
|
|
30
31
|
{ member: "authoredBy", words: AUTHORED_BY_WORDS, has: memberOf(AUTHORED_BY_WORDS) },
|
|
32
|
+
{ member: "classifierNoRuling", words: AUTO_MODE_FALLBACK_CAUSES, has: isAutoModeFallbackCause },
|
|
31
33
|
{ member: "requiresRealApproval", words: [true, false], has: (v) => v === true || v === false },
|
|
32
34
|
{ member: "probeMandated", words: [true], has: (v) => v === true },
|
|
33
35
|
{ member: "mandated", words: [true], has: (v) => v === true },
|
package/dist/core/gate-lanes.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { attestClassifierDenyCause, classifierDenyCauseOf, decisionText, refuseOutOfContractDecision } from "./tool-policy.js";
|
|
2
2
|
import { arbitrateByMode, classifyAsk, fileCommandOptionsSentence, persistedRuleMandateOf, ruleClearanceOf, safetyAxisOf, stampAskOrigin } from "./ask-origin.js";
|
|
3
|
-
import { classifierParseFailureDenyMessage, denialLimitFallbackMessage, unarmedWindow } from "./auto-mode.js";
|
|
4
|
-
import {
|
|
3
|
+
import { classifierParseFailureDenyMessage, denialLimitFallbackMessage, rungDidNotRule, unarmedWindow } from "./auto-mode.js";
|
|
4
|
+
import { classifierNoRulingArm, settleNoRulingAsk } from "./classifier-unavailable-arm.js";
|
|
5
5
|
import { SETTLEMENT_NOBODY_JUDGED } from "./gate-outcome.js";
|
|
6
6
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
7
7
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
@@ -228,6 +228,12 @@ export async function runGateLanes(pass) {
|
|
|
228
228
|
pass.decision = shadows.length > 0 ? { ...standing, allowRuleShadowed: shadows } : standing;
|
|
229
229
|
}
|
|
230
230
|
const originFacts = askOriginFacts(orgAnswer.org, ruleCallUnreadableOf(laneAnswer));
|
|
231
|
+
let noRulingMinted = false;
|
|
232
|
+
const fallbackAskOf = (standingAsk, fallback, latestBlockedAction) => {
|
|
233
|
+
const { classifierNoRuling: _why, ...standing } = standingAsk;
|
|
234
|
+
const fallbackAsk = { ...standing, message: denialLimitFallbackMessage(fallback, latestBlockedAction), decisionReason: "classifier", requiresRealApproval: true, denialLimitFallback: fallback };
|
|
235
|
+
return stampAskOrigin(fallbackAsk, originFacts, classifyStanding(fallbackAsk, originFacts));
|
|
236
|
+
};
|
|
231
237
|
if (pass.decision.action === "ask") {
|
|
232
238
|
const stamped = { ...pass.decision, ruleEvidence: mintRuleEvidence(personalEvidence), ...(laneCoverage !== undefined ? { segmentCoverage: laneCoverage } : {}) };
|
|
233
239
|
pass.decision = stampAskOrigin(stamped, originFacts, classifyStanding(stamped, originFacts));
|
|
@@ -271,36 +277,31 @@ export async function runGateLanes(pass) {
|
|
|
271
277
|
...(pass.policyRewrite !== undefined ? { updatedInput: pass.policyRewrite } : {}),
|
|
272
278
|
};
|
|
273
279
|
}
|
|
274
|
-
else if (verdict
|
|
275
|
-
const
|
|
276
|
-
|
|
280
|
+
else if (verdict !== undefined) {
|
|
281
|
+
const noRuling = rungDidNotRule(verdict);
|
|
282
|
+
const arm = noRuling !== undefined ? classifierNoRulingArm({ ask: pass.decision, req: pass.req, cause: noRuling, attribution: verdict.classifier, seat: input.autoMode.unavailable, aborted: classifySignal?.aborted === true }) : undefined;
|
|
283
|
+
if (arm?.kind === "asked") {
|
|
284
|
+
noRulingMinted = true;
|
|
277
285
|
pass.decision = stampAskOrigin(arm.ask, originFacts, classifyStanding(arm.ask, originFacts));
|
|
278
286
|
pass.classifierRound = undefined;
|
|
279
287
|
}
|
|
280
|
-
else
|
|
288
|
+
else if (arm?.kind === "denied")
|
|
281
289
|
pass.decision = arm.deny;
|
|
282
|
-
}
|
|
283
|
-
else if (verdict !== undefined) {
|
|
284
|
-
const reason = verdict.kind === "block" ? (verdict.reason ? inlineUntrusted(verdict.reason) : "") : classifierParseFailureDenyMessage(verdict.classifier);
|
|
285
|
-
const category = verdict.kind === "block" && verdict.category ? inlineUntrusted(verdict.category) : "";
|
|
286
|
-
const tracked = input.autoMode.denialTracking?.recordBlock();
|
|
287
|
-
if (tracked?.limitReached === true) {
|
|
288
|
-
const fallbackAsk = {
|
|
289
|
-
...pass.decision,
|
|
290
|
-
message: denialLimitFallbackMessage(tracked.fallback, reason || category || toolName),
|
|
291
|
-
decisionReason: "classifier",
|
|
292
|
-
requiresRealApproval: true,
|
|
293
|
-
denialLimitFallback: tracked.fallback,
|
|
294
|
-
};
|
|
295
|
-
pass.decision = stampAskOrigin(fallbackAsk, originFacts, classifyStanding(fallbackAsk, originFacts));
|
|
296
|
-
}
|
|
297
290
|
else {
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
291
|
+
const reason = verdict.kind === "block" ? (verdict.reason ? inlineUntrusted(verdict.reason) : "") : classifierParseFailureDenyMessage(verdict.classifier);
|
|
292
|
+
const category = verdict.kind === "block" && verdict.category ? inlineUntrusted(verdict.category) : "";
|
|
293
|
+
const tracked = input.autoMode.denialTracking?.recordBlock();
|
|
294
|
+
if (tracked?.limitReached === true) {
|
|
295
|
+
pass.decision = fallbackAskOf(pass.decision, tracked.fallback, reason || category || toolName);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
const denied = {
|
|
299
|
+
action: "deny",
|
|
300
|
+
message: verdict.kind === "block" ? `auto-mode classifier blocked this call${reason ? `: ${reason}` : category ? `: [${category}]` : ""}` : reason,
|
|
301
|
+
decisionReason: "classifier",
|
|
302
|
+
};
|
|
303
|
+
pass.decision = verdict.kind === "parse_error" ? attestClassifierDenyCause(denied, "parse_error", pass.req) : denied;
|
|
304
|
+
}
|
|
304
305
|
}
|
|
305
306
|
}
|
|
306
307
|
if (pass.decision.action === "deny") {
|
|
@@ -389,6 +390,7 @@ export async function runGateLanes(pass) {
|
|
|
389
390
|
const carryOf = (d) => ({
|
|
390
391
|
...(callSignal !== undefined ? { signal: callSignal } : {}),
|
|
391
392
|
...(d.action === "ask" && d.denialLimitFallback !== undefined ? { denialLimitFallback: unarmedWindow(d.denialLimitFallback) } : {}),
|
|
393
|
+
...(d.action === "ask" && d.classifierNoRuling !== undefined ? { classifierNoRuling: d.classifierNoRuling } : {}),
|
|
392
394
|
...(d.action === "ask" && d.origin !== undefined ? { origin: d.origin } : {}),
|
|
393
395
|
...(d.action === "ask" && d.askClass !== undefined && d.classes !== undefined ? { askClass: d.askClass, classes: d.classes } : {}),
|
|
394
396
|
...(d.action === "ask" && d.mandated === true ? { mandated: true, ...(d.mandate !== undefined ? { mandate: d.mandate } : {}) } : {}),
|
|
@@ -444,8 +446,14 @@ export async function runGateLanes(pass) {
|
|
|
444
446
|
}
|
|
445
447
|
}
|
|
446
448
|
if (pass.decision.action === "ask") {
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
+
let askBeforeResolve = pass.decision;
|
|
450
|
+
const resolveOnce = (d) => (callSignal !== undefined ? resolveAsk(d, pass.req, callSignal) : resolveAsk(d, pass.req));
|
|
451
|
+
let resolved = await resolveOnce(askBeforeResolve);
|
|
452
|
+
const refallback = settleNoRulingAsk({ ask: askBeforeResolve, mintedHere: noRulingMinted, resolved, tracker: input.autoMode?.denialTracking });
|
|
453
|
+
if (refallback !== undefined && askBeforeResolve.action === "ask") {
|
|
454
|
+
askBeforeResolve = fallbackAskOf(askBeforeResolve, refallback, toolName);
|
|
455
|
+
resolved = await resolveOnce(askBeforeResolve);
|
|
456
|
+
}
|
|
449
457
|
ledger.fromResolved(resolved, askBeforeResolve, toolName);
|
|
450
458
|
if (resolved.action === "deny")
|
|
451
459
|
pass.deniedBy = "ask_resolution";
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { normalizeOrgGateVerdict, normalizePersistedRuleHit } from "./rule-lane-
|
|
|
7
7
|
export { cloneObserverInput } from "./runner/gate-exit.js";
|
|
8
8
|
import { type AskClass } from "./ask-class.js";
|
|
9
9
|
import { type PermissionMode } from "./ask-origin.js";
|
|
10
|
-
import type
|
|
10
|
+
import { type AutoModeDenialTracker } from "./auto-mode.js";
|
|
11
11
|
export { formatHookFeedback } from "./reminder-mint.js";
|
|
12
12
|
import type { WiringLegKind } from "./wiring-manifest.js";
|
|
13
13
|
import { type SafeNotifier } from "./safe-notify.js";
|
|
@@ -1464,6 +1464,8 @@ export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats, All
|
|
|
1464
1464
|
* `autoMode` presence alone as the arming fact.
|
|
1465
1465
|
*/
|
|
1466
1466
|
permissionMode?: () => import("./ask-origin.js").PermissionMode;
|
|
1467
|
+
/** The leg's approval-seat form (`WiringManifest.ask.form`): the deny exit's way-out clause names a declaration, never a missing approver. */
|
|
1468
|
+
askSeatForm?: import("./wiring-manifest.js").AskSeamForm;
|
|
1467
1469
|
/** WHO put the tool's egress / irreversibility marks on the seat ({@link import("./ask-origin.js").ToolMarkSource});
|
|
1468
1470
|
* absent or empty with a mark present reads as the deployment's own (fail-closed: an operator's mandate). */
|
|
1469
1471
|
toolMarkSources?: ReadonlySet<import("./ask-origin.js").ToolMarkSource>;
|
|
@@ -1764,6 +1766,8 @@ export interface AskCarry {
|
|
|
1764
1766
|
* the `0` a park row shows is written. Persisted on the row so a durable card shows the counts the
|
|
1765
1767
|
* synchronous card shows. Absent then means: not a fallback ask. */
|
|
1766
1768
|
readonly denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
1769
|
+
/** The surviving ask's no-ruling member ({@link import("./tool-policy.js").AskRequest.classifierNoRuling}), for the row's twin. */
|
|
1770
|
+
readonly classifierNoRuling?: import("./auto-mode.js").AutoModeFallbackCause;
|
|
1767
1771
|
/** The surviving ask's engine-stamped origin word ({@link import("./ask-origin.js").AskOrigin}), persisted
|
|
1768
1772
|
* on the row so a durable card is rendered by the same word the synchronous card is. */
|
|
1769
1773
|
readonly origin?: import("./ask-origin.js").AskOrigin;
|
|
@@ -1788,6 +1792,7 @@ export interface AskCarry {
|
|
|
1788
1792
|
* the same screen the summary applies to the word). */
|
|
1789
1793
|
export declare function askCarryRowMembers(carry: AskCarry | undefined): {
|
|
1790
1794
|
denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
1795
|
+
classifierNoRuling?: import("./auto-mode.js").AutoModeFallbackCause;
|
|
1791
1796
|
origin?: import("./ask-origin.js").AskOrigin;
|
|
1792
1797
|
askClass?: import("./ask-origin.js").AskClass;
|
|
1793
1798
|
classes?: readonly import("./ask-origin.js").AskClass[];
|
package/dist/core/hooks.js
CHANGED
|
@@ -8,6 +8,7 @@ export { normalizeOrgGateVerdict, normalizePersistedRuleHit } from "./rule-lane-
|
|
|
8
8
|
export { cloneObserverInput } from "./runner/gate-exit.js";
|
|
9
9
|
import { brandPolicyAskClass } from "./ask-class.js";
|
|
10
10
|
import { isAskOrigin, screenAskClassMembers } from "./ask-origin.js";
|
|
11
|
+
import { isAutoModeFallbackCause } from "./auto-mode.js";
|
|
11
12
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
12
13
|
export { formatHookFeedback } from "./reminder-mint.js";
|
|
13
14
|
import { createSafeNotifier } from "./safe-notify.js";
|
|
@@ -242,9 +243,11 @@ export function askCarryRowMembers(carry) {
|
|
|
242
243
|
if (carry === undefined)
|
|
243
244
|
return {};
|
|
244
245
|
const origin = carry.origin;
|
|
246
|
+
const why = carry.classifierNoRuling;
|
|
245
247
|
const classed = screenAskClassMembers(carry.askClass, carry.classes);
|
|
246
248
|
return {
|
|
247
249
|
...(carry.denialLimitFallback !== undefined ? { denialLimitFallback: { ...carry.denialLimitFallback } } : {}),
|
|
250
|
+
...(isAutoModeFallbackCause(why) ? { classifierNoRuling: why } : {}),
|
|
248
251
|
...(isAskOrigin(origin) ? { origin } : {}),
|
|
249
252
|
...(classed !== undefined ? classed : {}),
|
|
250
253
|
};
|
|
@@ -163,17 +163,18 @@ export declare function modeRequestAfterResume(seat: PermissionMode | undefined,
|
|
|
163
163
|
export declare function refusePermissionModeDoors(spec: Pick<TaskSpec, "permissionMode" | "shellGate">): PermissionMode | undefined;
|
|
164
164
|
/**
|
|
165
165
|
* The doors a RESUMED leg's prepare refuses at, read as one screen for the resume ladder to run BEFORE the claim: the mode
|
|
166
|
-
* doors on the re-supplied config ({@link refusePermissionModeDoors}), its output-style seat (`resolveOutputStyleFrame`)
|
|
167
|
-
*
|
|
166
|
+
* doors on the re-supplied config ({@link refusePermissionModeDoors}), its output-style seat (`resolveOutputStyleFrame`), its
|
|
167
|
+
* approval-reach declaration (`screenApprovalReach`), the skills it invokes by name (`screenUserInvokedSkills` — the part no
|
|
168
|
+
* roster changes; a name the leg's roster reads as nothing is prepare's) and the mode words the checkpoint row recorded (`recordedModeSeed`) — prepare's own functions, so the refusal carries the very
|
|
168
169
|
* sentence and code prepare would throw. It leaves by the resume's ONE refusal channel, `CheckpointError`
|
|
169
170
|
* `checkpoint.config_refused` (`message` = prepare's sentence, `detail.code` = prepare's code, `detail.field` = the input that
|
|
170
171
|
* carries it: `"taskConfig"` or `"inheritedGate"`), where prepare itself would end the run `failed` after the claim — a
|
|
171
172
|
* refusal there consumes the row and reopens it as a system retry that replays the recorded decision, which a CONFIG
|
|
172
173
|
* correction cannot reach. Before the claim the row stays `pending` and byte-identical, and the same token is redeemable by a
|
|
173
|
-
* corrected resume. A throw that is not one of prepare's
|
|
174
|
+
* corrected resume. A throw that is not one of prepare's five codes (a caller's accessor failing) is not a refusal and leaves
|
|
174
175
|
* as it was thrown.
|
|
175
176
|
*/
|
|
176
|
-
export declare function refuseResumeDoors(config: Pick<TaskSpec, "permissionMode" | "shellGate" | "outputStyle">, recordedGate: Parameters<typeof recordedModeSeed>[0]): void;
|
|
177
|
+
export declare function refuseResumeDoors(config: Pick<TaskSpec, "permissionMode" | "shellGate" | "outputStyle" | "approvalReach" | "skills" | "userInvokedSkills">, recordedGate: Parameters<typeof recordedModeSeed>[0]): void;
|
|
177
178
|
/** The three-member answer every mode question gets — the manifest member, the steer verb's return value and the
|
|
178
179
|
* fold notice's detail share it. `requested` is absent when the caller declared nothing (a root inherits `default`,
|
|
179
180
|
* a child inherits its parent). */
|
package/dist/core/mode-policy.js
CHANGED
|
@@ -9,6 +9,8 @@ import { createSafeNotifier } from "./safe-notify.js";
|
|
|
9
9
|
import { assertNoRetiredKeys, RETIRED_TASK_SPEC_KEYS } from "./retired-keys.js";
|
|
10
10
|
import { resolveOutputStyleFrame } from "./output-style.js";
|
|
11
11
|
import { CheckpointError, isCheckpointConfigRefusalCode, recordedModeSeed } from "./checkpoint-store.js";
|
|
12
|
+
import { screenApprovalReach } from "./wiring-manifest.js";
|
|
13
|
+
import { screenUserInvokedSkills } from "./skill-grant.js";
|
|
12
14
|
export const PERMISSION_MODE_FOLD_REASONS = Object.freeze(["operator_deny", "face_absent", "inherited_ceiling"]);
|
|
13
15
|
export const ACCEPT_EDITS_FILE_COMMANDS = Object.freeze(["mkdir", "touch", "rm", "rmdir", "mv", "cp", "sed"]);
|
|
14
16
|
const MODE_POLICY_TABLE = {
|
|
@@ -112,6 +114,8 @@ export function refuseResumeDoors(config, recordedGate) {
|
|
|
112
114
|
screenAs("taskConfig", () => {
|
|
113
115
|
refusePermissionModeDoors(config);
|
|
114
116
|
resolveOutputStyleFrame(config.outputStyle);
|
|
117
|
+
screenApprovalReach(config.approvalReach);
|
|
118
|
+
screenUserInvokedSkills(config);
|
|
115
119
|
});
|
|
116
120
|
screenAs("inheritedGate", () => void recordedModeSeed(recordedGate));
|
|
117
121
|
}
|
|
@@ -282,9 +282,10 @@ export declare function prepareCardApproval(opts: {
|
|
|
282
282
|
principal?: string;
|
|
283
283
|
/** design/182 §4.5 (additive): the structural owner — only the local-owner path needs it. */
|
|
284
284
|
owner?: RuleOwner;
|
|
285
|
-
/** The tool whose ask this card belongs to. Only the shell tool the rule lane speaks for
|
|
285
|
+
/** The tool whose ask this card belongs to. Only the shell tool the rule lane speaks for and the name tools (the skill-activation
|
|
286
|
+
* card) are accepted. */
|
|
286
287
|
toolName: string;
|
|
287
|
-
/** The command as adjudicated — the same bytes the ask carried. */
|
|
288
|
+
/** The command as adjudicated — the same bytes the ask carried; for a name tool (`Skill`), the name its call carried (`args.skill`). */
|
|
288
289
|
command: string;
|
|
289
290
|
/** The ask's call id and argument digest, recorded for reconciliation. */
|
|
290
291
|
toolCallId?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { escapeForDisclosure, hasUnrenderableCharacters, parseRuleText, ruleAdmitsCommand, ruleBreadthWarningsOf, segmentCoverageOf, suggestRulesForCommand, RULE_BEHAVIORS, RULE_BEHAVIOR_PRECEDENCE, isRuleBehavior, } from "./permission-rule-model.js";
|
|
2
|
+
import { escapeForDisclosure, hasUnrenderableCharacters, parseRuleText, ruleAdmitsCommand, ruleBreadthWarningsOf, ruleToolGrammarOf, segmentCoverageOf, suggestRuleForName, suggestRulesForCommand, RULE_BEHAVIORS, RULE_BEHAVIOR_PRECEDENCE, isRuleBehavior, } from "./permission-rule-model.js";
|
|
3
3
|
import { effectiveOrThrow } from "./permission-rule-provider.js";
|
|
4
4
|
import { errText, isValidConsentScope, landRuleAdd, sameRuleOwner, sameScope, writerOf } from "./permission-rule-store.js";
|
|
5
5
|
export class InMemoryRuleApprovalRecordStore {
|
|
@@ -279,7 +279,8 @@ export async function prepareCardApproval(opts) {
|
|
|
279
279
|
e.code = "config.invalid_argument";
|
|
280
280
|
throw e;
|
|
281
281
|
}
|
|
282
|
-
|
|
282
|
+
const nameCard = ruleToolGrammarOf(opts.toolName) === "name";
|
|
283
|
+
if (opts.toolName !== CARD_RULE_TOOL && !nameCard)
|
|
283
284
|
return undefined;
|
|
284
285
|
const scope = (() => {
|
|
285
286
|
if (opts.scope !== undefined) {
|
|
@@ -303,22 +304,26 @@ export async function prepareCardApproval(opts) {
|
|
|
303
304
|
throw e;
|
|
304
305
|
})();
|
|
305
306
|
let coverage;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
307
|
+
if (!nameCard) {
|
|
308
|
+
try {
|
|
309
|
+
const view = await effectiveOrThrow(storeForOwner(opts.deps.provider, owner), opts.sessionId !== undefined ? { sessionId: opts.sessionId } : undefined);
|
|
310
|
+
const table = view.rules;
|
|
311
|
+
coverage = segmentCoverageOf(opts.command, { persisted: table }, { tool: CARD_RULE_TOOL, cwd: opts.cwd, sessionId: opts.sessionId, ...(opts.execCwd !== undefined ? { execCwd: opts.execCwd } : {}) });
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
coverage = undefined;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const suggested = nameCard
|
|
318
|
+
? [suggestRuleForName(opts.toolName, opts.command)].filter((o) => o !== undefined)
|
|
319
|
+
: suggestRulesForCommand(opts.command, {
|
|
320
|
+
...(coverage !== undefined ? { coverage } : {}),
|
|
321
|
+
scope,
|
|
322
|
+
...(opts.cwd !== undefined ? { cwd: opts.cwd } : {}),
|
|
323
|
+
...(opts.execCwd !== undefined ? { execCwd: opts.execCwd } : {}),
|
|
324
|
+
...(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {}),
|
|
325
|
+
...(opts.deps.deniesDirectoryRead !== undefined ? { deniesDirectoryRead: opts.deps.deniesDirectoryRead } : {}),
|
|
326
|
+
});
|
|
322
327
|
const candidates = [];
|
|
323
328
|
const claimedBy = new Map();
|
|
324
329
|
const claim = (text) => {
|
|
@@ -349,7 +354,7 @@ export async function prepareCardApproval(opts) {
|
|
|
349
354
|
rev: 0,
|
|
350
355
|
candidates,
|
|
351
356
|
offers,
|
|
352
|
-
command: opts.command,
|
|
357
|
+
...(nameCard ? {} : { command: opts.command }),
|
|
353
358
|
createdAt: nowIso(opts.deps),
|
|
354
359
|
...(opts.toolCallId !== undefined ? { toolCallId: opts.toolCallId } : {}),
|
|
355
360
|
...(opts.boundInputHash !== undefined ? { boundInputHash: opts.boundInputHash } : {}),
|
|
@@ -510,7 +515,7 @@ async function confirmEditedCandidate(rec, edit, deps) {
|
|
|
510
515
|
return no("edit_rejected", { message: "the record's candidates carry no single common scope — an edited candidate inherits the card's scope, and a record without one is malformed" });
|
|
511
516
|
}
|
|
512
517
|
if (rec.command === undefined) {
|
|
513
|
-
return no("edit_rejected", { message: "the record does not carry
|
|
518
|
+
return no("edit_rejected", { message: "the record does not carry an adjudicated command (a name card, or one minted before card edits existed) — coverage cannot be verified, so the edit is refused" });
|
|
514
519
|
}
|
|
515
520
|
const checked = checkEditedRuleText(edit.text, rec.command);
|
|
516
521
|
if (!checked.ok) {
|
|
@@ -156,14 +156,27 @@ export declare function callPathFaceOf(call: ToolCallLike): DenyFace | undefined
|
|
|
156
156
|
/** Does some {@link PATH_RULE_FACES} ask column hold `face` — may a face-keyed ask row speak for a call on it, whatever
|
|
157
157
|
* the call's tool is called (a caller's tool declaring a read path target is on the read face too)? */
|
|
158
158
|
export declare function faceAskSpeaksFor(face: DenyFace | undefined): boolean;
|
|
159
|
+
/**
|
|
160
|
+
* The tools whose rule content NAMES THE VALUE of one parameter instead of a command or a path — the reference's `Skill(<name>)`
|
|
161
|
+
* form, one row per tool: `param` is the argument the rule's content is read against, `params` every scalar argument the tool
|
|
162
|
+
* carries (the parameter spelling `Tool(<param>:<pattern>)` those names used to reach is retired for such a tool, see
|
|
163
|
+
* {@link parseNameRuleContent}). The tool's real schema keys are pinned equal to `params` (test/backlog1318).
|
|
164
|
+
*/
|
|
165
|
+
export declare const NAME_RULE_FACES: Readonly<Record<string, {
|
|
166
|
+
readonly param: string;
|
|
167
|
+
readonly params: readonly string[];
|
|
168
|
+
}>>;
|
|
159
169
|
/**
|
|
160
170
|
* Which tools a rule may name — the model's tool set is DERIVED, never a hand-kept list: the shell tool
|
|
161
171
|
* takes the COMMAND grammar; every tool the catalog declares a path target for (Read / Edit / Write /
|
|
162
|
-
* Glob / NotebookEdit / …, `pathTargetOf`) takes the PATH grammar
|
|
163
|
-
* content this model can judge and is refused (`unsupported.tool`). The tool set therefore
|
|
164
|
-
* catalog's declarations, exactly as the protective fences' covered set does.
|
|
172
|
+
* Glob / NotebookEdit / …, `pathTargetOf`) takes the PATH grammar; the tools of {@link NAME_RULE_FACES} take the NAME grammar.
|
|
173
|
+
* A rule on any other tool has no content this model can judge and is refused (`unsupported.tool`). The tool set therefore
|
|
174
|
+
* moves with the catalog's declarations, exactly as the protective fences' covered set does.
|
|
165
175
|
*/
|
|
166
|
-
export declare function ruleToolGrammarOf(toolName: string): "command" | "path" | undefined;
|
|
176
|
+
export declare function ruleToolGrammarOf(toolName: string): "command" | "path" | "name" | undefined;
|
|
177
|
+
/** The value a name-grammar tool's call carries in its name argument — the string every NAME rule is read against, the one a
|
|
178
|
+
* `Skill` call is resolved by — or undefined (not a name-grammar tool; no such argument; not a string). */
|
|
179
|
+
export declare function nameRuleSubjectOf(toolName: string, args: unknown): string | undefined;
|
|
167
180
|
/** The names the persisted grammar judges a call under: every name the call answers to (the model's spelling, the wire
|
|
168
181
|
* name, the other aliases that resolve to it) the grammar speaks for, so a stored `Bash(…)` or `Edit(…)` row reaches a
|
|
169
182
|
* tool that answers to that name under another wire name, as a policy's rule does. Empty ⇒ the grammar speaks for none. */
|
|
@@ -191,7 +204,7 @@ export declare function laneToolNamesOf(req: Pick<import("./tool-policy.js").Too
|
|
|
191
204
|
* otherwise the working directory). Its predicate is {@link pathRuleReaches}. Not an allow form: a
|
|
192
205
|
* standing approval by path pattern would be a wider grant than any card ever shows a person.
|
|
193
206
|
*/
|
|
194
|
-
export type PersistedRuleMatch = "exact" | "prefix" | "wildcard" | "subpath" | "path";
|
|
207
|
+
export type PersistedRuleMatch = "exact" | "prefix" | "wildcard" | "subpath" | "path" | "name" | "name_prefix";
|
|
195
208
|
/**
|
|
196
209
|
* Where a rule applies — design/382 §4.1, the THREE consent dimensions.
|
|
197
210
|
*
|
|
@@ -525,6 +538,9 @@ export declare function parseRuleText(text: string, behavior: RuleBehavior): {
|
|
|
525
538
|
* `subpath` the command is the lexical-normal absolute directory (one leading slash), re-spelled in the
|
|
526
539
|
* double-slash directory form; for `path` it is the pattern as spelled. */
|
|
527
540
|
export declare function formatRuleText(command: string, match: PersistedRuleMatch, tool: PersistedRuleTool): string;
|
|
541
|
+
/** Does a name rule reach this name? `name`: equal; `name_prefix`: begins with it (no boundary — the reference's `startsWith`).
|
|
542
|
+
* A rule of any other family reaches no name. */
|
|
543
|
+
export declare function nameRuleAdmits(rule: Pick<PersistedRule, "match" | "command">, name: string): boolean;
|
|
528
544
|
/**
|
|
529
545
|
* The closed set of BASES a path-form pattern resolves against — facts of the CALL (its execution
|
|
530
546
|
* environment and its task), never of the rule:
|
|
@@ -675,8 +691,8 @@ export type ProgramRunReachOutcome = {
|
|
|
675
691
|
* other segment already reached.
|
|
676
692
|
*
|
|
677
693
|
* A rule body the lexer cannot read (foreign store bytes; every entrance validates against exactly
|
|
678
|
-
* this) reaches nothing. A
|
|
679
|
-
* {@link directoryRuleAdmits} and {@link
|
|
694
|
+
* this) reaches nothing. A rule of another family (path: `subpath` / `path`; name: `name` / `name_prefix`) reaches no
|
|
695
|
+
* command: its predicates are {@link directoryRuleAdmits}, {@link pathRuleReachOf} and {@link nameRuleAdmits}.
|
|
680
696
|
*/
|
|
681
697
|
export declare function programRunReachOf(rule: Pick<PersistedRule, "match" | "command">, command: string): ProgramRunReachOutcome;
|
|
682
698
|
/** The boolean face of {@link programRunReachOf}: `reached` only. The shadow view and the pins that
|
|
@@ -926,6 +942,30 @@ export declare function adjudicatePersistedPathRules(rules: readonly PersistedRu
|
|
|
926
942
|
readonly tighten?: PathTargetReading;
|
|
927
943
|
readonly allow?: PathTargetReading;
|
|
928
944
|
}, bases: PathRuleBases): PersistedRuleVerdict | undefined;
|
|
945
|
+
/**
|
|
946
|
+
* The name-family sibling of {@link adjudicatePersistedRules}: the same precedence walk over the rules of one name tool
|
|
947
|
+
* ({@link NAME_RULE_FACES}), judged on the one string the call names (`nameRuleSubjectOf`). A rule of the tool speaks for the call
|
|
948
|
+
* when it is live, of this behavior, in scope ({@link eligiblePersisted}, the one predicate) and {@link nameRuleAdmits} the name; the
|
|
949
|
+
* first behavior in {@link RULE_BEHAVIORS_BY_PRECEDENCE} whose rules speak decides, the first table-order rule of it is the answer.
|
|
950
|
+
* Every behavior is judged with the one predicate — a name has no "program run" the tightening direction could read wider — and
|
|
951
|
+
* there is nothing unreadable: a name is a string.
|
|
952
|
+
*/
|
|
953
|
+
export declare function adjudicatePersistedNameRules(rules: readonly PersistedRule[], call: {
|
|
954
|
+
tool: string;
|
|
955
|
+
name: string | undefined;
|
|
956
|
+
cwd: string | undefined;
|
|
957
|
+
sessionId?: string;
|
|
958
|
+
}): PersistedRuleVerdict | undefined;
|
|
959
|
+
/**
|
|
960
|
+
* The ONE offer a name tool's approval card carries: the exact rule for the name the call carries (`Skill(<name>)`, the reference's
|
|
961
|
+
* "don't ask again for <skill>" row), or undefined when no rule spells exactly that name (the round-trip law: the canonical text,
|
|
962
|
+
* parsed back as an allow, must be the exact name rule over the very name — a leading slash, an interior star or an unreadable
|
|
963
|
+
* spelling offers nothing rather than a rule that would not clear the ask it was minted on). The reference's second row, the first
|
|
964
|
+
* word as a prefix, is offered only for a name containing a space; it is not minted here (it would widen past the name shown).
|
|
965
|
+
*/
|
|
966
|
+
export declare function suggestRuleForName(tool: string, name: string): Extract<RuleOffer, {
|
|
967
|
+
kind: "single";
|
|
968
|
+
}> | undefined;
|
|
929
969
|
/**
|
|
930
970
|
* design/375 §5.2 — the per-segment coverage table for `command`: which segments an eligible rule
|
|
931
971
|
* already admits, judged with the SAME split and the same eligibility predicates as the gate's
|