@sema-agent/core 7.49.4 → 7.50.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 +152 -0
- package/dist/agents/observer.js +3 -10
- package/dist/agents/peer-directory.d.ts +8 -3
- package/dist/agents/peer-directory.js +5 -3
- package/dist/agents/subagent-steps.d.ts +6 -14
- package/dist/agents/subagent-steps.js +3 -17
- package/dist/brain/stream-shared.d.ts +1 -1
- package/dist/core/allow-entry.d.ts +1 -1
- package/dist/core/allow-entry.js +1 -1
- package/dist/core/ask-origin.d.ts +15 -11
- package/dist/core/ask-origin.js +6 -14
- package/dist/core/auto-mode-prompt.js +6 -2
- package/dist/core/checkpoint-store.d.ts +6 -4
- package/dist/core/checkpoint-store.js +1 -1
- package/dist/core/context-edit.d.ts +2 -2
- package/dist/core/decision-receipt.d.ts +2 -2
- package/dist/core/decision-receipt.js +30 -11
- package/dist/core/delegation-frames.d.ts +66 -18
- package/dist/core/delegation-frames.js +22 -0
- package/dist/core/engine-notice.d.ts +7 -5
- package/dist/core/frame-settlement.d.ts +6 -1
- package/dist/core/gate-fold.js +19 -13
- package/dist/core/gate-lanes.js +12 -11
- package/dist/core/gate-outcome.d.ts +2 -2
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +28 -20
- package/dist/core/human-input-projection.js +2 -0
- package/dist/core/lsp-diagnostics.d.ts +3 -0
- package/dist/core/lsp-diagnostics.js +6 -0
- package/dist/core/lsp-session.d.ts +39 -17
- package/dist/core/lsp-session.js +118 -58
- package/dist/core/lsp.d.ts +83 -3
- package/dist/core/lsp.js +146 -16
- package/dist/core/mcp.js +2 -1
- package/dist/core/media-byte-cap.d.ts +14 -0
- package/dist/core/media-byte-cap.js +15 -3
- package/dist/core/memory-engine/consolidation-driver.d.ts +23 -1
- package/dist/core/memory-engine/consolidation-driver.js +54 -6
- package/dist/core/memory-engine/consolidation-write-gate.d.ts +46 -0
- package/dist/core/memory-engine/consolidation-write-gate.js +82 -0
- package/dist/core/memory-engine/consolidation.d.ts +17 -0
- package/dist/core/memory-engine/consolidation.js +1 -0
- package/dist/core/memory-engine/distiller.d.ts +8 -2
- package/dist/core/memory-engine/distiller.js +8 -6
- package/dist/core/memory-engine/engine.d.ts +37 -4
- package/dist/core/memory-engine/engine.js +127 -38
- package/dist/core/memory-engine/file-backend.d.ts +8 -3
- package/dist/core/memory-engine/file-backend.js +44 -16
- package/dist/core/memory-engine/index.d.ts +2 -2
- package/dist/core/memory-engine/index.js +1 -1
- package/dist/core/memory-engine/landing-admission.d.ts +65 -0
- package/dist/core/memory-engine/landing-admission.js +68 -0
- package/dist/core/memory-engine/types.d.ts +23 -0
- package/dist/core/mode-policy.d.ts +3 -2
- package/dist/core/mode-policy.js +2 -0
- package/dist/core/permission-rule-model.d.ts +19 -13
- package/dist/core/permission-rule-model.js +7 -4
- package/dist/core/permission-rule-provider.d.ts +11 -0
- package/dist/core/permission-rules.d.ts +1 -1
- package/dist/core/permission-rules.js +5 -2
- package/dist/core/persisted-rule-arms.d.ts +15 -7
- package/dist/core/persisted-rule-arms.js +13 -6
- package/dist/core/posix-mode-face.d.ts +10 -0
- package/dist/core/posix-mode-face.js +3 -0
- package/dist/core/reminder-mint.d.ts +5 -0
- package/dist/core/reminder-mint.js +1 -0
- package/dist/core/retired-keys.d.ts +8 -0
- package/dist/core/retired-keys.js +4 -0
- package/dist/core/rule-lane-reads.d.ts +4 -4
- package/dist/core/rule-lane-reads.js +10 -1
- package/dist/core/runner/construction-doors.d.ts +10 -4
- package/dist/core/runner/construction-doors.js +25 -2
- package/dist/core/runner/contracts.d.ts +20 -3
- package/dist/core/runner/env-capability-census.js +1 -1
- package/dist/core/runner/gate-exit.js +2 -1
- package/dist/core/runner/memory-consolidation-driver.d.ts +6 -4
- package/dist/core/runner/memory-consolidation-driver.js +5 -2
- package/dist/core/runner/memory-consolidation-write-judge.d.ts +17 -0
- package/dist/core/runner/memory-consolidation-write-judge.js +14 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +16 -22
- package/dist/core/runner/permission-rule-lanes.js +13 -43
- package/dist/core/runner/person-path-rows.d.ts +244 -0
- package/dist/core/runner/person-path-rows.js +387 -0
- package/dist/core/runner/prepare-acquire-reconcile.js +8 -1
- package/dist/core/runner/prepare-artifact.d.ts +5 -3
- package/dist/core/runner/prepare-artifact.js +2 -8
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
- package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
- package/dist/core/runner/prepare-context-lane.js +2 -2
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -3
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +19 -10
- package/dist/core/runner/prepare-hands-readface.js +57 -43
- package/dist/core/runner/prepare-lsp.d.ts +15 -13
- package/dist/core/runner/prepare-lsp.js +11 -11
- package/dist/core/runner/prepare-policy-chain.d.ts +8 -4
- package/dist/core/runner/prepare-policy-chain.js +6 -7
- package/dist/core/runner/prepare-run-refs.js +1 -1
- package/dist/core/runner/prepare-task.js +24 -16
- package/dist/core/runner/prepare-turn-wiring.d.ts +13 -6
- package/dist/core/runner/prepare-turn-wiring.js +19 -8
- package/dist/core/runner/read-root-grant.d.ts +1 -25
- package/dist/core/runner/read-root-grant.js +1 -17
- package/dist/core/runner/retired-runner-deps.d.ts +2 -2
- package/dist/core/runner/run-brain-sinks.d.ts +1 -5
- package/dist/core/runner/run-brain-sinks.js +2 -4
- package/dist/core/runner/run-harness-handlers.js +5 -4
- package/dist/core/runner/run-identity-wiring.d.ts +4 -4
- package/dist/core/runner/run-identity-wiring.js +10 -1
- package/dist/core/runner/run-leg.d.ts +1 -1
- package/dist/core/runner/run-leg.js +22 -9
- package/dist/core/runner/run-settle-and-teardown.d.ts +0 -4
- package/dist/core/runner/run-settle-and-teardown.js +7 -4
- package/dist/core/runner/run-terminal-adoption.d.ts +1 -1
- package/dist/core/runner/run-terminal-adoption.js +1 -1
- package/dist/core/runner/run-turn-boundary.js +2 -2
- package/dist/core/runner/runtask.js +8 -8
- package/dist/core/runner/session-file-state-replay.d.ts +14 -4
- package/dist/core/runner/session-file-state-replay.js +8 -0
- package/dist/core/runner/stream-steer-verb.js +3 -0
- package/dist/core/runner/tool-end-body.d.ts +5 -4
- package/dist/core/runner/tool-end-body.js +4 -4
- package/dist/core/runner/tool-output-projection.d.ts +6 -4
- package/dist/core/runner/tool-output-projection.js +66 -47
- package/dist/core/runner-deps.d.ts +6 -3
- package/dist/core/shared-memory/types.d.ts +2 -2
- package/dist/core/shell-read-silence.d.ts +23 -0
- package/dist/core/shell-read-silence.js +18 -0
- package/dist/core/synthetic-read-verdict.d.ts +80 -21
- package/dist/core/synthetic-read-verdict.js +89 -18
- package/dist/core/task-event.d.ts +113 -25
- package/dist/core/task-spec.d.ts +44 -0
- package/dist/core/task-tool-shape.d.ts +1 -1
- package/dist/core/tool-catalog-entries.js +16 -2
- package/dist/core/tool-policy.d.ts +25 -15
- package/dist/core/tool-policy.js +9 -13
- package/dist/core/trace.d.ts +3 -2
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-text.d.ts +2 -2
- package/dist/core/user-attachment.d.ts +164 -0
- package/dist/core/user-attachment.js +437 -0
- package/dist/engine/compaction/compaction.js +10 -3
- package/dist/engine/compaction/utils.js +6 -0
- package/dist/engine/execution-env/node-execution-env.d.ts +19 -1
- package/dist/engine/execution-env/node-execution-env.js +127 -1
- package/dist/engine/harness/agent-harness.d.ts +12 -2
- package/dist/engine/harness/agent-harness.js +15 -3
- package/dist/engine/harness/types.d.ts +21 -0
- package/dist/engine/llm/content-block-render.d.ts +15 -0
- package/dist/engine/llm/content-block-render.js +10 -0
- package/dist/engine/llm/index.d.ts +1 -0
- package/dist/engine/llm/index.js +1 -0
- package/dist/engine/llm/types.d.ts +80 -4
- package/dist/engine/llm/types.js +43 -1
- package/dist/engine/session/import-validate.js +2 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +2 -1
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/internal/llm.d.ts +2 -2
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/workflow.d.ts +4 -5
- package/dist/orchestration/workflow.js +2 -2
- package/dist/prompts/default.d.ts +1 -1
- package/dist/stores/file/permission-rule-store.d.ts +13 -1
- package/dist/stores/file/permission-rule-store.js +40 -21
- package/dist/tools/artifact/artifact-tool.js +1 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
- package/dist/tools/fs/dir-entries.d.ts +4 -10
- package/dist/tools/fs/dir-entries.js +4 -11
- package/dist/tools/fs/file-read-check.js +3 -3
- package/dist/tools/fs/fs-pdf.js +2 -4
- package/dist/tools/fs/fs-read.d.ts +47 -4
- package/dist/tools/fs/fs-read.js +63 -19
- package/dist/tools/fs/fs-search-tools.d.ts +3 -3
- package/dist/tools/fs/fs-search-tools.js +7 -2
- package/dist/tools/fs/fs-write.d.ts +1 -1
- package/dist/tools/fs/fs-write.js +7 -6
- package/dist/tools/fs/index.d.ts +7 -1
- package/dist/tools/fs/index.js +56 -34
- package/dist/tools/fs/read-deny-home.d.ts +19 -1
- package/dist/tools/fs/read-deny-home.js +32 -22
- package/dist/tools/fs/read-deny.d.ts +14 -0
- package/dist/tools/fs/read-deny.js +8 -0
- package/dist/tools/fs/repo-map.d.ts +3 -2
- package/dist/tools/fs/repo-map.js +11 -3
- package/dist/tools/fs/safety.d.ts +41 -2
- package/dist/tools/fs/safety.js +12 -3
- package/dist/tools/fs/shell-read-check.d.ts +12 -3
- package/dist/tools/fs/shell-read-check.js +28 -15
- package/dist/tools/fs/shell-read-device.d.ts +1 -1
- package/dist/tools/fs/shell-read-judge.d.ts +9 -4
- package/dist/tools/fs/shell-read-judge.js +1 -1
- package/dist/tools/loop-tick.d.ts +2 -2
- package/package.json +3 -1
- package/test/export-surface.snapshot.json +39 -1
- package/wire-changes/core@7.49.5.json +104 -0
- package/wire-changes/core@7.50.0.json +1699 -0
- package/dist/core/runner/persisted-path-rows.d.ts +0 -88
- package/dist/core/runner/persisted-path-rows.js +0 -108
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { attest } from "./tool-policy.js";
|
|
2
|
+
import { callPathFaceOf } from "./permission-rule-model.js";
|
|
1
3
|
import { decisionAskClassesOf } from "./ask-origin.js";
|
|
2
4
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
3
5
|
export function strongestAnswer(answers) {
|
|
@@ -9,12 +11,15 @@ export function disclosedRuleSet(rules) {
|
|
|
9
11
|
const rest = rules.length - shown.length;
|
|
10
12
|
return rest > 0 ? `${shown.join(", ")} and ${rest} more` : shown.join(", ");
|
|
11
13
|
}
|
|
12
|
-
export function applyPersistedTightening(decision, read) {
|
|
13
|
-
if (read?.unreadable === true
|
|
14
|
+
export function applyPersistedTightening(decision, read, call) {
|
|
15
|
+
if (read?.unreadable === true) {
|
|
14
16
|
if (decision.action === "deny")
|
|
15
17
|
return { decision };
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
+
const callForm = read.reason !== undefined;
|
|
19
|
+
const message = callForm
|
|
20
|
+
? `this call could not be read against the person's standing deny/ask rules (${read.reason}) — whether one of them forbids it is unknown, so it needs a real approval`
|
|
21
|
+
: "the person's standing permission rules could not be read — whether one of them forbids this call is unknown, so it needs a real approval";
|
|
22
|
+
const unreadableCall = callForm ? { ruleUnreadable: "persisted_call" } : {};
|
|
18
23
|
const standingFence = decision.action === "ask" && decisionAskClassesOf({ ...decision, foldedAskClasses: undefined }).has("policy_rra")
|
|
19
24
|
? { foldedAskClasses: [...(decision.foldedAskClasses ?? []), "policy_rra"] }
|
|
20
25
|
: {};
|
|
@@ -22,7 +27,7 @@ export function applyPersistedTightening(decision, read) {
|
|
|
22
27
|
decision: decision.action === "allow"
|
|
23
28
|
? { action: "ask", message, decisionReason: "persisted_rule", requiresRealApproval: true, ...unreadableCall, ...(decision.updatedInput !== undefined ? { updatedInput: decision.updatedInput } : {}) }
|
|
24
29
|
: { ...decision, requiresRealApproval: true, ...unreadableCall, ...standingFence, message: `${decision.message !== undefined ? `${decision.message} ` : ""}(${message})` },
|
|
25
|
-
tightened: "unreadable",
|
|
30
|
+
tightened: callForm ? "unreadable" : "store_unreadable",
|
|
26
31
|
};
|
|
27
32
|
}
|
|
28
33
|
const hit = read?.hit;
|
|
@@ -30,7 +35,9 @@ export function applyPersistedTightening(decision, read) {
|
|
|
30
35
|
return { decision };
|
|
31
36
|
const shown = disclosedRuleSet(hit.rules);
|
|
32
37
|
if (hit.behavior === "deny") {
|
|
33
|
-
|
|
38
|
+
const denied = { action: "deny", message: `a persisted deny rule (${shown}) refuses this call`, decisionReason: "persisted_rule" };
|
|
39
|
+
const attested = hit.denial !== undefined && call !== undefined && hit.denial.face === callPathFaceOf(call);
|
|
40
|
+
return { decision: attested ? attest(denied, call, { pathDenial: hit.denial }) : denied, tightened: "deny" };
|
|
34
41
|
}
|
|
35
42
|
if (decision.action === "deny")
|
|
36
43
|
return { decision };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Does this platform report POSIX permission bits worth judging? Windows' `Stats.mode` is a projection of
|
|
3
|
+
* the read-only attribute (`0o666` / `0o444`; a directory created with mode `0o700` reads `0o666` too), not
|
|
4
|
+
* permission bits — so a group/world-writable test over it fires on every file and says nothing about who may
|
|
5
|
+
* write. Every reader of `mode & 0o022` (and the file-mode report of the execution env) asks THIS predicate,
|
|
6
|
+
* so a platform never judges a bit it cannot have.
|
|
7
|
+
*
|
|
8
|
+
* `platform` is a parameter so a caller can ask about the platform it was told, not only the one it runs on.
|
|
9
|
+
*/
|
|
10
|
+
export declare function posixModeBitsMeaningful(platform?: NodeJS.Platform): boolean;
|
|
@@ -60,6 +60,11 @@ export declare function openSystemReminder(mark: string | undefined): string;
|
|
|
60
60
|
* used by the user-lane wrap homes. The BODY is byte-untouched by the mark (design/319 first
|
|
61
61
|
* invariant: zero data-byte change — the mark rides only the engine's own tag). */
|
|
62
62
|
export declare function mintSystemReminder(body: string, mark: string | undefined): string;
|
|
63
|
+
/** The one boundary rule for an engine-minted marked block that follows data in the same message or tool result (a
|
|
64
|
+
* read's disclosure trailer, an MCP result's, a user-attachment frame's closing block): it opens on its own line after
|
|
65
|
+
* a blank line, whatever the data ends with. The completions lanes join a message's text blocks with no separator, so
|
|
66
|
+
* the block's own bytes carry the boundary. */
|
|
67
|
+
export declare const MARKED_BLOCK_AFTER_DATA = "\n\n";
|
|
63
68
|
/** Wrap model-facing hook/gate feedback in a `<system-reminder>` so it reads as guidance, not data.
|
|
64
69
|
* NOTE (council design/74 #6): this does NOT escape a literal `</system-reminder>` in `text` — callers MUST
|
|
65
70
|
* pass trusted, first-party strings (every current caller does: fixed gate/limit messages). If a future
|
|
@@ -13,6 +13,7 @@ export function openSystemReminder(mark) {
|
|
|
13
13
|
export function mintSystemReminder(body, mark) {
|
|
14
14
|
return `${openSystemReminder(mark)}\n${body}\n</system-reminder>`;
|
|
15
15
|
}
|
|
16
|
+
export const MARKED_BLOCK_AFTER_DATA = "\n\n";
|
|
16
17
|
export function formatHookFeedback(text, mark) {
|
|
17
18
|
return mintSystemReminder(text, mark);
|
|
18
19
|
}
|
|
@@ -34,6 +34,14 @@ export declare const RETIRED_TOOL_POLICY_KEYS: Readonly<Record<string, string>>;
|
|
|
34
34
|
* refused by name (`config.deps_retired_key`); the station reads one write seat, so a retired one would leave every write
|
|
35
35
|
* target unjudged by its rows. (The engine's own station builds its boundaries itself.) */
|
|
36
36
|
export declare const RETIRED_SHELL_READ_BOUNDARY_KEYS: Readonly<Record<string, string>>;
|
|
37
|
+
/** The RETIRED members of a permission DECISION (`PermissionResult`, a PreToolUse hook's result), each with the sentence that
|
|
38
|
+
* names its replacement — the table the decision receipt reads ({@link import("./tool-policy.js").refuseOutOfContractDecision}).
|
|
39
|
+
* That door does not use {@link retiredKeyOf}: a decision is caller-supplied data the receipt reads by descriptor without
|
|
40
|
+
* running a getter, so a name is retired when the decision carries it by the receipt's presence law (its own descriptor, or a
|
|
41
|
+
* non-absent one on its prototype chain; a data value of `undefined` is absence). A
|
|
42
|
+
* decision that carries one is denied in the engine's own words, never read without it: the type retires a member only where
|
|
43
|
+
* a decision is a literal in an annotated position, and a `check` whose return type is inferred still compiles with it. */
|
|
44
|
+
export declare const RETIRED_DECISION_MEMBERS: Readonly<Record<string, string>>;
|
|
37
45
|
/** The `ToolPolicy` door ({@link RETIRED_TOOL_POLICY_KEYS}); `undefined` is no policy. */
|
|
38
46
|
export declare function assertNoRetiredToolPolicyKeys(policy: object | undefined): void;
|
|
39
47
|
/** The `ShellReadBoundary` door ({@link RETIRED_SHELL_READ_BOUNDARY_KEYS}). */
|
|
@@ -37,6 +37,10 @@ export const RETIRED_SHELL_READ_BOUNDARY_KEYS = Object.freeze({
|
|
|
37
37
|
writeProtectionJudge: 'the station asks ONE write judgment, `ShellReadBoundary.writeTargetJudge`, over the write-protection table and the deployment\'s guarded write rows together — it answers `"protected"`, `"guarded"` or `undefined`, not a boolean',
|
|
38
38
|
writeTargetGuard: 'the deployment\'s guarded write rows join the same one judgment, `ShellReadBoundary.writeTargetJudge` (`"guarded"` for a target a `ToolPolicy.writeGuardRows` row covers) — the boundary has no second write seat',
|
|
39
39
|
});
|
|
40
|
+
export const RETIRED_DECISION_MEMBERS = Object.freeze({
|
|
41
|
+
reason: 'rename it to "message" (the one text field a decision carries)',
|
|
42
|
+
guard: 'write `guards: ["<word>"]` instead (the same five words, as a list: one ask may stand on several integrity guards)',
|
|
43
|
+
});
|
|
40
44
|
export function assertNoRetiredToolPolicyKeys(policy) {
|
|
41
45
|
if (policy !== undefined)
|
|
42
46
|
assertNoRetiredKeys(policy, RETIRED_TOOL_POLICY_KEYS, "ToolPolicy");
|
|
@@ -69,10 +69,10 @@ export declare function readOrgRuleLane(org: {
|
|
|
69
69
|
/**
|
|
70
70
|
* The persisted-rule lane's one read for one call — every consumer's (the live gate, the synthetic-Read
|
|
71
71
|
* judge), normalized once. A lane that throws, hangs past the dependency timeout, or answers something
|
|
72
|
-
* unreadable is a lane that could not answer:
|
|
73
|
-
*
|
|
74
|
-
* the RECORD it is `"unavailable"`. A call the lane could not read against the rows (`reason`) fails
|
|
75
|
-
* in the arms. Absent lane ⇒ the clean negative.
|
|
72
|
+
* unreadable is a lane that could not answer: a row nobody could read may forbid the call, so for the DECISION it
|
|
73
|
+
* fails closed (`applyPersistedTightening` reads `{ unreadable }` without a reason as the store form: a real-approval
|
|
74
|
+
* ask), and for the RECORD it is `"unavailable"`. A call the lane could not read against the rows (`reason`) fails
|
|
75
|
+
* closed in the arms the same way, as the call form. Absent lane ⇒ the clean negative.
|
|
76
76
|
*/
|
|
77
77
|
export declare function readPersistedRuleLane(lane: {
|
|
78
78
|
adjudicate: (req: ToolCallRequest) => PersistedRuleAnswer | Promise<PersistedRuleAnswer>;
|
|
@@ -68,6 +68,14 @@ export function normalizeOrgGateVerdict(answer, unreadable) {
|
|
|
68
68
|
const unreadableCell = behavior === "ask" && typeof reason === "string" ? { unreadable: reason } : {};
|
|
69
69
|
return clean({ status: "available", verdict: clean({ behavior: behavior, rule, ...unreadableCell }), ...revisionCell });
|
|
70
70
|
}
|
|
71
|
+
function copiedDenial(raw, ruleTexts) {
|
|
72
|
+
if (!isPlainOwnRecord(raw))
|
|
73
|
+
return undefined;
|
|
74
|
+
const rule = ownDataValue(raw, "rule");
|
|
75
|
+
const view = ownDataValue(raw, "view");
|
|
76
|
+
const face = ownDataValue(raw, "face");
|
|
77
|
+
return typeof rule === "string" && typeof view === "string" && view !== "" && ruleTexts.includes(rule) && (face === "read" || face === "write") ? { rule, view, face } : undefined;
|
|
78
|
+
}
|
|
71
79
|
function copiedUsableDots(dots) {
|
|
72
80
|
let copied;
|
|
73
81
|
try {
|
|
@@ -133,7 +141,8 @@ export function normalizePersistedRuleHit(hit) {
|
|
|
133
141
|
const behavior = ownDataValue(hit, "behavior");
|
|
134
142
|
if (!isRuleBehavior(behavior))
|
|
135
143
|
return {};
|
|
136
|
-
|
|
144
|
+
const denial = behavior === "deny" ? copiedDenial(ownDataValue(hit, "denial"), members.map((m) => m.rule)) : undefined;
|
|
145
|
+
return { hit: { behavior, rules: members.map((m) => ({ rule: m.rule, ...copiedUsableDots(m.dots) })), ...(denial !== undefined ? { denial } : {}) } };
|
|
137
146
|
}
|
|
138
147
|
export async function readOrgRuleLane(org, req, signal, timeoutMs = ORG_ADJUDICATION_TIMEOUT_MS) {
|
|
139
148
|
let pending;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { RunnerDeps } from "../types.js";
|
|
2
|
-
/** The retired-seat door ({@link assertNoRetiredRunnerDeps})
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
|
|
2
|
+
/** The retired-seat door ({@link assertNoRetiredRunnerDeps}) looks at the caller's own object, where a retired name may sit on
|
|
3
|
+
* the prototype or as a non-enumerable property. Then the deps must be a PLAIN object (prototype `Object.prototype` or none):
|
|
4
|
+
* every seat is read once as an OWN property — enumerable or not, a getter invoked once — into a copy of enumerable data
|
|
5
|
+
* properties, the object the engine keeps and spreads from; the caller's getters are never read again, and a seat a prototype
|
|
6
|
+
* would supply is never read, so an object with another prototype is refused rather than wired without that seat. The seats
|
|
7
|
+
* whose VALUE is screened at construction and never again are judged on the copy: the declared configuration root must be the
|
|
8
|
+
* absolute path of an existing directory — a root that names no directory guards nothing, so it is refused rather than read as
|
|
9
|
+
* absent; the media budget must be a non-negative number of bytes — a bad value is refused rather than read as "no budget" by
|
|
10
|
+
* the request strip and the `tool_end.output` projection. */
|
|
11
|
+
export declare function retainRunnerDeps(deps: RunnerDeps): RunnerDeps;
|
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import { checkedAgentConfigDir } from "../agent-config-dir.js";
|
|
2
|
+
import { checkedMediaByteCap } from "../media-byte-cap.js";
|
|
2
3
|
import { assertNoRetiredRunnerDeps } from "./retired-runner-deps.js";
|
|
3
|
-
export function
|
|
4
|
+
export function retainRunnerDeps(deps) {
|
|
4
5
|
assertNoRetiredRunnerDeps(deps);
|
|
5
|
-
|
|
6
|
+
const refusal = (message) => {
|
|
7
|
+
const e = new Error(message);
|
|
8
|
+
e.code = "config.invalid_argument";
|
|
9
|
+
return e;
|
|
10
|
+
};
|
|
11
|
+
const proto = Object.getPrototypeOf(deps);
|
|
12
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
13
|
+
throw refusal("RunnerDeps must be a plain object (its prototype is neither Object.prototype nor null): every seat is read once as an own property, and a seat its prototype would supply is not read — refused rather than wired without it");
|
|
14
|
+
}
|
|
15
|
+
const retained = Object.create(Object.prototype);
|
|
16
|
+
for (const name of Object.getOwnPropertyNames(deps)) {
|
|
17
|
+
let value;
|
|
18
|
+
try {
|
|
19
|
+
value = Reflect.get(deps, name);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
throw refusal(`RunnerDeps.${name} threw while it was read at new Runner — a seat is read once, here; refused rather than wired without it`);
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(retained, name, { value, enumerable: true, writable: true, configurable: true });
|
|
25
|
+
}
|
|
26
|
+
checkedAgentConfigDir(retained.agentConfigDir, "RunnerDeps.agentConfigDir");
|
|
27
|
+
checkedMediaByteCap(retained.mediaByteCapBytes, "RunnerDeps.mediaByteCapBytes");
|
|
28
|
+
return retained;
|
|
6
29
|
}
|
|
@@ -450,6 +450,8 @@ export interface Prepared {
|
|
|
450
450
|
};
|
|
451
451
|
/** The deployment's caller policy, re-checked on a durable resume when the bytes to execute are not the bytes the gate saw. */
|
|
452
452
|
basePolicyForResumeEdit?: ToolPolicy;
|
|
453
|
+
/** The WRITE face of the run's synthetic judge (the settle tail's background consolidation asks it): an `ask` is an objection. */
|
|
454
|
+
syntheticWriteVerdict: import("../synthetic-read-verdict.js").SyntheticWriteJudge;
|
|
453
455
|
approvedCallScreen?: (call: {
|
|
454
456
|
toolCallId: string;
|
|
455
457
|
toolName: string;
|
|
@@ -538,6 +540,18 @@ export interface Prepared {
|
|
|
538
540
|
* because nothing registers an interest and the receipt sink only records awaited keys.
|
|
539
541
|
*/
|
|
540
542
|
frameSettlement: import("../frame-settlement.js").FrameSettlement;
|
|
543
|
+
/** The attachments the objective declared, as the door screened them (absent: none, or a resume leg — whose door
|
|
544
|
+
* refuses any). */
|
|
545
|
+
userAttachments?: readonly import("../user-attachment.js").ScreenedUserAttachment[];
|
|
546
|
+
/** The pending commits of this leg's attachment frames, settled by the transcript-append receipt sink — the one place
|
|
547
|
+
* an attachment's read effects are applied. Always present. */
|
|
548
|
+
attachmentDelivery: import("../user-attachment.js").AttachmentDelivery;
|
|
549
|
+
/** The leg's one file reader — the Read tool's own (one read state, one set of disclosure windows and counters),
|
|
550
|
+
* handed to the leg's other carriers of a file. Present iff hands are mounted. */
|
|
551
|
+
modelFileReader?: import("../../tools/fs/fs-read.js").ModelFileReader;
|
|
552
|
+
/** The run's synthetic-Read judge over every layer that can refuse a model's own `Read` (the changed-file notice reads
|
|
553
|
+
* it; so does the attachment read, as a face that puts contents in front of the model). */
|
|
554
|
+
syntheticReadVerdict: import("../synthetic-read-verdict.js").SyntheticReadJudge;
|
|
541
555
|
/**
|
|
542
556
|
* #1036 — the session-box drain's BOUNDARY PASS, called by the turn-boundary lane as its tail
|
|
543
557
|
* (in a `finally`, so every early exit of the boundary still settles). Present iff this leg bound
|
|
@@ -1059,6 +1073,8 @@ export interface Prepared {
|
|
|
1059
1073
|
* `NodeLspManager` — so "already delivered" has to be qualified by run). runtask passes it to every
|
|
1060
1074
|
* `drain` and calls `releaseRun` with it at the run's terminal. */
|
|
1061
1075
|
runIdent: string;
|
|
1076
|
+
/** The files of a drain the run's read face withholds (`lspListedWithheld`: every view, fail-closed; kept files absent). */
|
|
1077
|
+
withheldOf: (uris: readonly string[]) => Promise<ReadonlyMap<string, unknown>>;
|
|
1062
1078
|
};
|
|
1063
1079
|
/** The ctx `requestStopAfterTurn` primitive's run-local mirror (one-way, set when a tool asked for a
|
|
1064
1080
|
* clean stop after the current turn). The harness latch itself is private to the loop; the turn
|
|
@@ -2585,7 +2601,9 @@ export interface RunState {
|
|
|
2585
2601
|
* with — minus the conversation bits: `sessionId` comes from the checkpoint and `objective` is replaced by
|
|
2586
2602
|
* an internally-generated continuation, so both are omitted.
|
|
2587
2603
|
*/
|
|
2588
|
-
|
|
2604
|
+
/** A resume continues the suspended leg: it sends no new human message, so it takes neither an objective nor the
|
|
2605
|
+
* objective's attachments (a config that carries `userAttachments` anyway is refused before the claim). */
|
|
2606
|
+
export type ResumeTaskConfig = Omit<TaskSpec, "objective" | "sessionId" | "userAttachments">;
|
|
2589
2607
|
/** Resume plan threaded from {@link Runner.resume} into the shared run loop. */
|
|
2590
2608
|
export interface ResumeRun {
|
|
2591
2609
|
cp: Checkpoint;
|
|
@@ -2757,8 +2775,6 @@ export interface HarnessHandlersDeps {
|
|
|
2757
2775
|
parentToolCallId?: string;
|
|
2758
2776
|
sourceTaskId?: string;
|
|
2759
2777
|
};
|
|
2760
|
-
parentToolCallId: string | undefined;
|
|
2761
|
-
subagentName: string | undefined;
|
|
2762
2778
|
pushContent: (e: TaskEvent) => void;
|
|
2763
2779
|
emitCommitted: (entryId: string, role: "user" | "assistant" | "toolResult", toolCallId?: string) => void;
|
|
2764
2780
|
startedToolCallIds: Set<string>;
|
|
@@ -2776,6 +2792,7 @@ export interface HarnessHandlersDeps {
|
|
|
2776
2792
|
onError: RunnerDeps["onError"];
|
|
2777
2793
|
onNotice: RunnerDeps["onNotice"];
|
|
2778
2794
|
};
|
|
2795
|
+
mediaByteCapBytes: RunnerDeps["mediaByteCapBytes"];
|
|
2779
2796
|
/** The run's ONE isolation scope for the {@link RunInternals} observation sinks, minted by the
|
|
2780
2797
|
* identity-wiring lane (which calls the first of them) and shared with these handlers, so the
|
|
2781
2798
|
* "first failure per site" disclosure is one account for the whole run rather than one per lane. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isRemoteExecutionEnv } from "../remote-env.js";
|
|
2
2
|
import { deliverEngineNotice } from "../engine-notice.js";
|
|
3
3
|
const BOOLEAN_CAPABILITY_KEYS = ["isolation", "suspendable", "canonicalPathAuthoritative"];
|
|
4
|
-
const OPTIONAL_METHOD_KEYS = ["writeFileExclusive", "writeFileGuarded", "setFileMode", "readLink", "canonicalChain", "fileIdentity"];
|
|
4
|
+
const OPTIONAL_METHOD_KEYS = ["writeFileExclusive", "writeFileGuarded", "setFileMode", "readLink", "canonicalChain", "fileIdentity", "openBound"];
|
|
5
5
|
export function malformedCapabilityDeclarations(env) {
|
|
6
6
|
const out = [];
|
|
7
7
|
for (const key of OPTIONAL_METHOD_KEYS) {
|
|
@@ -3,6 +3,7 @@ import { askFaceOf, classifyAsk, headlessApprovalRemedy } from "../ask-origin.js
|
|
|
3
3
|
import { wireOf } from "../tool-name-readings.js";
|
|
4
4
|
import { isProtectedWrite } from "../tool-registry.js";
|
|
5
5
|
import { attestationOf, decisionText } from "../tool-policy.js";
|
|
6
|
+
import { pathDenialCardOf } from "../../tools/fs/safety.js";
|
|
6
7
|
import { boundInputHashOf } from "../canonical-json.js";
|
|
7
8
|
import { askUnresolvableRemedySeat } from "../ask-unresolvable-notice.js";
|
|
8
9
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
@@ -249,7 +250,7 @@ export async function exitGate(pass) {
|
|
|
249
250
|
gate,
|
|
250
251
|
finalInput,
|
|
251
252
|
...(() => {
|
|
252
|
-
const details = pass.denyDetails ?? (modeRefusal !== undefined ? undefined : pass.shellReadDetails);
|
|
253
|
+
const details = pathDenialCardOf(attestationOf(pass.decision, { toolCallId, toolName })?.pathDenial) ?? pass.denyDetails ?? (modeRefusal !== undefined ? undefined : pass.shellReadDetails);
|
|
253
254
|
return details !== undefined ? { details } : {};
|
|
254
255
|
})(),
|
|
255
256
|
preToolContext: pass.preToolContext,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import type { Model } from "../../internal/llm.js";
|
|
16
16
|
import type { Brain, ModelRoles, ModelRef, TaskSpec } from "../types.js";
|
|
17
17
|
import { type DistillerChatFn } from "../memory-engine/distiller.js";
|
|
18
|
-
import { type RunMemoryConsolidationOptions, type ConsolidationDriverEngine } from "../memory-engine/consolidation-driver.js";
|
|
18
|
+
import { type RunMemoryConsolidationOptions, type ConsolidationDriverEngine, type ConsolidationWriteJudge } from "../memory-engine/consolidation-driver.js";
|
|
19
19
|
import { type MemoryConsolidationOptions } from "../memory-engine/consolidation.js";
|
|
20
20
|
import type { LlmDistillerContract } from "../memory-engine/distiller.js";
|
|
21
21
|
import type { EngineNotice } from "../engine-notice.js";
|
|
@@ -44,10 +44,10 @@ export interface MemoryConsolidationDriverDeps {
|
|
|
44
44
|
* Resolve a deployment's driver options: `{ chat, model }` from the explicit seat or the role
|
|
45
45
|
* chain, plus the deployment knobs (contract / maxCycles / mintRepairBudget) and the protocol
|
|
46
46
|
* parameters (`memoryConsolidation`) threaded through verbatim. The result feeds
|
|
47
|
-
* `runMemoryConsolidationDriver(engine, scope, resolved)`
|
|
48
|
-
* coded here or in the verb's own screen, never folds to a default.
|
|
47
|
+
* `runMemoryConsolidationDriver(engine, scope, { ...resolved, writeJudge })` (the write judge is the caller's seat,
|
|
48
|
+
* never resolved from deps); every bad value refuses coded here or in the verb's own screen, never folds to a default.
|
|
49
49
|
*/
|
|
50
|
-
export declare function resolveMemoryConsolidationDriver(deps: MemoryConsolidationDriverDeps): RunMemoryConsolidationOptions
|
|
50
|
+
export declare function resolveMemoryConsolidationDriver(deps: MemoryConsolidationDriverDeps): Omit<RunMemoryConsolidationOptions, "writeJudge">;
|
|
51
51
|
/**
|
|
52
52
|
* "Is there ANY model seat to resolve a consolidation run from?" (the question the
|
|
53
53
|
* `autoRunOnRecommendation` construction door asks). It mirrors {@link resolveMemoryConsolidationDriver}'s
|
|
@@ -65,12 +65,14 @@ export declare function consolidationSeatResolvable(deps: MemoryConsolidationDri
|
|
|
65
65
|
* failure table settles the run (zero-write on mint failure, `_incomplete` on non-convergence), this
|
|
66
66
|
* bridge only relays the run's advisory notices to `onNotice` and routes a seat/engine fault to
|
|
67
67
|
* `onError(phase:"memory")`. Adds no failure semantics of its own — the trigger is the only new thing.
|
|
68
|
+
* The write judge is the LEG's; its refusal is a person's standing rule at work, so it is a refusal NOTICE, never a fault.
|
|
68
69
|
*/
|
|
69
70
|
export declare function autoRunConsolidationFromRecommendation(args: {
|
|
70
71
|
deps: MemoryConsolidationDriverDeps;
|
|
71
72
|
engine: ConsolidationDriverEngine;
|
|
72
73
|
scope: string;
|
|
73
74
|
sessionId: string;
|
|
75
|
+
writeJudge: ConsolidationWriteJudge;
|
|
74
76
|
onNotice?: (notice: EngineNotice) => void;
|
|
75
77
|
onError?: (err: unknown, ctx: {
|
|
76
78
|
phase: "memory";
|
|
@@ -3,6 +3,7 @@ import { expandTiers, roleModelIfSet } from "../roles.js";
|
|
|
3
3
|
import { isAliasModelId, openAiCompatChatSeat } from "../memory-engine/distiller.js";
|
|
4
4
|
import { runMemoryConsolidationDriver } from "../memory-engine/consolidation-driver.js";
|
|
5
5
|
import { recommendationUnconsumed } from "../memory-engine/consolidation.js";
|
|
6
|
+
import { deliverWriteRefusal } from "./memory-consolidation-write-judge.js";
|
|
6
7
|
import { asThrownError } from "../thrown-value.js";
|
|
7
8
|
function seatRefusal(message) {
|
|
8
9
|
const e = new Error(message);
|
|
@@ -68,12 +69,14 @@ export function consolidationSeatResolvable(deps) {
|
|
|
68
69
|
return (roleModelIfSet("consolidate", {}, { models, roles: deps.roles }) ?? roleModelIfSet("summarize", {}, { models, roles: deps.roles })) !== undefined;
|
|
69
70
|
}
|
|
70
71
|
export function autoRunConsolidationFromRecommendation(args) {
|
|
71
|
-
const { deps, engine, scope, sessionId, onNotice, onError } = args;
|
|
72
|
+
const { deps, engine, scope, sessionId, writeJudge, onNotice, onError } = args;
|
|
72
73
|
return (async () => {
|
|
73
74
|
try {
|
|
74
|
-
await runMemoryConsolidationDriver(engine, scope, { ...resolveMemoryConsolidationDriver(deps), onNotice });
|
|
75
|
+
await runMemoryConsolidationDriver(engine, scope, { ...resolveMemoryConsolidationDriver(deps), writeJudge, onNotice });
|
|
75
76
|
}
|
|
76
77
|
catch (err) {
|
|
78
|
+
if (deliverWriteRefusal(err, scope, onNotice))
|
|
79
|
+
return;
|
|
77
80
|
try {
|
|
78
81
|
onError?.(asThrownError(err), { phase: "memory", sessionId });
|
|
79
82
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Runner's half of the consolidation driver's write judge: the leg's synthetic write judge as the seat
|
|
3
|
+
* `runMemoryConsolidationDriver` asks (for the library directory before it claims anything, then for each file a commit
|
|
4
|
+
* lands), and the refusal's notice dialect for the auto lane. The judgment itself is `createSyntheticWriteJudge` — the layers
|
|
5
|
+
* a model's own `Write` meets; nothing here judges.
|
|
6
|
+
*/
|
|
7
|
+
import type { SyntheticWriteJudge } from "../synthetic-read-verdict.js";
|
|
8
|
+
import { type ConsolidationWriteJudge } from "../memory-engine/consolidation-driver.js";
|
|
9
|
+
import { type EngineNotice } from "../engine-notice.js";
|
|
10
|
+
/**
|
|
11
|
+
* The leg's write judge as the consolidation driver's seat: each target the run asks about is put to the leg's synthetic
|
|
12
|
+
* Write judge as one `Write` of that path, under a deadline of its own. The answer is the judge's, unchanged — nothing here
|
|
13
|
+
* decides, merges or defaults.
|
|
14
|
+
*/
|
|
15
|
+
export declare function consolidationWriteJudgeOfLeg(judge: SyntheticWriteJudge): ConsolidationWriteJudge;
|
|
16
|
+
/** Deliver a write-judge refusal as the verb's refusal notice; false (nothing delivered) for any other error. */
|
|
17
|
+
export declare function deliverWriteRefusal(err: unknown, scope: string, onNotice: ((notice: EngineNotice) => void) | undefined): boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ORG_ADJUDICATION_TIMEOUT_MS } from "../permission-rule-org.js";
|
|
2
|
+
import { CONSOLIDATION_WRITE_REFUSED_CODE } from "../memory-engine/consolidation-driver.js";
|
|
3
|
+
import { ConsolidationRefusedError } from "../memory-engine/consolidation.js";
|
|
4
|
+
import { memoryConsolidationRefusedNotice } from "../memory-engine/engine.js";
|
|
5
|
+
import { deliverEngineNotice } from "../engine-notice.js";
|
|
6
|
+
export function consolidationWriteJudgeOfLeg(judge) {
|
|
7
|
+
return (target) => judge({ file_path: target.path }, "memory-consolidation", { deadlineAt: Date.now() + ORG_ADJUDICATION_TIMEOUT_MS });
|
|
8
|
+
}
|
|
9
|
+
export function deliverWriteRefusal(err, scope, onNotice) {
|
|
10
|
+
if (!(err instanceof ConsolidationRefusedError) || err.code !== CONSOLIDATION_WRITE_REFUSED_CODE)
|
|
11
|
+
return false;
|
|
12
|
+
deliverEngineNotice(onNotice, memoryConsolidationRefusedNotice({ code: CONSOLIDATION_WRITE_REFUSED_CODE, scope, reason: err.message, occurrenceId: err.occurrenceId }));
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
@@ -4,20 +4,18 @@
|
|
|
4
4
|
*
|
|
5
5
|
* `prepareTask` used to assemble the personal lane inline (a store read + a hand-spliced session
|
|
6
6
|
* overlay + two admission arms) and the org lane from a second seam. Both now read the ONE store query —
|
|
7
|
-
* `PermissionRuleStore.effective({ sessionId })` —
|
|
8
|
-
* is shared between the two lanes of one adjudicated call.
|
|
7
|
+
* `PermissionRuleStore.effective({ sessionId })` — through the run's one reader of it (`person-path-rows.ts`).
|
|
9
8
|
*
|
|
10
9
|
* ## One read per adjudicated call
|
|
11
10
|
*
|
|
12
11
|
* The gate consults the org lane (`orgRules.adjudicate`) and then the personal lane
|
|
13
12
|
* (`persistedRules.adjudicate`) on every decision, in that order (the personal lane's deny/ask rows must
|
|
14
|
-
* be able to tighten an allow, so it can no longer wait for a surviving ask).
|
|
15
|
-
*
|
|
16
|
-
* one store query — one org-source fetch — per call,
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* for one decision.
|
|
13
|
+
* be able to tighten an allow, so it can no longer wait for a surviving ask). Both take the call's ONE read,
|
|
14
|
+
* pinned to its `toolCallId` by whichever of the call's consumers asked first (the read station ahead of them
|
|
15
|
+
* in the fold, or the org lane): a governed deployment pays one store query — one org-source fetch — per call,
|
|
16
|
+
* and every consumer of the call judges one snapshot, which is the honest shape for one decision. A partition
|
|
17
|
+
* the read could not read takes the run's one failure law (the reader's): its deny and ask rows as last read,
|
|
18
|
+
* none of its allow rows.
|
|
21
19
|
*
|
|
22
20
|
* ## Where the lanes do NOT go
|
|
23
21
|
*
|
|
@@ -33,7 +31,7 @@ import { type PathRuleBases } from "../permission-rule-model.js";
|
|
|
33
31
|
import { type HeadLeads } from "../path-rule-binding.js";
|
|
34
32
|
import type { ToolCallLike } from "../tool-registry.js";
|
|
35
33
|
import { type OrgRuleResolution } from "../permission-rule-org.js";
|
|
36
|
-
import { type EffectivePermissionRules, type PermissionRuleStoreProvider } from "../permission-rule-provider.js";
|
|
34
|
+
import { type EffectivePermissionRules, type PermissionRuleStoreProvider, type PersonRowsRead } from "../permission-rule-provider.js";
|
|
37
35
|
/** The one SHELL tool the persisted-rule lane's COMMAND grammar speaks for (the rule-offer face mints for it and the name tools); the other grammars' tools are derived (`ruleToolGrammarOf`). */
|
|
38
36
|
export declare const PERSISTED_RULE_TOOL = "Bash";
|
|
39
37
|
/**
|
|
@@ -119,9 +117,9 @@ export interface PermissionRuleLanes {
|
|
|
119
117
|
} | undefined;
|
|
120
118
|
}
|
|
121
119
|
/** The ORG lane: the org's verdict about one call, off the store view `read` supplies — the gate's `orgRules` face over the
|
|
122
|
-
*
|
|
120
|
+
* call's pinned read ({@link createPermissionRuleLanes}), and {@link orgVerdictBeforeClaim} over a view of its own. */
|
|
123
121
|
export declare function orgRuleLane(cfg: {
|
|
124
|
-
read: (toolCallId: string) => Promise<EffectivePermissionRules
|
|
122
|
+
read: (toolCallId: string) => Promise<Pick<EffectivePermissionRules, "org">>;
|
|
125
123
|
questionToolMounted: boolean;
|
|
126
124
|
questionToolName: string;
|
|
127
125
|
onRevisionDefect: (message: string) => void;
|
|
@@ -172,17 +170,13 @@ export declare function createPermissionRuleLanes(cfg: {
|
|
|
172
170
|
questionToolMounted: boolean;
|
|
173
171
|
questionToolName: string;
|
|
174
172
|
onRevisionDefect: (message: string) => void;
|
|
175
|
-
/** Sink for
|
|
176
|
-
* that were dropped) and for a durable read failure — each read's lines are delivered once, whichever
|
|
177
|
-
* lane took the read. */
|
|
173
|
+
/** Sink for a judgment against the rows that threw. */
|
|
178
174
|
onDisclosure: (message: string) => void;
|
|
179
|
-
/** The
|
|
180
|
-
*
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
/** The run's cut: a read it interrupts is abandoned quietly — a cancelled wait is no fact about the store. */
|
|
185
|
-
signal?: AbortSignal;
|
|
175
|
+
/** The run's one reader of the person's rules: the call's ONE read, pinned, that the read station, the org lane and the
|
|
176
|
+
* personal lane all take (the lanes consume a reader; they do not make one). */
|
|
177
|
+
rows: {
|
|
178
|
+
pin(toolCallId: string): Promise<PersonRowsRead>;
|
|
179
|
+
};
|
|
186
180
|
/** The allow layer's auto strip over a stored ALLOW row (true ⇒ out of this call's table BEFORE the coverage is chosen). */
|
|
187
181
|
stripsAllow?: (rule: PersistedRule, req: ToolCallRequest) => boolean;
|
|
188
182
|
}): PermissionRuleLanes;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { persistedRuleMandateOf, ruleClearanceOf } from "../ask-origin.js";
|
|
2
2
|
import {} from "../hooks.js";
|
|
3
|
-
import { adjudicatePersistedDomainRules, adjudicatePersistedNameRules, adjudicatePersistedPathRules, adjudicatePersistedRules, callPathFaceOf, domainRuleSubjectOf,
|
|
3
|
+
import { adjudicatePersistedDomainRules, adjudicatePersistedNameRules, adjudicatePersistedPathRules, adjudicatePersistedRules, callPathFaceOf, domainRuleSubjectOf, faceRowsSpeakFor, laneToolNamesOf, nameRuleSubjectOf, ruleToolGrammarOf, segmentCoverageOf, suggestRuleForName, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
4
4
|
import { callTargetViewsOf, validatePath } from "../effective-path-target.js";
|
|
5
5
|
import { bindPathRules, persistedRowAnchors, projectScopeIdentity } from "../path-rule-binding.js";
|
|
6
6
|
import { heldBy, seatOf, wireOf } from "../tool-name-readings.js";
|
|
@@ -9,11 +9,9 @@ import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
|
|
|
9
9
|
import { ownerStoreOf } from "../permission-rule-provider.js";
|
|
10
10
|
import { thrownText } from "../thrown-value.js";
|
|
11
11
|
import { strongestAnswer } from "../persisted-rule-arms.js";
|
|
12
|
-
import { gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
|
|
13
12
|
import { ORG_ADJUDICATION_TIMEOUT_MS } from "../permission-rule-org.js";
|
|
14
13
|
export const PERSISTED_RULE_TOOL = "Bash";
|
|
15
14
|
const NO_LEADS = new Map();
|
|
16
|
-
const HANDOFF_BOUND = 256;
|
|
17
15
|
export function inheritedAskRuleEvidence(provider) {
|
|
18
16
|
const org = provider?.partitions.org === true ? "not_adjudicated" : "not_wired";
|
|
19
17
|
const personal = provider?.partitions.durable === true ? "not_adjudicated" : "not_wired";
|
|
@@ -32,7 +30,7 @@ export function orgRevisionEvidenceOf(resolution, onDefect) {
|
|
|
32
30
|
export function persistedRuleHitOf(verdict) {
|
|
33
31
|
return verdict === undefined
|
|
34
32
|
? undefined
|
|
35
|
-
: { behavior: verdict.behavior, rules: verdict.rules.map((r) => ({ rule: r.rule, dots: r.adds.map((a) => ({ actor: a.dot.actor, counter: a.dot.counter })) })) };
|
|
33
|
+
: { behavior: verdict.behavior, rules: verdict.rules.map((r) => ({ rule: r.rule, dots: r.adds.map((a) => ({ actor: a.dot.actor, counter: a.dot.counter })) })), ...(verdict.denial !== undefined ? { denial: { ...verdict.denial } } : {}) };
|
|
36
34
|
}
|
|
37
35
|
export function persistedRuleAnswerOf(verdict) {
|
|
38
36
|
if (verdict?.unreadable !== undefined)
|
|
@@ -96,57 +94,30 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
96
94
|
if (provider === undefined)
|
|
97
95
|
return { personal: undefined, org: undefined };
|
|
98
96
|
const anonymous = cfg.principal === undefined || cfg.principal === "";
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return
|
|
105
|
-
});
|
|
106
|
-
const hand = (toolCallId) => {
|
|
107
|
-
const p = read();
|
|
108
|
-
handed.delete(toolCallId);
|
|
109
|
-
handed.set(toolCallId, p);
|
|
110
|
-
if (handed.size > HANDOFF_BOUND) {
|
|
111
|
-
const oldest = handed.keys().next().value;
|
|
112
|
-
if (oldest !== undefined)
|
|
113
|
-
handed.delete(oldest);
|
|
114
|
-
}
|
|
115
|
-
p.catch(() => undefined);
|
|
116
|
-
return p;
|
|
117
|
-
};
|
|
118
|
-
const claim = (toolCallId) => {
|
|
119
|
-
const p = handed.get(toolCallId);
|
|
120
|
-
if (p === undefined)
|
|
121
|
-
return read();
|
|
122
|
-
handed.delete(toolCallId);
|
|
123
|
-
return p;
|
|
97
|
+
const reader = cfg.rows;
|
|
98
|
+
const orgViewOf = async (toolCallId) => {
|
|
99
|
+
const read = await reader.pin(toolCallId);
|
|
100
|
+
if (read.org === undefined)
|
|
101
|
+
throw new Error(read.unreadable ?? "the store did not answer");
|
|
102
|
+
return { org: read.org };
|
|
124
103
|
};
|
|
125
|
-
const org = provider.partitions.org ? orgRuleLane({ read:
|
|
104
|
+
const org = provider.partitions.org ? orgRuleLane({ read: orgViewOf, questionToolMounted: cfg.questionToolMounted, questionToolName: cfg.questionToolName, onRevisionDefect: cfg.onRevisionDefect }) : undefined;
|
|
126
105
|
const personalAdjudicate = async (req) => {
|
|
127
106
|
if (anonymous && !cfg.localOwnerDeclared)
|
|
128
107
|
return undefined;
|
|
129
108
|
const judged = laneToolNamesOf(req).map((n) => [n, ruleToolGrammarOf(n)]);
|
|
130
|
-
if (!judged.some(([, g]) => g === "path") &&
|
|
109
|
+
if (!judged.some(([, g]) => g === "path") && faceRowsSpeakFor(callPathFaceOf(req)))
|
|
131
110
|
judged.push([req.toolName, "path"]);
|
|
132
111
|
if (judged.length === 0)
|
|
133
112
|
return undefined;
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
view = await withGateTimeout(claim(req.toolCallId), ORG_ADJUDICATION_TIMEOUT_MS, cfg.signal, "persistedRules");
|
|
137
|
-
}
|
|
138
|
-
catch (err) {
|
|
139
|
-
return gateWaitFailureOf(err) === "aborted" ? { unreadable: true } : skipped(thrownText(err));
|
|
140
|
-
}
|
|
141
|
-
if (view.unreadable !== undefined)
|
|
142
|
-
skipped(view.unreadable, view.unreadableSources);
|
|
143
|
-
cfg.onRowsDropped(view.droppedRows);
|
|
113
|
+
const view = await reader.pin(req.toolCallId);
|
|
144
114
|
try {
|
|
145
115
|
const table = view.rules.filter((r) => r.behavior !== "allow" || cfg.stripsAllow?.(r, req) !== true);
|
|
146
116
|
const leads = judged.some(([, g]) => g === "path") ? await cfg.leads.resolve(bindPathRules(table.filter((r) => r.match === "path" || r.match === "subpath").map((r) => ({ parsed: r, ruleText: r.rule, rule: r })), (spec) => anchorsOf(spec.rule))) : NO_LEADS;
|
|
147
117
|
const scopeIdentity = await projectScopeIdentity(table, cfg.rootCanonical, cfg.leads);
|
|
148
118
|
const answer = strongestAnswer(judged.map(([n, grammar]) => judgeAgainst(table, { ...req, toolName: n }, grammar, leads, scopeIdentity)));
|
|
149
|
-
|
|
119
|
+
const stands = answer !== undefined && (("behavior" in answer && answer.behavior === "deny") || "unreadable" in answer);
|
|
120
|
+
return view.unreadable !== undefined && !stands ? { unreadable: true } : answer;
|
|
150
121
|
}
|
|
151
122
|
catch (err) {
|
|
152
123
|
const why = `judging this call against the rows threw: ${thrownText(err)}`;
|
|
@@ -174,7 +145,6 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
174
145
|
const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd, ...scope });
|
|
175
146
|
return coverage !== undefined ? { segmentCoverage: coverage } : undefined;
|
|
176
147
|
};
|
|
177
|
-
const skipped = (why, sources) => (cfg.onDisclosure(why), cfg.onStoreUnreadable("call", sources), { unreadable: true });
|
|
178
148
|
const personal = provider.partitions.durable ? { adjudicate: personalAdjudicate } : undefined;
|
|
179
149
|
return { personal, org };
|
|
180
150
|
}
|