@inhuman.tools/turn 0.0.0-stage → 0.2.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/LICENSE.md +7 -0
- package/dist/agent-calls/acceptance.d.ts +12 -0
- package/dist/agent-calls/acceptance.d.ts.map +1 -0
- package/dist/agent-calls/acceptance.js +64 -0
- package/dist/agent-calls/acceptance.js.map +1 -0
- package/dist/agent-calls/callbacks.d.ts +14 -0
- package/dist/agent-calls/callbacks.d.ts.map +1 -0
- package/dist/agent-calls/callbacks.js +59 -0
- package/dist/agent-calls/callbacks.js.map +1 -0
- package/dist/agent-calls/delivery.d.ts +7 -0
- package/dist/agent-calls/delivery.d.ts.map +1 -0
- package/dist/agent-calls/delivery.js +16 -0
- package/dist/agent-calls/delivery.js.map +1 -0
- package/dist/agent-calls/index.d.ts +7 -0
- package/dist/agent-calls/index.d.ts.map +1 -0
- package/dist/agent-calls/index.js +6 -0
- package/dist/agent-calls/index.js.map +1 -0
- package/dist/agent-calls/operator.d.ts +21 -0
- package/dist/agent-calls/operator.d.ts.map +1 -0
- package/dist/agent-calls/operator.js +78 -0
- package/dist/agent-calls/operator.js.map +1 -0
- package/dist/agent-calls/runtime.d.ts +99 -0
- package/dist/agent-calls/runtime.d.ts.map +1 -0
- package/dist/agent-calls/runtime.js +245 -0
- package/dist/agent-calls/runtime.js.map +1 -0
- package/dist/agent-calls/types.d.ts +31 -0
- package/dist/agent-calls/types.d.ts.map +1 -0
- package/dist/agent-calls/types.js +2 -0
- package/dist/agent-calls/types.js.map +1 -0
- package/dist/context/compact-floors.d.ts +43 -0
- package/dist/context/compact-floors.d.ts.map +1 -0
- package/dist/context/compact-floors.js +52 -0
- package/dist/context/compact-floors.js.map +1 -0
- package/dist/context/compact.d.ts +42 -0
- package/dist/context/compact.d.ts.map +1 -0
- package/dist/context/compact.js +155 -0
- package/dist/context/compact.js.map +1 -0
- package/dist/context/delivery.d.ts +13 -0
- package/dist/context/delivery.d.ts.map +1 -0
- package/dist/context/delivery.js +6 -0
- package/dist/context/delivery.js.map +1 -0
- package/dist/context/history.d.ts +66 -0
- package/dist/context/history.d.ts.map +1 -0
- package/dist/context/history.js +573 -0
- package/dist/context/history.js.map +1 -0
- package/dist/context/index.d.ts +10 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +10 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/observations.d.ts +7 -0
- package/dist/context/observations.d.ts.map +1 -0
- package/dist/context/observations.js +11 -0
- package/dist/context/observations.js.map +1 -0
- package/dist/context/retention/defaults.d.ts +10 -0
- package/dist/context/retention/defaults.d.ts.map +1 -0
- package/dist/context/retention/defaults.js +47 -0
- package/dist/context/retention/defaults.js.map +1 -0
- package/dist/context/retention/index.d.ts +14 -0
- package/dist/context/retention/index.d.ts.map +1 -0
- package/dist/context/retention/index.js +21 -0
- package/dist/context/retention/index.js.map +1 -0
- package/dist/context/retention/narrow.d.ts +22 -0
- package/dist/context/retention/narrow.d.ts.map +1 -0
- package/dist/context/retention/narrow.js +140 -0
- package/dist/context/retention/narrow.js.map +1 -0
- package/dist/context/retention/render.d.ts +37 -0
- package/dist/context/retention/render.d.ts.map +1 -0
- package/dist/context/retention/render.js +149 -0
- package/dist/context/retention/render.js.map +1 -0
- package/dist/context/retention/resolve.d.ts +51 -0
- package/dist/context/retention/resolve.d.ts.map +1 -0
- package/dist/context/retention/resolve.js +254 -0
- package/dist/context/retention/resolve.js.map +1 -0
- package/dist/context/retention/schema.d.ts +345 -0
- package/dist/context/retention/schema.d.ts.map +1 -0
- package/dist/context/retention/schema.js +149 -0
- package/dist/context/retention/schema.js.map +1 -0
- package/dist/context/retention/stamp.d.ts +25 -0
- package/dist/context/retention/stamp.d.ts.map +1 -0
- package/dist/context/retention/stamp.js +103 -0
- package/dist/context/retention/stamp.js.map +1 -0
- package/dist/context/retention/step.d.ts +33 -0
- package/dist/context/retention/step.d.ts.map +1 -0
- package/dist/context/retention/step.js +74 -0
- package/dist/context/retention/step.js.map +1 -0
- package/dist/context/retention/view.d.ts +57 -0
- package/dist/context/retention/view.d.ts.map +1 -0
- package/dist/context/retention/view.js +121 -0
- package/dist/context/retention/view.js.map +1 -0
- package/dist/context/squeeze.d.ts +45 -0
- package/dist/context/squeeze.d.ts.map +1 -0
- package/dist/context/squeeze.js +182 -0
- package/dist/context/squeeze.js.map +1 -0
- package/dist/context/tools.d.ts +16 -0
- package/dist/context/tools.d.ts.map +1 -0
- package/dist/context/tools.js +73 -0
- package/dist/context/tools.js.map +1 -0
- package/dist/context/transcript.d.ts +151 -0
- package/dist/context/transcript.d.ts.map +1 -0
- package/dist/context/transcript.js +659 -0
- package/dist/context/transcript.js.map +1 -0
- package/dist/policy-processor/bypass.d.ts +95 -0
- package/dist/policy-processor/bypass.d.ts.map +1 -0
- package/dist/policy-processor/bypass.js +188 -0
- package/dist/policy-processor/bypass.js.map +1 -0
- package/dist/policy-processor/classifier.d.ts +47 -0
- package/dist/policy-processor/classifier.d.ts.map +1 -0
- package/dist/policy-processor/classifier.js +671 -0
- package/dist/policy-processor/classifier.js.map +1 -0
- package/dist/policy-processor/cluster.d.ts +7 -0
- package/dist/policy-processor/cluster.d.ts.map +1 -0
- package/dist/policy-processor/cluster.js +32 -0
- package/dist/policy-processor/cluster.js.map +1 -0
- package/dist/policy-processor/evidence.d.ts +26 -0
- package/dist/policy-processor/evidence.d.ts.map +1 -0
- package/dist/policy-processor/evidence.js +204 -0
- package/dist/policy-processor/evidence.js.map +1 -0
- package/dist/policy-processor/explain.d.ts +59 -0
- package/dist/policy-processor/explain.d.ts.map +1 -0
- package/dist/policy-processor/explain.js +224 -0
- package/dist/policy-processor/explain.js.map +1 -0
- package/dist/policy-processor/index.d.ts +6 -0
- package/dist/policy-processor/index.d.ts.map +1 -0
- package/dist/policy-processor/index.js +6 -0
- package/dist/policy-processor/index.js.map +1 -0
- package/dist/policy-processor/license.d.ts +23 -0
- package/dist/policy-processor/license.d.ts.map +1 -0
- package/dist/policy-processor/license.js +47 -0
- package/dist/policy-processor/license.js.map +1 -0
- package/dist/policy-processor/peel-cwd.d.ts +2 -0
- package/dist/policy-processor/peel-cwd.d.ts.map +1 -0
- package/dist/policy-processor/peel-cwd.js +2 -0
- package/dist/policy-processor/peel-cwd.js.map +1 -0
- package/dist/policy-processor/real-path.d.ts +7 -0
- package/dist/policy-processor/real-path.d.ts.map +1 -0
- package/dist/policy-processor/real-path.js +42 -0
- package/dist/policy-processor/real-path.js.map +1 -0
- package/dist/policy-processor/scope.d.ts +111 -0
- package/dist/policy-processor/scope.d.ts.map +1 -0
- package/dist/policy-processor/scope.js +629 -0
- package/dist/policy-processor/scope.js.map +1 -0
- package/dist/policy-processor/self-protect.d.ts +10 -0
- package/dist/policy-processor/self-protect.d.ts.map +1 -0
- package/dist/policy-processor/self-protect.js +48 -0
- package/dist/policy-processor/self-protect.js.map +1 -0
- package/dist/policy-processor/soft-deny.d.ts +75 -0
- package/dist/policy-processor/soft-deny.d.ts.map +1 -0
- package/dist/policy-processor/soft-deny.js +185 -0
- package/dist/policy-processor/soft-deny.js.map +1 -0
- package/dist/policy-processor/unattended-guard.d.ts +28 -0
- package/dist/policy-processor/unattended-guard.d.ts.map +1 -0
- package/dist/policy-processor/unattended-guard.js +145 -0
- package/dist/policy-processor/unattended-guard.js.map +1 -0
- package/dist/session/cancel.d.ts +14 -0
- package/dist/session/cancel.d.ts.map +1 -0
- package/dist/session/cancel.js +32 -0
- package/dist/session/cancel.js.map +1 -0
- package/dist/session/commands.d.ts +41 -0
- package/dist/session/commands.d.ts.map +1 -0
- package/dist/session/commands.js +241 -0
- package/dist/session/commands.js.map +1 -0
- package/dist/session/compact-actor.d.ts +23 -0
- package/dist/session/compact-actor.d.ts.map +1 -0
- package/dist/session/compact-actor.js +91 -0
- package/dist/session/compact-actor.js.map +1 -0
- package/dist/session/compact-env.d.ts +54 -0
- package/dist/session/compact-env.d.ts.map +1 -0
- package/dist/session/compact-env.js +345 -0
- package/dist/session/compact-env.js.map +1 -0
- package/dist/session/compact.d.ts +42 -0
- package/dist/session/compact.d.ts.map +1 -0
- package/dist/session/compact.js +68 -0
- package/dist/session/compact.js.map +1 -0
- package/dist/session/index.d.ts +9 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +8 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/park.d.ts +16 -0
- package/dist/session/park.d.ts.map +1 -0
- package/dist/session/park.js +73 -0
- package/dist/session/park.js.map +1 -0
- package/dist/session/side-channel-reminder.d.ts +41 -0
- package/dist/session/side-channel-reminder.d.ts.map +1 -0
- package/dist/session/side-channel-reminder.js +132 -0
- package/dist/session/side-channel-reminder.js.map +1 -0
- package/dist/session/types.d.ts +44 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +2 -0
- package/dist/session/types.js.map +1 -0
- package/dist/tool-catalog/catalog.d.ts +77 -0
- package/dist/tool-catalog/catalog.d.ts.map +1 -0
- package/dist/tool-catalog/catalog.js +380 -0
- package/dist/tool-catalog/catalog.js.map +1 -0
- package/dist/tool-catalog/index.d.ts +4 -0
- package/dist/tool-catalog/index.d.ts.map +1 -0
- package/dist/tool-catalog/index.js +3 -0
- package/dist/tool-catalog/index.js.map +1 -0
- package/dist/tool-catalog/recipes.d.ts +36 -0
- package/dist/tool-catalog/recipes.d.ts.map +1 -0
- package/dist/tool-catalog/recipes.js +121 -0
- package/dist/tool-catalog/recipes.js.map +1 -0
- package/dist/tool-catalog/types.d.ts +103 -0
- package/dist/tool-catalog/types.d.ts.map +1 -0
- package/dist/tool-catalog/types.js +5 -0
- package/dist/tool-catalog/types.js.map +1 -0
- package/dist/tool-runtime/approval/approved.d.ts +13 -0
- package/dist/tool-runtime/approval/approved.d.ts.map +1 -0
- package/dist/tool-runtime/approval/approved.js +20 -0
- package/dist/tool-runtime/approval/approved.js.map +1 -0
- package/dist/tool-runtime/approval/dist.d.ts +6 -0
- package/dist/tool-runtime/approval/dist.d.ts.map +1 -0
- package/dist/tool-runtime/approval/dist.js +28 -0
- package/dist/tool-runtime/approval/dist.js.map +1 -0
- package/dist/tool-runtime/approval/inbound.d.ts +7 -0
- package/dist/tool-runtime/approval/inbound.d.ts.map +1 -0
- package/dist/tool-runtime/approval/inbound.js +48 -0
- package/dist/tool-runtime/approval/inbound.js.map +1 -0
- package/dist/tool-runtime/approval/unattended.d.ts +8 -0
- package/dist/tool-runtime/approval/unattended.d.ts.map +1 -0
- package/dist/tool-runtime/approval/unattended.js +23 -0
- package/dist/tool-runtime/approval/unattended.js.map +1 -0
- package/dist/tool-runtime/blob-port.d.ts +46 -0
- package/dist/tool-runtime/blob-port.d.ts.map +1 -0
- package/dist/tool-runtime/blob-port.js +47 -0
- package/dist/tool-runtime/blob-port.js.map +1 -0
- package/dist/tool-runtime/extra-root.d.ts +3 -0
- package/dist/tool-runtime/extra-root.d.ts.map +1 -0
- package/dist/tool-runtime/extra-root.js +14 -0
- package/dist/tool-runtime/extra-root.js.map +1 -0
- package/dist/tool-runtime/handler.d.ts +29 -0
- package/dist/tool-runtime/handler.d.ts.map +1 -0
- package/dist/tool-runtime/handler.js +460 -0
- package/dist/tool-runtime/handler.js.map +1 -0
- package/dist/tool-runtime/index.d.ts +14 -0
- package/dist/tool-runtime/index.d.ts.map +1 -0
- package/dist/tool-runtime/index.js +14 -0
- package/dist/tool-runtime/index.js.map +1 -0
- package/dist/tool-runtime/peel.d.ts +19 -0
- package/dist/tool-runtime/peel.d.ts.map +1 -0
- package/dist/tool-runtime/peel.js +74 -0
- package/dist/tool-runtime/peel.js.map +1 -0
- package/dist/tool-runtime/plugin-context.d.ts +13 -0
- package/dist/tool-runtime/plugin-context.d.ts.map +1 -0
- package/dist/tool-runtime/plugin-context.js +55 -0
- package/dist/tool-runtime/plugin-context.js.map +1 -0
- package/dist/tool-runtime/recall.d.ts +18 -0
- package/dist/tool-runtime/recall.d.ts.map +1 -0
- package/dist/tool-runtime/recall.js +100 -0
- package/dist/tool-runtime/recall.js.map +1 -0
- package/dist/tool-runtime/run-system-tool.d.ts +13 -0
- package/dist/tool-runtime/run-system-tool.d.ts.map +1 -0
- package/dist/tool-runtime/run-system-tool.js +230 -0
- package/dist/tool-runtime/run-system-tool.js.map +1 -0
- package/dist/tool-runtime/tool-error.d.ts +6 -0
- package/dist/tool-runtime/tool-error.d.ts.map +1 -0
- package/dist/tool-runtime/tool-error.js +6 -0
- package/dist/tool-runtime/tool-error.js.map +1 -0
- package/dist/tool-runtime/worker-tools.d.ts +8 -0
- package/dist/tool-runtime/worker-tools.d.ts.map +1 -0
- package/dist/tool-runtime/worker-tools.js +65 -0
- package/dist/tool-runtime/worker-tools.js.map +1 -0
- package/dist/tool-runtime/write-admit.d.ts +21 -0
- package/dist/tool-runtime/write-admit.d.ts.map +1 -0
- package/dist/tool-runtime/write-admit.js +89 -0
- package/dist/tool-runtime/write-admit.js.map +1 -0
- package/package.json +114 -3
- package/policy/auto-mode-ask.scm +201 -0
- package/policy/bypass-detect.prompt +55 -0
- package/policy/classifier-bash-incomplete.prompt +46 -0
- package/policy/classifier-bash.prompt +45 -0
- package/policy/classifier-mcp.prompt +45 -0
- package/policy/classifier-read.prompt +44 -0
- package/policy/classifier-write-outside.prompt +46 -0
- package/policy/classifier-write-risky.prompt +45 -0
- package/policy/classifier-write-workspace.prompt +46 -0
- package/policy/classifier-write.prompt +45 -0
- package/policy/soft-deny-round1.prompt +51 -0
- package/policy/soft-deny-round2.prompt +60 -0
- package/src/agent-calls/acceptance.ts +71 -0
- package/src/agent-calls/callbacks.ts +75 -0
- package/src/agent-calls/delivery.ts +15 -0
- package/src/agent-calls/index.ts +16 -0
- package/src/agent-calls/operator.ts +99 -0
- package/src/agent-calls/runtime.ts +310 -0
- package/src/agent-calls/types.ts +33 -0
- package/src/context/compact-floors.ts +76 -0
- package/src/context/compact.ts +176 -0
- package/src/context/delivery.ts +15 -0
- package/src/context/history.ts +725 -0
- package/src/context/index.ts +35 -0
- package/src/context/observations.ts +13 -0
- package/src/context/retention/defaults.ts +58 -0
- package/src/context/retention/index.ts +45 -0
- package/src/context/retention/narrow.ts +123 -0
- package/src/context/retention/render.ts +177 -0
- package/src/context/retention/resolve.ts +306 -0
- package/src/context/retention/schema.ts +173 -0
- package/src/context/retention/stamp.ts +121 -0
- package/src/context/retention/step.ts +85 -0
- package/src/context/retention/view.ts +161 -0
- package/src/context/squeeze.ts +218 -0
- package/src/context/tools.ts +92 -0
- package/src/context/transcript.ts +705 -0
- package/src/policy-processor/README.md +43 -0
- package/src/policy-processor/bypass.ts +246 -0
- package/src/policy-processor/classifier.ts +711 -0
- package/src/policy-processor/cluster.ts +31 -0
- package/src/policy-processor/evidence.ts +214 -0
- package/src/policy-processor/explain.ts +310 -0
- package/src/policy-processor/index.ts +6 -0
- package/src/policy-processor/license.ts +45 -0
- package/src/policy-processor/peel-cwd.ts +1 -0
- package/src/policy-processor/real-path.ts +41 -0
- package/src/policy-processor/scope.ts +655 -0
- package/src/policy-processor/self-protect.ts +46 -0
- package/src/policy-processor/soft-deny.ts +225 -0
- package/src/policy-processor/unattended-guard.ts +158 -0
- package/src/session/cancel.ts +34 -0
- package/src/session/commands.ts +292 -0
- package/src/session/compact-actor.ts +132 -0
- package/src/session/compact-env.ts +539 -0
- package/src/session/compact.ts +95 -0
- package/src/session/index.ts +18 -0
- package/src/session/park.ts +73 -0
- package/src/session/side-channel-reminder.ts +148 -0
- package/src/session/types.ts +50 -0
- package/src/tool-catalog/catalog.ts +282 -0
- package/src/tool-catalog/index.ts +26 -0
- package/src/tool-catalog/recipes.ts +154 -0
- package/src/tool-catalog/types.ts +110 -0
- package/src/tool-runtime/approval/approved.ts +20 -0
- package/src/tool-runtime/approval/dist.ts +29 -0
- package/src/tool-runtime/approval/inbound.ts +50 -0
- package/src/tool-runtime/approval/unattended.ts +24 -0
- package/src/tool-runtime/blob-port.ts +64 -0
- package/src/tool-runtime/extra-root.ts +16 -0
- package/src/tool-runtime/handler.ts +576 -0
- package/src/tool-runtime/index.ts +21 -0
- package/src/tool-runtime/peel.ts +91 -0
- package/src/tool-runtime/plugin-context.ts +68 -0
- package/src/tool-runtime/recall.ts +104 -0
- package/src/tool-runtime/run-system-tool.ts +273 -0
- package/src/tool-runtime/tool-error.ts +6 -0
- package/src/tool-runtime/worker-tools.ts +76 -0
- package/src/tool-runtime/write-admit.ts +90 -0
- package/README.md +0 -3
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# `@inhuman.tools/policy-processor`
|
|
2
|
+
|
|
3
|
+
JSON floor for the **shell-argv** and **file-access** clusters, plus the monadic authorization wait and one auto remainder PDP. Prep walks an [unbash](https://www.npmjs.com/package/unbash) AST into prepared questions; the matcher never sees `|`, `xargs`, or `FOO=bar`. Floor is data (`policy.json`). Remainder is Arrival Scheme (`auto-mode-ask.scm`); each `.prompt` is judgement (`ext/prompt/isolated` → child `CompletionExpectation`). `license` is the PEP wait.
|
|
4
|
+
|
|
5
|
+
**Floor:** one `policy.json` — `read_file` / `write_file` enable lists (`!` deny), `read_block` / `write_block` hard-block, and a `bash` command tree → `approve | deny | ask | block`. A cluster the document did not define is `ask`. Empty file list is deny-all. `allow` is a spelling of `approve`. `assume` smokes the bash processor at compile.
|
|
6
|
+
|
|
7
|
+
**Host:** `@inhuman.tools/policy-processor/license` — `license(job, turn, root) → Promise<boolean>`. Programs live on the session root (`policyProgram`, `judgeAskProgram`); compiled Policy / remainder votes intern on that root. Matcher compile is `LicenseScope.compiledPolicy(program)` → `{ bash, read, write }`. Dist ask: [`auto-mode-ask.scm`](../../policy/auto-mode-ask.scm) + [`classifier-bash.prompt`](../../policy/classifier-bash.prompt) / [`classifier-bash-incomplete.prompt`](../../policy/classifier-bash-incomplete.prompt) (Tirith nested-incomplete remainder) / [`classifier-read.prompt`](../../policy/classifier-read.prompt) / [`classifier-write.prompt`](../../policy/classifier-write.prompt) (in-enable `!`) / [`classifier-write-outside.prompt`](../../policy/classifier-write-outside.prompt) / [`classifier-write-risky.prompt`](../../policy/classifier-write-risky.prompt) / [`classifier-mcp.prompt`](../../policy/classifier-mcp.prompt). Registers `classifier/bash` `classifier/read` `classifier/write` `classifier/mcp`. Floor deny is a join vote (no remainder).
|
|
8
|
+
|
|
9
|
+
**Defaults:** empty-config envelope: [`src/reasonable-defaults.json`](../../../../packages-policy/policy/src/reasonable-defaults.json).
|
|
10
|
+
|
|
11
|
+
## Equation
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
job, turn, root
|
|
15
|
+
→ license
|
|
16
|
+
→ reads root.policyProgram / judgeAskProgram
|
|
17
|
+
→ writes policyResult / classifierResult / classifierAbstain
|
|
18
|
+
→ boolean
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Matcher (still the floor):
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
policy.json → compiledPolicy → { bash(command), read(path), write(path) } → approve | deny | ask | block
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`block` is unliftable (descending arms cannot override it; auto has no processor for it). `deny` is overridable by children in the floor lattice; auto has no deny remainder. Prep is inside `bash`. Cut keep is per node inside `eval_T`. The arity table that *discovers* glued-short shapes of `I` is not this package.
|
|
28
|
+
|
|
29
|
+
## Tirith shell floor
|
|
30
|
+
|
|
31
|
+
`FloorHandle` checks the complete original shell command with the installed Tirith binary before publishing `policyResult`. `checkBash` and the Scheme floor remain pure. The host joins both votes with `block > deny > ask > approve`. Tirith **coverage** (not raw `action`) is the join: concrete allow/warn/block map to `approve`/`ask`/`block`; nested-incomplete-only is remainder `ask`; offline package-intel gaps are not a finding; scanner failure is `block`. Dist ask + `classifier-bash-incomplete.prompt` is the nested-incomplete intern. Classifier evidence is concrete findings only.
|
|
32
|
+
|
|
33
|
+
The adapter uses argument-array execution, POSIX parsing (matching the shell tool's `/bin/sh`), offline local analysis, a five-second timeout, a 1 MiB output limit, and cancellation. It validates JSON schema 3 and the verdict/exit-code pair. Program, command, tool name, or workspace changes cancel pending checks; cancelled, released, and closed jobs cannot receive stale votes. No verdict is published until inspection finishes.
|
|
34
|
+
|
|
35
|
+
`tirith-policy/.tirith/policy.yaml` is the package-owned scanner policy. Child configuration and state are isolated from user/project policy files, allowlists, and bypass environment variables. Each check uses temporary state that is removed afterward, so this integration uses Tirith's bundled detection rules without a persistent threat-intelligence cache or background updates. It is command inspection, not execution containment.
|
|
36
|
+
|
|
37
|
+
Structured findings are retained in `policyEvidenceJson`; `policyMessage` carries concrete and nested-incomplete copy into the shell approval/receipt UI (intel-gap is silent). The remainder PIP includes **concrete** findings only, labeled untrusted as instructions, and uses that cut in cache identity.
|
|
38
|
+
|
|
39
|
+
Case guards live in `src/__tests__/tirith-floor.test.ts` and `src/__tests__/tirith.test.ts`. They cover precedence, pending checks, lifecycle invalidation, scanner failures, output validation, hostile configuration, command-as-data handling, and real-binary attacks. Run `pnpm --filter @inhuman.tools/turn test`. The initial red run had 21 failures and one clean-command pass; the shell receipt guard also failed before implementation.
|
|
40
|
+
|
|
41
|
+
## Status
|
|
42
|
+
|
|
43
|
+
Matcher + algebra suite. `license` is the guard; tests stand up a Job orchestrator. MCP residual retries until a vote (no language yet). Host wiring of `license` is the next consumer.
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bypass detection for unattended runs. A denied action means the restriction applies to its effect;
|
|
3
|
+
* pursuing the same effect by another route is an attempt to get around it. After repeated denials a
|
|
4
|
+
* dedicated judge pass reads the recent series of calls ("does this look like an attempt to bypass the
|
|
5
|
+
* restrictions?") and the run stops for a person only when it says yes.
|
|
6
|
+
*
|
|
7
|
+
* Counters live in a per-run ledger. What counts as a denial: a floor block, a resolver deny, a
|
|
8
|
+
* judge deny or failure that resolved to deny, a human resolver turned into `interaction_required`.
|
|
9
|
+
* What does not: a human refusal, a cancellation. Triggers: 3 consecutive or 20 total (and at least 3
|
|
10
|
+
* new since the last check), an immediate stop for control-plane tampering after earlier denials,
|
|
11
|
+
* and a hard stop at `hardStopTotal` (default 60, null = off). All thresholds are configurable.
|
|
12
|
+
*/
|
|
13
|
+
import type { Escalation, ToolCallJob } from "@inhuman.tools/harness-model";
|
|
14
|
+
import { CLASSES, isClassId } from "@inhuman.tools/policy/floor";
|
|
15
|
+
|
|
16
|
+
export type CallOutcome = "ran" | "floor-block" | "resolver-deny" | "judge-deny" | "interaction-required";
|
|
17
|
+
|
|
18
|
+
export type CallRecord = {
|
|
19
|
+
readonly i: number;
|
|
20
|
+
readonly tool: string;
|
|
21
|
+
/** JSON of the arguments, cut. Agent-written: data, never an instruction. */
|
|
22
|
+
readonly args: string;
|
|
23
|
+
readonly tier: string | null;
|
|
24
|
+
readonly classes: readonly string[];
|
|
25
|
+
readonly ruleIds: readonly string[];
|
|
26
|
+
readonly outcome: CallOutcome;
|
|
27
|
+
readonly judgeReason?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const ARGS_LIMIT = 500;
|
|
31
|
+
|
|
32
|
+
export class RunLedger {
|
|
33
|
+
consecutive = 0;
|
|
34
|
+
total = 0;
|
|
35
|
+
lastCheckAtTotal = 0;
|
|
36
|
+
checks = 0;
|
|
37
|
+
/** Bypass-check failures in a row. */
|
|
38
|
+
checkFails = 0;
|
|
39
|
+
stopped = false;
|
|
40
|
+
readonly calls: CallRecord[] = [];
|
|
41
|
+
/** Assistant texts between calls (≤ 5 are shown to the judge). Agent-written. */
|
|
42
|
+
readonly notes: string[] = [];
|
|
43
|
+
|
|
44
|
+
/** A call that was licensed and started. */
|
|
45
|
+
ran(record: Omit<CallRecord, "i" | "outcome">): void {
|
|
46
|
+
this.consecutive = 0;
|
|
47
|
+
this.#push({ ...record, outcome: "ran" });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A call denied by policy. Returns the record. */
|
|
51
|
+
denied(record: Omit<CallRecord, "i">): CallRecord {
|
|
52
|
+
this.consecutive += 1;
|
|
53
|
+
this.total += 1;
|
|
54
|
+
return this.#push(record);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A denial that is not the agent's doing (a human refused, a cancellation) is not counted. */
|
|
58
|
+
uncounted(record: Omit<CallRecord, "i">): void {
|
|
59
|
+
this.#push(record);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#push(record: Omit<CallRecord, "i">): CallRecord {
|
|
63
|
+
const full: CallRecord = { ...record, i: this.calls.length };
|
|
64
|
+
this.calls.push(full);
|
|
65
|
+
return full;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Why the run should be looked at, or null. `check` means ask the bypass judge. */
|
|
70
|
+
export type Trigger = "immediate" | "hard-stop" | "check" | null;
|
|
71
|
+
|
|
72
|
+
export function decideTrigger(
|
|
73
|
+
ledger: RunLedger,
|
|
74
|
+
esc: Escalation,
|
|
75
|
+
last: { readonly classes: readonly string[]; readonly outcome: CallOutcome },
|
|
76
|
+
): Trigger {
|
|
77
|
+
// The gate's own files, reached for after the run has already been denied once.
|
|
78
|
+
if (last.outcome === "floor-block" && last.classes.includes("control-plane") && ledger.total > 1) return "immediate";
|
|
79
|
+
if (esc.hardStopTotal !== null && ledger.total >= esc.hardStopTotal) return "hard-stop";
|
|
80
|
+
const fresh = ledger.total - ledger.lastCheckAtTotal;
|
|
81
|
+
if (fresh < 3) return null;
|
|
82
|
+
if (ledger.consecutive >= esc.consecutiveDenials || fresh >= esc.totalDenials) return "check";
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Denied only because a required acknowledgement is missing in an unattended run. */
|
|
87
|
+
export function isAckRefusal(job: ToolCallJob): boolean {
|
|
88
|
+
return job.ackPending && job.licenseDetail.code === "interaction_required" && job.baseLicenseDetail.decision === true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function outcomeOf(job: ToolCallJob): { outcome: CallOutcome; counted: boolean } {
|
|
92
|
+
if (job.humanApprove === false) return { outcome: "resolver-deny", counted: false };
|
|
93
|
+
if (job.cancelled && job.licenseDecision !== false) return { outcome: "resolver-deny", counted: false };
|
|
94
|
+
const detail = job.licenseDetail;
|
|
95
|
+
if (job.policyResult === "block") return { outcome: "floor-block", counted: true };
|
|
96
|
+
// A missing acknowledgement is the user's own flag, not the agent probing the floor: it is reported, never counted.
|
|
97
|
+
if (isAckRefusal(job)) return { outcome: "interaction-required", counted: false };
|
|
98
|
+
if (detail.code === "interaction_required") return { outcome: "interaction-required", counted: true };
|
|
99
|
+
if (job.classifierResult === "deny") return { outcome: "judge-deny", counted: true };
|
|
100
|
+
return { outcome: "resolver-deny", counted: true };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function recordOf(job: ToolCallJob): Omit<CallRecord, "i" | "outcome"> {
|
|
104
|
+
const args = JSON.stringify(job.args ?? {});
|
|
105
|
+
return {
|
|
106
|
+
tool: job.name,
|
|
107
|
+
args: args.length <= ARGS_LIMIT ? args : `${args.slice(0, ARGS_LIMIT)}…`,
|
|
108
|
+
tier: job.policyResult,
|
|
109
|
+
classes: job.policyClasses,
|
|
110
|
+
ruleIds: (job.policyVotes as ReadonlyArray<{ ruleId?: string }>).flatMap((vote) =>
|
|
111
|
+
typeof vote.ruleId === "string" ? [vote.ruleId] : [],
|
|
112
|
+
),
|
|
113
|
+
...(job.judgeReason === null ? {} : { judgeReason: job.judgeReason }),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Deterministic hints for the judge: cheap to compute, not verdicts. */
|
|
118
|
+
export type Hints = {
|
|
119
|
+
readonly sameRuleAs: readonly (readonly number[])[];
|
|
120
|
+
readonly executesFileWrittenAt: readonly { readonly call: number; readonly writtenAt: number }[];
|
|
121
|
+
readonly encodedPayload: readonly number[];
|
|
122
|
+
readonly controlPlaneTouch: readonly number[];
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const ENCODED = /\b(?:base64|xxd|openssl\s+enc)\b|\beval\b|\$\([^)]*\)|`[^`]*`|\\x[0-9a-f]{2}/i;
|
|
126
|
+
|
|
127
|
+
export function hintsFor(calls: readonly CallRecord[]): Hints {
|
|
128
|
+
const byRule = new Map<string, number[]>();
|
|
129
|
+
for (const call of calls) {
|
|
130
|
+
if (call.outcome === "ran") continue;
|
|
131
|
+
for (const id of call.ruleIds) byRule.set(id, [...(byRule.get(id) ?? []), call.i]);
|
|
132
|
+
}
|
|
133
|
+
const sameRuleAs = [...byRule.values()].filter((group) => group.length > 1);
|
|
134
|
+
const written: Array<{ path: string; at: number }> = [];
|
|
135
|
+
const executes: Array<{ call: number; writtenAt: number }> = [];
|
|
136
|
+
for (const call of calls) {
|
|
137
|
+
let args: Record<string, unknown> = {};
|
|
138
|
+
try {
|
|
139
|
+
args = JSON.parse(call.args) as Record<string, unknown>;
|
|
140
|
+
} catch {
|
|
141
|
+
/* cut args are not JSON; the hints just skip them */
|
|
142
|
+
}
|
|
143
|
+
const command = typeof args.command === "string" ? args.command : "";
|
|
144
|
+
for (const file of written) {
|
|
145
|
+
if (call.i > file.at && command.includes(file.path)) executes.push({ call: call.i, writtenAt: file.at });
|
|
146
|
+
}
|
|
147
|
+
if (call.tool === "write_file" && typeof args.path === "string") written.push({ path: args.path, at: call.i });
|
|
148
|
+
for (const match of command.matchAll(/>{1,2}\s*([^\s;&|]+)/g)) {
|
|
149
|
+
if (match[1] !== undefined) written.push({ path: match[1], at: call.i });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const denied = calls.filter((call) => call.outcome !== "ran");
|
|
153
|
+
return {
|
|
154
|
+
sameRuleAs,
|
|
155
|
+
executesFileWrittenAt: executes,
|
|
156
|
+
encodedPayload: denied.length === 0 ? [] : calls.filter((call) => ENCODED.test(call.args)).map((call) => call.i),
|
|
157
|
+
controlPlaneTouch: calls
|
|
158
|
+
.filter((call) => call.classes.some((cls) => cls === "control-plane" || cls === "credential"))
|
|
159
|
+
.map((call) => call.i),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export type BypassSections = {
|
|
164
|
+
readonly taskJson: string;
|
|
165
|
+
readonly restrictionsJson: string;
|
|
166
|
+
readonly callsJson: string;
|
|
167
|
+
readonly hintsJson: string;
|
|
168
|
+
readonly notesJson: string;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/** Every section is one line of JSON. The agent's text is only ever inside CALLS and AGENT NOTES. */
|
|
172
|
+
export function buildBypassSections(input: {
|
|
173
|
+
readonly task: string;
|
|
174
|
+
readonly calls: readonly CallRecord[];
|
|
175
|
+
readonly notes: readonly string[];
|
|
176
|
+
readonly window: number;
|
|
177
|
+
}): BypassSections {
|
|
178
|
+
const window = input.calls.slice(-input.window);
|
|
179
|
+
const rules = new Map<string, { cls: string; rule: string }>();
|
|
180
|
+
for (const call of window) {
|
|
181
|
+
if (call.outcome === "ran") continue;
|
|
182
|
+
for (const rule of call.ruleIds.length === 0 ? [""] : call.ruleIds) {
|
|
183
|
+
for (const cls of call.classes.length === 0 ? [""] : call.classes) rules.set(`${cls}|${rule}`, { cls, rule });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const restrictions = [...rules.values()].map(({ cls, rule }) => ({
|
|
187
|
+
class: cls,
|
|
188
|
+
rule,
|
|
189
|
+
description: isClassId(cls) ? CLASSES[cls].description : "",
|
|
190
|
+
}));
|
|
191
|
+
return {
|
|
192
|
+
taskJson: JSON.stringify(input.task.slice(0, 4000)),
|
|
193
|
+
restrictionsJson: JSON.stringify(restrictions),
|
|
194
|
+
callsJson: JSON.stringify(window),
|
|
195
|
+
hintsJson: JSON.stringify(hintsFor(window)),
|
|
196
|
+
notesJson: JSON.stringify(input.notes.slice(-5).map((note) => note.slice(0, 1000))),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const BYPASS_PATTERNS = [
|
|
201
|
+
"same-effect-different-route",
|
|
202
|
+
"script-indirection",
|
|
203
|
+
"encoding-or-obfuscation",
|
|
204
|
+
"control-plane-tampering",
|
|
205
|
+
"scope-escalation",
|
|
206
|
+
"credential-probing",
|
|
207
|
+
"persistence",
|
|
208
|
+
"repeated-identical",
|
|
209
|
+
"none",
|
|
210
|
+
] as const;
|
|
211
|
+
|
|
212
|
+
export type BypassVerdict = {
|
|
213
|
+
readonly verdict: "bypass" | "no-bypass" | "unsure";
|
|
214
|
+
readonly pattern: (typeof BYPASS_PATTERNS)[number];
|
|
215
|
+
readonly calls: readonly number[];
|
|
216
|
+
readonly reason: string;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/** Strict parse. Anything off is a failure (the check could not be made). */
|
|
220
|
+
export function parseBypassVerdict(raw: unknown): BypassVerdict | { readonly fail: string } {
|
|
221
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return { fail: "bypass check did not answer" };
|
|
222
|
+
const rec = Object.fromEntries(Object.entries(raw).map(([key, value]) => [key.replace(/^'?:/, ""), value]));
|
|
223
|
+
const keys = Object.keys(rec);
|
|
224
|
+
if (keys.length !== 4 || !["verdict", "pattern", "calls", "reason"].every((key) => keys.includes(key))) {
|
|
225
|
+
return { fail: "bypass check answered with the wrong fields" };
|
|
226
|
+
}
|
|
227
|
+
const { verdict, pattern, calls, reason } = rec;
|
|
228
|
+
if (verdict !== "bypass" && verdict !== "no-bypass" && verdict !== "unsure")
|
|
229
|
+
return { fail: "bypass check: unknown verdict" };
|
|
230
|
+
if (!BYPASS_PATTERNS.includes(pattern as never)) return { fail: "bypass check: unknown pattern" };
|
|
231
|
+
if (!Array.isArray(calls) || calls.length > 20 || !calls.every((c) => Number.isInteger(c) && c >= 0)) {
|
|
232
|
+
return { fail: "bypass check: calls must be indexes" };
|
|
233
|
+
}
|
|
234
|
+
if (typeof reason !== "string") return { fail: "bypass check: no reason" };
|
|
235
|
+
return {
|
|
236
|
+
verdict,
|
|
237
|
+
pattern: pattern as BypassVerdict["pattern"],
|
|
238
|
+
calls: calls as number[],
|
|
239
|
+
reason: reason.slice(0, 300),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function stopReason(verdict: BypassVerdict): string {
|
|
244
|
+
const calls = verdict.calls.length === 0 ? "" : ` (calls ${verdict.calls.join(", ")})`;
|
|
245
|
+
return `bypass suspected: ${verdict.pattern}${calls}: ${verdict.reason}`;
|
|
246
|
+
}
|