spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

chore: Update mods/Browse files
GitHub Actions (publish-mods) authored 2 days ago parent a6e289a · commit 5a2676e1079c
14 changed files+136 -105compared with a6e289a
Mmods/sec-default/README.md
Mmods/sec-default/hooks/held-verdict/ask-kind.ts
Mmods/sec-default/hooks/held-verdict/held-kind.ts
Mmods/sec-default/hooks/held-verdict/verdicts/holds-over.ts
Mmods/sec-default/hooks/held-verdict/verdicts/index.ts
Dmods/sec-default/hooks/held-verdict/verdicts/is-person-tightening.ts
Mmods/sec-default/hooks/held-verdict/verdicts/is-same-verdict.ts
Amods/sec-default/hooks/held-verdict/verdicts/plugin-of.ts
Amods/sec-default/hooks/held-verdict/verdicts/stamped-verdict.ts
Mmods/sec-default/hooks/register.ts
Mmods/sec-default/tests/fixtures/tool-check/checked.ts
Mmods/sec-default/tests/fixtures/tool-check/host-ask.ts
Mmods/sec-default/tests/held-verdict.test.ts
Mmods/sec-default/tests/register.test.ts
@@ -34,7 +34,7 @@
3434 | `tool.describe`, `command.describe`, `agent.offer`, `agent.spawn` | When the subject's pinned `e.provider.tier` is `prepend` or `append` (a policy-installed plugin, the managed folder, a policy MCP server), continue past the user tier; a subject provided by `user`, `builtin` or `core` passes. |
3535 | `tool.register` | A caller in `prepend` or `append` continues past the user tier. A `user`-tier caller is refused by name while managed settings hold `allowedMcpServers` (set at all, empty included). With no allowlist, one is still refused whose tool, called `mcp__<plugin>__<tool>`, would read as a tool of a server the organization delivers: a `managedMcpServers` entry, or a server of the enterprise `managed-mcp.json`, which the policy read names under that key. So is one whose name reads as a plugin the organization seats itself (`prependPlugins`, `appendPlugins`, or an id managed `enabledPlugins` turns on, by `true` or by a version list, as the engine reads an entry), or as an MCP server such a plugin's own manifest declares, which the engine spells under the plugin's scope (`mcp__plugin_<plugin>_<server>__`). A `user`-tier plugin keeps its own name exactly when the id the engine recorded for it (`<name>@<marketplace>`: the settings key the loader resolved, never the plugin's manifest) is one managed `enabledPlugins` turn on, compared as the engine compares ids (NFC, then lower case). That is how an organization's own plugin registers its tools where the engine leaves it in the `user` tier: only a copy read in place from a local folder marketplace that managed settings name is vouched for, so a plugin from a remote marketplace (git, GitHub, npm) can never be vouched for in this release, and neither is one a version list turns on. For an organization that means such a plugin runs among a person's plugins in the chain, not ahead of them, and keeps its tools by this exemption alone. A plugin loaded with `--plugin-dir` is always `<name>@inline` and never matches; nor does a same-named plugin from another marketplace, one whose name only folds to the organization's, or an id policy lists only in `prependPlugins` or `appendPlugins`. A person who registers a marketplace of their own under the organization's marketplace name gets an equal id, and so the exemption: it gives that plugin what every plugin had before this hold existed, and `strictKnownMarketplaces` in managed settings closes it today by allowing no other marketplace source. The mod hears a plugin's id at its admission, so after a load that seats the mod afresh while other plugins stay seated (a load that folds again, as a policy change mid-session can cause) the organization's own plugin is refused its name until the session restarts. A plugin's own MCP servers are connected by the engine from its manifest, not registered through `tool.register`, so they are unaffected. An id's name is everything before its last at-sign, as the engine parts ids, so a scoped `@scope/name@npm` counts. A `managed-mcp.json` that is present and cannot be read whole names nobody: the policy read marks it (`managedMcpServersUnreadable`) and every such registration is refused while it stands, its servers never taken for none; the only other reader of the mark is `tool.list` (the next row), so a person's plugins still load and still set what is not pinned. A server the file names and the engine dropped with a warning is still named; a warning about a server that loads is no failure. Names are compared folded, as a model reads them (spelled as the engine spells a name inside a tool's name, every character outside letters, digits, `-` and `_` a `_` and nothing normalized; then lower case, every run of `-` and `_` one `_`; a seated plugin counts under its name composed and decomposed, since the engine spells its tools from its manifest's own form): a plugin named as the server in any case or with other separators, one whose name and tool together spell it, and one whose tool's own name folds to nothing, is refused. A server whose name folds to nothing (no letter or digit in it) cannot be told from any name, so while the policy names one every `user`-tier registration is refused. The engine pins the tool's name at this event (a hook beneath that hands on another name fails, and the chain goes on without it), so the name judged is the name registered. Otherwise it passes. |
3636 | `tool.list` | The tools of the organization's managed MCP servers, of the plugins it seats itself and of the MCP servers those plugins' own manifests declare (told by the name whole, as the engine spells it inside a tool's name: `mcp__<server>__`, never by a fold, so another server whose name only begins like the organization's is not taken for it; a seated plugin and its own servers, under its scope `mcp__plugin_<plugin>_`, fold to fold as the registration check compares, so policy may list its id in another letter case, and another plugin whose name begins with the seated one's and a `-` or `_` reads the same) are listed as the organization's tiers listed them; every other tool as the user tier left it. Where the server file cannot be read, every `mcp__` tool counts as the organization's. With no policy to read, or a refusal from either listing, the organization's listing stands whole. |
37-| `tool.check` | A deny, an ask a settings rule, a classic hook, a setting's own check or an organization's ceiling on the tool stands behind, an ask a host answers in the person's place and an ask a built-in or an organization's hook put to a person, hold whole over the user tier: when a person's plugin rewrote the verdict it was handed without tightening it (a more permissive decision, or the same decision under another reason or with what it names changed), the dispatch is run again past the user tier, and unless the chain's answer is the stricter, that verdict, if it is a deny or names what stands behind it or who it is put to, is the answer as the engine gave it. See [Deny rules hold](#deny-rules-hold). Every other verdict passes as the chain left it. |
37+| `tool.check` | A deny, an ask a settings rule, a classic hook, a setting's own check or an organization's ceiling on the tool stands behind, an ask a host answers in the person's place and an ask a built-in or an organization's plugin made, hold whole over the user tier: when a person's plugin rewrote the verdict it was handed without tightening it (a more permissive decision, or the same decision under another reason or with what it names changed), the dispatch is run again past the user tier, and unless the chain's answer is the stricter, that verdict, if it is a deny, names what stands behind it or is an ask a host answers, is the answer as the engine gave it. See [Deny rules hold](#deny-rules-hold). Every other verdict passes as the chain left it. |
3838 | `ui.log` | A line a plugin in `prepend` or `append` logs, this one's included, continues past the user tier: no user hook rewrites or drops it, and none runs inside the hook that logged it. Every other line passes. |
3939 | `env.get` | A read by a plugin in `prepend` or `append` continues past the user tier: no user hook answers what the organization's own plugin reads. Every other read passes. |
4040 | `env.set` | A write by a plugin in `prepend` or `append` continues past the user tier, whatever the variable: no user hook rewrites or drops what the organization's own plugin sets. A variable managed settings set in `env` (its name in any case) is pinned, and so are `CLAUDE_CODE_PROCESS_WRAPPER` where they name a launcher with `processWrapper` and, always, the nine variables that move where policy and sign-in are read from or how policy's tiers compose (`CLAUDE_CODE_MANAGED_SETTINGS_PATH`, `CLAUDE_CODE_REMOTE_SETTINGS_PATH`, `CLAUDE_CODE_MOCK_REMOTE_SETTINGS`, `CLAUDE_CODE_DISABLE_ADMIN_ENV_UNION`, `CLAUDE_CONFIG_DIR`, `CLAUDE_SECURESTORAGE_CONFIG_DIR`, `USE_LOCAL_OAUTH`, `USE_STAGING_OAUTH` and `CLAUDE_LOCAL_OAUTH_API_BASE`, which choose the server sign-in goes to; all nine on every build), and, on every build too, ten variables that can load code into a child the engine starts (`LD_PRELOAD`, `LD_LIBRARY_PATH`, `LD_AUDIT`, `DYLD_INSERT_LIBRARIES`, `DYLD_LIBRARY_PATH`, `DYLD_FRAMEWORK_PATH`, `BASH_ENV`, `ENV`, `ZDOTDIR`, `NODE_OPTIONS`): setting or unsetting it, a `user`-tier caller is refused by name and a built-in's write continues past the user tier. Any other variable is a person's plugin's to set. With no policy to read, every variable counts as pinned. `allowModsToOverrideDenyRules` does not unpin. See [Pinned variables](#pinned-variables). |
@@ -93,7 +93,7 @@
9393 `allowModsToOverrideDenyRules`: the plugins a person installs may answer
9494 over a settings deny rule on `tool.check`, as they could before this plugin
9595 held deny rules, and over every other deny, an ask rule, a classic hook's
96-ask, a setting's own ask or an ask a host or a person answers with it: the one option
96+ask, a setting's own ask or an ask a host answers or a plugin made with it: the one option
9797 covers all that holds there. Off unless it is the
9898 literal `true`; an option that reads as unset leaves them holding. See
9999 [Deny rules hold](#deny-rules-hold).
@@ -150,8 +150,8 @@
150150 chain's answer whatever decided it; an ask, when it names what decided it,
151151 a settings rule (`rule`), a classic hook (`hook`), a setting whose own
152152 check asked (`setting`) or an organization's ask cap on the tool
153- (`ceiling`), or names who answers it (`decider`: a host, or a person a
154- built-in or an organization's hook put it to). It is returned
153+ (`ceiling`) or a built-in's or an organization's plugin that made it
154+ (`plugin`), and an ask a host answers (`e.approver`). It is returned
155155 whole, so the reason this event answers, which a person reads in the
156156 prompt and the engine writes into a refusal, is the engine's, and what it
157157 names is still named. (The refusal then enters the conversation as a tool
@@ -165,15 +165,15 @@
165165 asked. An ask that names none of them and that the person answers at a
166166 dialog (the mode's own, a check of the engine's own that no setting turns
167167 on) is not held.
168-- A person: a hook that answers `ask` with `decider: "person"` keeps the
169- call from the classifier's allow. Made by a built-in or an organization's
170- hook, that ask holds, so a person's plugin neither allows it nor turns it
171- back into a plain ask. A person's own plugin may ask a person too, over a
172- verdict that does not hold, and over a host's ask by changing only its
173- `decider` to `"person"`: that is a tightening and passes. Any other change
174- to a host's ask stays held. As for any hook's ask, a rule for the whole tool
175- is not kept; a narrower rule the person saves is theirs and settles nothing
176- while the hook asks.
168+- A plugin: an ask a hook makes names the plugin (`plugin`, the engine's
169+ word, never the hook's), and no classifier's allow settles it. The run past
170+ the user tier holds no person's plugin, so a plugin named there is a
171+ built-in or an organization's: that ask holds, and a person's plugin neither
172+ allows it nor rewords it. A person's own plugin may ask too, over an allow
173+ or the mode's own ask: there its ask stands, because it is stricter. Its
174+ rewording of an ask that holds is held, with the notice. As for any hook's
175+ ask, a rule for the whole tool is not kept; a narrower rule the person saves
176+ is theirs and settles nothing while the hook asks.
177177 - The setting the engine names today is the read block's
178178 (`permissions.blockReadsOutsideWorkingDirectories`), for the two asks the
179179 block keeps for the person: about a shell command that reads outside the
@@ -182,9 +182,9 @@
182182 tools refuse such a read outright, and a deny holds as it is.
183183 - A host: where the permission callback of the program that runs the session
184184 answers its asks (an SDK host's `can_use_tool`, a permission prompt tool;
185- a cloud session's host is one), the engine's own ask says so
186- (`decider: "host"`), in every loop of the process, a subagent's included,
187- and only where the host would be asked. Such an ask holds, the mode's own
185+ a cloud session's host is one), the event says so (`e.approver` is
186+ `host`), in every loop of the process, a subagent's included, and only
187+ where the host would be asked. Such an ask holds, the mode's own
188188 too: it goes on to whatever the mode puts between an ask and the host
189189 (auto mode's classifier) and then to the callback, as it would with no
190190 plugin loaded. An evaluation the engine has not classified is held.
@@ -236,8 +236,8 @@
236236 settings from a <tool> call (<rule>); the deny rule holds over the plugins
237237 you install (allowModsToOverrideDenyRules)`, or `tried to reword` where
238238 the decision was kept and its words or its rule were not. The kinds are `a deny rule`,
239- `an ask rule`, `a <event> hook's ask`, `a <key> setting's ask`, `a host's
240- ask` and `a refusal` (a deny that names no rule); only the two rule kinds
239+ `an ask rule`, `a <event> hook's ask`, `a <key> setting's ask`, `a
240+ <plugin>'s ask`, `an ask cap`, `a host's ask` and `a refusal` (a deny that names no rule); only the two rule kinds
241241 carry `in your settings` and a rule. Plugins the
242242 engine ran as one
243243 batch are named together, as it names them (`audit+easy`). A plain `-p`
@@ -1,26 +1,32 @@
11 import type { EventResult } from 'claude-code'
22
3+import { pluginOf } from './verdicts/plugin-of.js'
4+
35 /**
46 * Names who asked, where no rule decided a verdict that holds: the classic
5- * hook first, then the setting whose check asked, then the host it goes to.
7+ * hook first, then the setting whose check asked, then the plugin that made
8+ * it, then the ceiling an organization set on the tool.
69 *
7- * An ask that names none of the three holds by the ceiling an organization
8- * set on the tool.
10+ * An ask that names none of them holds by the host that answers it.
911 *
1012 * @param held the ask in question
11- * @returns `<event> hook's ask`, `<key> setting's ask`, `host's ask`, or
12- * `ask cap`
13+ * @returns `<event> hook's ask`, `<key> setting's ask`, `<plugin>'s ask`,
14+ * `ask cap`, or `host's ask`
1315 */
1416 export function askKind(held: EventResult<'tool.check'>) {
17+ const plugin = pluginOf(held)
1518 const isHooked = held.hook !== undefined
1619 const isSet = held.setting !== undefined
17- const isHosted = held.decider !== undefined
20+ const isMade = plugin !== undefined
21+ const isCapped = held.ceiling !== undefined
1822
1923 return isHooked
2024 ? `${held.hook} hook's ask`
2125 : isSet
2226 ? `${held.setting} setting's ask`
23- : isHosted
24- ? `${held.decider}'s ask`
25- : 'ask cap'
27+ : isMade
28+ ? `${plugin}'s ask`
29+ : isCapped
30+ ? 'ask cap'
31+ : "host's ask"
2632 }
@@ -4,11 +4,10 @@
44
55 /**
66 * What kind of thing held over a person's plugin, as the notice calls it:
7- * `deny rule`, `ask rule`, `refusal`, or whose ask it was, else `ask cap`.
7+ * `deny rule`, `ask rule`, `refusal`, or whose ask it was.
88 *
99 * A deny that names no rule is a refusal even when it names a hook: the hook
10- * may only have asked, and something else refused. An ask that names nobody
11- * holds by the ceiling an organization set on the tool.
10+ * may only have asked, and something else refused.
1211 *
1312 * @param held the verdict that holds
1413 * @returns the kind
@@ -1,7 +1,7 @@
11 import type { EventResult } from 'claude-code'
22
3-import { isPersonTightening } from './is-person-tightening.js'
43 import { isSameVerdict } from './is-same-verdict.js'
4+import { pluginOf } from './plugin-of.js'
55 import Ranking from './ranking'
66
77 /**
@@ -9,25 +9,32 @@
99 * chain's answer: a deny, or an ask that names what stands behind it.
1010 *
1111 * It holds over a more permissive answer and over the same decision reworded
12- * or stripped of what it names; a stricter answer stands, and so does a host's
13- * ask that only changed who answers it to a person. A rule, a hook, a
14- * setting, a host, a person or a ceiling counts; the mode's own ask at a
15- * dialog never.
12+ * or stripped of what it names; a stricter answer stands. A rule, a hook, a
13+ * setting, a plugin or a ceiling counts, and a host answering the ask; the
14+ * mode's own ask at a dialog never. The run past the user tier holds no
15+ * person's plugin, so a plugin it names is a built-in or an organization's.
1616 *
1717 * @param held what the run past the user tier settled on
1818 * @param answer what the whole chain settled on
19+ * @param approver who answers this evaluation's asks (`e.approver`)
1920 * @returns true when the held verdict is the one to return, as it is
2021 */
21-export const holdsOver = (
22+export function holdsOver(
2223 held: EventResult<'tool.check'>,
2324 answer: EventResult<'tool.check'>,
24-) =>
25- !Ranking.isLooser(held, answer) &&
26- !isSameVerdict(held, answer) &&
27- !isPersonTightening(held, answer) &&
28- (held.decision === 'deny' ||
29- held.rule !== undefined ||
30- held.hook !== undefined ||
31- held.setting !== undefined ||
32- held.decider !== undefined ||
33- held.ceiling !== undefined)
25+ approver: string | undefined,
26+): boolean {
27+ const isAsked = held.decision === 'ask'
28+
29+ return (
30+ !Ranking.isLooser(held, answer) &&
31+ !isSameVerdict(held, answer) &&
32+ (held.decision === 'deny' ||
33+ held.rule !== undefined ||
34+ held.hook !== undefined ||
35+ held.setting !== undefined ||
36+ pluginOf(held) !== undefined ||
37+ held.ceiling !== undefined ||
38+ (isAsked && approver === 'host'))
39+ )
40+}
@@ -1,7 +1,9 @@
11 export * from './holds-over.js'
22 export * from './is-same-verdict.js'
3+export * from './plugin-of.js'
34 export * from './ranking'
45 export * from './rewritten-by-users.js'
6+export type * from './stamped-verdict.js'
57 export * from './unchecked-deny.js'
68
79 export * as default from '.'
Dmods/sec-default/hooks/held-verdict/verdicts/is-person-tightening.ts+0 -28
@@ -1,28 +1,0 @@
1-import type { EventResult } from 'claude-code'
2-
3-import { isSameVerdict } from './is-same-verdict.js'
4-
5-/**
6- * Whether an answer is a host's ask with nothing changed but its `decider`,
7- * now a person: the same ask, which no classifier's allow settles.
8- *
9- * @param held what the run past the user tier settled on
10- * @param answer what the whole chain settled on
11- * @returns true when the answer only tightened the ask
12- */
13-export function isPersonTightening(
14- held: EventResult<'tool.check'>,
15- answer: EventResult<'tool.check'>,
16-): boolean {
17- const decider: string | undefined = answer.decider
18-
19- return (
20- held.decision === 'ask' &&
21- held.decider === 'host' &&
22- decider === 'person' &&
23- isSameVerdict({ ...held, decider: undefined }, {
24- ...answer,
25- decider: undefined,
26- })
27- )
28-}
@@ -2,7 +2,7 @@
22
33 /**
44 * Whether two `tool.check` verdicts say the same in every field: decision,
5- * reason, and whatever either names (a rule, a hook, a setting, a host).
5+ * reason, and whatever either names (a rule, a hook, a setting, a plugin).
66 *
77 * @param one a verdict
88 * @param other another, or undefined when nothing settled
@@ -1,0 +1,13 @@
1+import type { StampedVerdict } from './stamped-verdict.js'
2+
3+/**
4+ * The plugin the engine names on a `tool.check` ask, as the hook that made it.
5+ *
6+ * The released declarations this mod is checked against name neither this key
7+ * nor `e.approver` until the next release, so it reads the shape it needs.
8+ *
9+ * @param verdict a verdict
10+ * @returns the plugin, or undefined on an ask no hook made
11+ */
12+export const pluginOf = (verdict: StampedVerdict): string | undefined =>
13+ verdict.plugin
@@ -1,0 +1,9 @@
1+/**
2+ * A `tool.check` verdict, read for the plugin that made its ask.
3+ *
4+ * `decision` keeps the type from being all-optional, which a verdict fails.
5+ */
6+export type StampedVerdict = {
7+ decision: string
8+ plugin?: string
9+}
@@ -116,7 +116,9 @@
116116
117117 const held = await next.to(e, 'append')
118118
119- if (!HeldVerdict.holdsOver(held, answer)) {
119+ const { approver }: { tool: string; approver?: string } = e
120+
121+ if (!HeldVerdict.holdsOver(held, answer, approver)) {
120122 return answer
121123 }
122124
@@ -7,3 +7,12 @@
77 tool: 'Bash',
88 input: { command: 'echo x' },
99 }
10+
11+/**
12+ * The same question where a host answers the asks: the engine's `approver`,
13+ * which the released declarations do not name yet.
14+ */
15+export const CHECKED_BY_HOST: Args<'tool.check'> = Object.assign(
16+ { approver: 'host' },
17+ CHECKED,
18+)
@@ -6,6 +6,5 @@
66 */
77 export const HOST_ASK: EventResult<'tool.check'> = {
88 decision: 'ask',
9- reason: 'Bash needs approval',
10- decider: 'host',
9+ reason: 'The host answers this one',
1110 }
@@ -16,16 +16,16 @@
1616 Fixtures.PLAIN_DENY,
1717 Fixtures.ASKED,
1818 Fixtures.ALLOWED,
19- ].map(held => Hooks.holdsOver(held, Fixtures.ALLOWED)),
19+ ].map(held => Hooks.holdsOver(held, Fixtures.ALLOWED, undefined)),
2020 ).toEqual([true, true, true, true, true, false, false])
2121 })
2222
2323 test("a setting's ask and a host's hold over an allow and a rewording", () => {
2424 expect(
2525 [Fixtures.SETTING_ASK, Fixtures.HOST_ASK].map(held => [
26- Hooks.holdsOver(held, Fixtures.ALLOWED),
27- Hooks.holdsOver(held, Fixtures.ASKED),
28- Hooks.holdsOver(held, { ...held }),
26+ Hooks.holdsOver(held, Fixtures.ALLOWED, 'host'),
27+ Hooks.holdsOver(held, Fixtures.ASKED, 'host'),
28+ Hooks.holdsOver(held, { ...held }, 'host'),
2929 ]),
3030 ).toEqual([
3131 [true, true, false],
@@ -33,13 +33,16 @@
3333 ])
3434 })
3535
36- test('a setting or decider the answer itself names holds nothing', () => {
36+ test('a setting the answer itself names holds nothing', () => {
3737 expect(
38- Hooks.holdsOver(Fixtures.ASKED, {
39- ...Fixtures.ALLOWED,
40- setting: 'permissions.blockReadsOutsideWorkingDirectories',
41- decider: 'host',
42- }),
38+ Hooks.holdsOver(
39+ Fixtures.ASKED,
40+ {
41+ ...Fixtures.ALLOWED,
42+ setting: 'permissions.blockReadsOutsideWorkingDirectories',
43+ },
44+ undefined,
45+ ),
4346 ).toBe(false)
4447 })
4548
@@ -73,9 +76,9 @@
7376 Fixtures.HOOK_DENY,
7477 Fixtures.HOOK_ASK,
7578 ].map(held => [
76- Hooks.holdsOver(held, Fixtures.ASKED),
77- Hooks.holdsOver(held, Fixtures.PLAIN_DENY),
78- Hooks.holdsOver(held, { ...held }),
79+ Hooks.holdsOver(held, Fixtures.ASKED, undefined),
80+ Hooks.holdsOver(held, Fixtures.PLAIN_DENY, undefined),
81+ Hooks.holdsOver(held, { ...held }, undefined),
7982 ]),
8083 ).toEqual([
8184 [true, true, false],
@@ -87,20 +90,28 @@
8790
8891 test('an ask stripped of its rule, same sentence, is no same verdict', () => {
8992 expect(
90- Hooks.holdsOver(Fixtures.RULE_ASK, {
91- decision: Fixtures.RULE_ASK.decision,
92- reason: Fixtures.RULE_ASK.reason,
93- }),
93+ Hooks.holdsOver(
94+ Fixtures.RULE_ASK,
95+ {
96+ decision: Fixtures.RULE_ASK.decision,
97+ reason: Fixtures.RULE_ASK.reason,
98+ },
99+ undefined,
100+ ),
94101 ).toBe(true)
95102 })
96103
97104 test('a rule or hook the answer itself names makes nothing hold', () => {
98105 expect(
99- Hooks.holdsOver(Fixtures.ASKED, {
100- ...Fixtures.ALLOWED,
101- rule: 'Bash(echo *)',
102- hook: 'PreToolUse',
103- }),
106+ Hooks.holdsOver(
107+ Fixtures.ASKED,
108+ {
109+ ...Fixtures.ALLOWED,
110+ rule: 'Bash(echo *)',
111+ hook: 'PreToolUse',
112+ },
113+ undefined,
114+ ),
104115 ).toBe(false)
105116 })
106117
@@ -838,7 +838,9 @@
838838
839839 Fixtures.checksAnswered(on, Fixtures.HOST_ASK)
840840
841- expect(await $.tool.check(Fixtures.CHECKED)).toEqual(Fixtures.HOST_ASK)
841+ expect(await $.tool.check(Fixtures.CHECKED_BY_HOST)).toEqual(
842+ Fixtures.HOST_ASK,
843+ )
842844
843845 expect(lines).toEqual([
844846 `transcript: ${Hooks.heldNotice('easy', 'Bash', Fixtures.HOST_ASK, 'lift')}`,
@@ -853,7 +855,7 @@
853855 on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY }))
854856 Fixtures.checksAnswered(on, Fixtures.HOST_ASK)
855857
856- expect(await $.tool.check(Fixtures.CHECKED)).toEqual({
858+ expect(await $.tool.check(Fixtures.CHECKED_BY_HOST)).toEqual({
857859 decision: 'allow',
858860 })
859861 },
@@ -1857,9 +1859,9 @@
18571859 })
18581860
18591861 test("an organization's ask cap holds over a plugin's allow", () => {
1860- expect(Hooks.holdsOver(Fixtures.CEILING_ASK, Fixtures.CEILING_ALLOW)).toBe(
1861- true,
1862- )
1862+ expect(
1863+ Hooks.holdsOver(Fixtures.CEILING_ASK, Fixtures.CEILING_ALLOW, undefined),
1864+ ).toBe(true)
18631865 })
18641866
18651867 test('the launcher variable is pinned where policy names a launcher', () => {