@sema-agent/core 7.45.0 → 7.47.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 +298 -1
- package/dist/agents/extra-tool-names.d.ts +65 -0
- package/dist/agents/extra-tool-names.js +124 -0
- package/dist/agents/subagent.d.ts +17 -5
- package/dist/agents/subagent.js +33 -25
- package/dist/agents/tool-filter.d.ts +17 -1
- package/dist/agents/tool-filter.js +7 -0
- package/dist/core/agent-config-dir.d.ts +20 -0
- package/dist/core/agent-config-dir.js +49 -0
- package/dist/core/allow-entry.d.ts +6 -4
- package/dist/core/allow-entry.js +6 -5
- package/dist/core/ask-origin.d.ts +35 -12
- package/dist/core/ask-origin.js +24 -7
- package/dist/core/checkpoint-store.d.ts +39 -2
- package/dist/core/effective-path-target.d.ts +136 -4
- package/dist/core/effective-path-target.js +110 -2
- package/dist/core/engine-notice.d.ts +12 -4
- package/dist/core/gate-fold.js +5 -2
- package/dist/core/gate-lanes.js +11 -4
- package/dist/core/gate-outcome.d.ts +4 -2
- package/dist/core/gate-outcome.js +2 -2
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/path-rule-binding.d.ts +65 -0
- package/dist/core/path-rule-binding.js +99 -0
- package/dist/core/permission-rule-model.d.ts +59 -24
- package/dist/core/permission-rule-model.js +48 -44
- package/dist/core/permission-rule-provider.d.ts +7 -0
- package/dist/core/permission-rule-provider.js +10 -3
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +6 -2
- package/dist/core/permission-rule-sync.js +2 -2
- package/dist/core/permission-rules.d.ts +26 -45
- package/dist/core/permission-rules.js +77 -57
- package/dist/core/runner/child-tree.d.ts +97 -0
- package/dist/core/runner/child-tree.js +104 -0
- package/dist/core/runner/construction-doors.d.ts +5 -0
- package/dist/core/runner/construction-doors.js +6 -0
- package/dist/core/runner/contracts.d.ts +46 -0
- package/dist/core/runner/gate-exit.d.ts +4 -1
- package/dist/core/runner/gate-exit.js +5 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +34 -25
- package/dist/core/runner/permission-rule-lanes.js +18 -12
- package/dist/core/runner/persisted-path-rows.d.ts +31 -7
- package/dist/core/runner/persisted-path-rows.js +31 -12
- package/dist/core/runner/prepare-ask-lane.d.ts +4 -0
- package/dist/core/runner/prepare-ask-lane.js +4 -2
- package/dist/core/runner/prepare-execution-env.d.ts +5 -2
- package/dist/core/runner/prepare-execution-env.js +10 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -0
- package/dist/core/runner/prepare-gate-stations.js +2 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
- package/dist/core/runner/prepare-hands-readface.js +11 -11
- package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
- package/dist/core/runner/prepare-inherited-gate.js +8 -2
- package/dist/core/runner/prepare-park-ask.d.ts +3 -0
- package/dist/core/runner/prepare-park-ask.js +5 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +14 -2
- package/dist/core/runner/prepare-policy-chain.js +13 -5
- package/dist/core/runner/prepare-suspend-saga.d.ts +10 -0
- package/dist/core/runner/prepare-suspend-saga.js +7 -1
- package/dist/core/runner/prepare-task.js +37 -18
- package/dist/core/runner/prepare-turn-wiring.d.ts +2 -0
- package/dist/core/runner/prepare-turn-wiring.js +2 -1
- package/dist/core/runner/prepare-workspace-restore.d.ts +3 -0
- package/dist/core/runner/prepare-workspace-restore.js +1 -0
- package/dist/core/runner/resume-apply.js +17 -3
- package/dist/core/runner/resume-checkpoint-screen.js +13 -0
- package/dist/core/runner/run-leg.js +5 -1
- package/dist/core/runner/runtask.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +1 -1
- package/dist/core/runner/write-protection-station.d.ts +3 -2
- package/dist/core/runner/write-protection-station.js +1 -0
- package/dist/core/runner-deps.d.ts +14 -0
- package/dist/core/sensitive-path-policy.d.ts +8 -0
- package/dist/core/sensitive-path-policy.js +18 -2
- package/dist/core/shell-wrapper-projection.d.ts +11 -0
- package/dist/core/shell-wrapper-projection.js +56 -0
- package/dist/core/shell-wrapper-table.d.ts +144 -34
- package/dist/core/shell-wrapper-table.js +40 -20
- package/dist/core/synthetic-read-verdict.d.ts +3 -0
- package/dist/core/synthetic-read-verdict.js +3 -1
- package/dist/core/tool-catalog-entries.d.ts +21 -0
- package/dist/core/tool-catalog-entries.js +12 -2
- package/dist/core/tool-face.d.ts +10 -1
- package/dist/core/tool-name-readings.d.ts +8 -0
- package/dist/core/tool-name-readings.js +2 -0
- package/dist/core/tool-policy.d.ts +40 -17
- package/dist/core/tool-policy.js +11 -9
- package/dist/core/tool-registry.d.ts +9 -0
- package/dist/core/tool-registry.js +10 -0
- package/dist/core/tool-spec.d.ts +3 -2
- package/dist/core/write-protect.d.ts +14 -3
- package/dist/core/write-protect.js +41 -10
- package/dist/index.d.ts +6 -4
- package/dist/index.js +5 -3
- package/dist/orchestration/run-spec.js +8 -7
- package/dist/orchestration/workflow.js +1 -1
- package/dist/shell/lex.d.ts +22 -0
- package/dist/shell/lex.js +75 -13
- package/dist/tools/fs/bash-program-position.d.ts +14 -90
- package/dist/tools/fs/bash-program-position.js +41 -74
- package/dist/tools/fs/bash-readonly-classifier.d.ts +17 -444
- package/dist/tools/fs/bash-readonly-classifier.js +169 -1225
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/program-read-model.d.ts +711 -0
- package/dist/tools/fs/program-read-model.js +1259 -0
- package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
- package/dist/tools/fs/program-read-model.rows.js +624 -0
- package/dist/tools/fs/read-deny-home.d.ts +19 -1
- package/dist/tools/fs/read-deny-home.js +62 -24
- package/dist/tools/fs/read-deny.d.ts +54 -14
- package/dist/tools/fs/read-deny.js +104 -50
- package/dist/tools/fs/removal-targets.d.ts +7 -6
- package/dist/tools/fs/removal-targets.js +34 -24
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +10 -3
- package/dist/tools/fs/shell-read-argv.d.ts +86 -0
- package/dist/tools/fs/shell-read-argv.js +388 -0
- package/dist/tools/fs/shell-read-bindings.d.ts +300 -0
- package/dist/tools/fs/shell-read-bindings.js +755 -0
- package/dist/tools/fs/shell-read-check.js +23 -15
- package/dist/tools/fs/shell-read-establish.js +82 -8
- package/dist/tools/fs/shell-read-judge.d.ts +11 -1
- package/dist/tools/fs/shell-read-judge.js +9 -1
- package/dist/tools/fs/shell-read-plan.d.ts +33 -8
- package/dist/tools/fs/shell-read-plan.js +309 -407
- package/dist/tools/fs/shell-read-word.d.ts +60 -26
- package/dist/tools/fs/shell-read-word.js +47 -37
- package/dist/tools/fs/shell-read-write-targets.d.ts +15 -64
- package/dist/tools/fs/shell-read-write-targets.js +85 -137
- package/dist/tools/fs/shell-reentry-text.d.ts +136 -6
- package/dist/tools/fs/shell-reentry-text.js +203 -53
- package/dist/tools/monitor.d.ts +3 -3
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +25 -5
- package/wire-changes/core@7.46.0.json +1522 -0
- package/wire-changes/core@7.47.0.json +412 -0
|
@@ -488,6 +488,9 @@ export interface ModeArbitrationRow {
|
|
|
488
488
|
* tool's effect lands at a remote peer, outside any containment the leg declares). `hookAskFloor` marks the classes the reference's rule / safety check answers
|
|
489
489
|
* `ask` for — an ask rule, a question the tool needs a person for, a dangerous removal, a protected or unresolvable write
|
|
490
490
|
* target, the session transcript, another's shared artifact.
|
|
491
|
+
* A row is a class's reading on every face but the ones {@link RELEASED_ON_SHELL_FACE} names: `write_protection_unresolved`'s
|
|
492
|
+
* `bypassImmune` is the file face's (and every other face's) reading, and the shell face reads it released — folded where the
|
|
493
|
+
* ask's row is ({@link foldArbitrationRow}), listed per face by {@link releasableAskClassesFor}.
|
|
491
494
|
*/
|
|
492
495
|
export declare const MODE_ARBITRATION: {
|
|
493
496
|
readonly content_question: {
|
|
@@ -668,23 +671,37 @@ export declare const MODE_ARBITRATION: {
|
|
|
668
671
|
};
|
|
669
672
|
/** The fence over the arbitration table: `never` while every class has a row. */
|
|
670
673
|
export type ArbitrationTableCoversEveryClass = AssertAllKeysHandled<Exclude<AskClass, keyof typeof MODE_ARBITRATION>>;
|
|
674
|
+
/**
|
|
675
|
+
* The FACE a question was put on — the second axis the permission mode reads a class under, beside the leg's read face
|
|
676
|
+
* ({@link ContainmentFace}). `shell`: the call's `command` argument is a shell command line the command grammar judges (the
|
|
677
|
+
* tool catalog's shell face — Bash, Monitor); `other`: every other call (the file tools, MCP, A2A, a caller's own tool). The set
|
|
678
|
+
* is closed; {@link askFaceOf} is the one derivation of a face from a tool name.
|
|
679
|
+
*/
|
|
680
|
+
export declare const ASK_FACES: readonly ["shell", "other"];
|
|
681
|
+
export type AskFace = (typeof ASK_FACES)[number];
|
|
682
|
+
/** The face of a call, by its WIRE name (the name the gate resolved the call to — a release widens, so an alias a tool merely
|
|
683
|
+
* wears does not count): the catalog's shell face for exactly the names that declare the command grammar, `other` for every
|
|
684
|
+
* other name. */
|
|
685
|
+
export declare function askFaceOf(toolName: string): AskFace;
|
|
671
686
|
/** The classes whose own mint puts the `requiresRealApproval` bit on the ask — a bit one of them explains is not
|
|
672
687
|
* the bare bit `policy_rra` names. The mark classes (probe / write protection / tool marks / operator forced / the
|
|
673
688
|
* shell floor) mint no bit and explain none — except `requires_user_interaction`, whose declaration the origin stamp
|
|
674
689
|
* turns into the bit on every ask of the tool. */
|
|
675
690
|
export declare const RRA_EXPLAINERS: ReadonlySet<AskClass>;
|
|
676
|
-
/** The classes a permission mode can RELEASE
|
|
677
|
-
*
|
|
678
|
-
|
|
691
|
+
/** The classes a permission mode can RELEASE for a call on `face`, in {@link ASK_CLASSES} order. The same list on every face
|
|
692
|
+
* but one class — {@link RELEASED_ON_SHELL_FACE}. A frozen list: a caller holding it cannot change the next read. A word
|
|
693
|
+
* outside {@link ASK_FACES} throws. */
|
|
694
|
+
export declare function releasableAskClassesFor(face: AskFace): readonly AskClass[];
|
|
679
695
|
/**
|
|
680
696
|
* The closed set of `reason` words a mode release carries (`GateDisposition.released.reason`, the `released` arm of
|
|
681
697
|
* {@link Arbitration}): each is a releasable class the release record names on its own — `rule_unreadable`, a release
|
|
682
|
-
* over a person's rule the call could not be read against
|
|
698
|
+
* over a person's rule the call could not be read against; `write_protection_unresolved`, a release (the shell face only,
|
|
699
|
+
* {@link RELEASED_ON_SHELL_FACE}) over a write target the judge could not resolve. A release carries the first member (array order) its class
|
|
683
700
|
* set holds, and none when it holds no member. {@link arbitrateByMode} and {@link isModeReleaseReason} read one copy
|
|
684
701
|
* of this list taken at load (a caller that writes into the exported array changes neither), so a new reason is one
|
|
685
702
|
* word here.
|
|
686
703
|
*/
|
|
687
|
-
export declare const MODE_RELEASE_REASONS: readonly ["rule_unreadable"];
|
|
704
|
+
export declare const MODE_RELEASE_REASONS: readonly ["rule_unreadable", "write_protection_unresolved"];
|
|
688
705
|
export type ModeReleaseReason = (typeof MODE_RELEASE_REASONS)[number];
|
|
689
706
|
/** Membership test for {@link ModeReleaseReason} — the word crosses the wire on `tool_end.gate.disposition.released.reason`. */
|
|
690
707
|
export declare function isModeReleaseReason(v: unknown): v is ModeReleaseReason;
|
|
@@ -700,6 +717,8 @@ export interface AskClassFacts extends AskOriginFacts {
|
|
|
700
717
|
readonly ancestorClasses?: ReadonlySet<AskClass>;
|
|
701
718
|
/** The read face the leg this ask was raised on resolved ({@link ContainmentFace}); absent ⇒ none resolved (a leg with no hands). */
|
|
702
719
|
readonly readFace?: ContainmentFace;
|
|
720
|
+
/** The tool face the question was put on ({@link askFaceOf} of the call's tool name); absent ⇒ the face-less reading, which is the `other` face's. */
|
|
721
|
+
readonly askFace?: AskFace;
|
|
703
722
|
}
|
|
704
723
|
/** The classification the arbitration reads: the whole set, the disclosed word (the set's first member in
|
|
705
724
|
* {@link ASK_CLASSES} order) and the row folded across the set. */
|
|
@@ -735,10 +754,11 @@ export declare function decisionAskClassesOf(d: Extract<PermissionResult, {
|
|
|
735
754
|
}>): Set<AskClass>;
|
|
736
755
|
/** Fold a class set onto one row: `bypassImmune` and `hookAskFloor` by OR (any immune member keeps the ask; any floored
|
|
737
756
|
* member floors a hook's ask), the three permissive switches by AND (one refusing member withholds the lane). An empty
|
|
738
|
-
* set is `ordinary`.
|
|
739
|
-
*
|
|
740
|
-
*
|
|
741
|
-
|
|
757
|
+
* set is `ordinary`. The two faces the ask was raised under enter HERE and nowhere else, so every reader of the folded row
|
|
758
|
+
* reads it already folded: `readFace` — `roots` takes the classifier's lane from a set that holds a class the narrowed face
|
|
759
|
+
* withholds from it; `askFace` — `shell` reads a class {@link RELEASED_ON_SHELL_FACE} holds as not bypass-immune. Absent
|
|
760
|
+
* faces read the table's own rows. */
|
|
761
|
+
export declare function foldArbitrationRow(classes: ReadonlySet<AskClass>, readFace?: ContainmentFace, askFace?: AskFace): ModeArbitrationRow;
|
|
742
762
|
/**
|
|
743
763
|
* The ONE class derivation: the decision half ({@link decisionAskClassesOf}) joined with the gate's own facts —
|
|
744
764
|
* the question tool, an ancestor's mark, the org layer's two arms, the persisted lane's unreadable call, the tool
|
|
@@ -814,9 +834,12 @@ export type Arbitration = {
|
|
|
814
834
|
* The ONE mode read point. `dontAsk` refuses every ask — one value for every class — but a hook's that no member of the
|
|
815
835
|
* set floors (`hookAskFloor`: a hook's ask beside a rule's or a safety check's ask is refused, as the reference's full
|
|
816
836
|
* pipeline refuses it; a hook's ask alone is the reference's forced decision, which never reaches that conversion);
|
|
817
|
-
* `bypassPermissions` refuses a write whose protection could not be judged
|
|
818
|
-
* releasing would be looser than the allow the
|
|
819
|
-
*
|
|
837
|
+
* `bypassPermissions` refuses a write whose protection could not be judged where that class stays immune (the file
|
|
838
|
+
* face: a person cannot be asked, and releasing would be looser than the allow the write-protection tighten refuses there —
|
|
839
|
+
* the folded row says it, {@link foldArbitrationRow}), keeps every immune class and releases the rest: the shell face
|
|
840
|
+
* releases that class ({@link RELEASED_ON_SHELL_FACE}), and there the tighten turns an allow into the same question rather
|
|
841
|
+
* than a refusal (`writeProtectionTightenDecision` reads the face), so on neither face is the release looser than an allow;
|
|
842
|
+
* `acceptEdits` answers an ORDINARY ask itself on a call the caller vouches is one of the mode's file commands
|
|
820
843
|
* (`call.fileCommand`, `accepted`); `plan`, `default` and `auto` hand every ask on (plan's own questions were raised by
|
|
821
844
|
* its write gate; the reference implementation refuses nothing at this point under plan — auto's classifier reads the
|
|
822
845
|
* row at its own station). Reads the class SET and the folded row — never the disclosed word.
|
package/dist/core/ask-origin.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isShellFaceToolName } from "./tool-registry.js";
|
|
1
2
|
export const ASK_ORIGINS = Object.freeze(["content_question", "ancestor_marked", "org_unavailable", "org_rule", "rule_store_unavailable", "hook", "ask_rule", "denial_limit_fallback", "shell_gate_tighten", "safety_tighten", "policy"]);
|
|
2
3
|
const ASK_ORIGIN_SET = new Set(ASK_ORIGINS);
|
|
3
4
|
export function isAskOrigin(v) {
|
|
@@ -117,7 +118,7 @@ const MANDATE_DISCLOSURE = {
|
|
|
117
118
|
write_protection_unresolved: "the write-protection judge could not resolve this write's target, so whether it lands on the protected table is unknown — it is cleared by confirming this call: no standing rule and no automatic decider answers for it, though a wired approver seat can",
|
|
118
119
|
};
|
|
119
120
|
export function mandateDisclosureOf(mandate) {
|
|
120
|
-
return mandate === undefined ? "an explicit ask rule matched this call (a person's ask-me-each-time outranks a standing allow rule)" : MANDATE_DISCLOSURE[mandate];
|
|
121
|
+
return mandate === undefined ? "an explicit ask rule matched this call (a person's or the deployment's ask-me-each-time outranks a standing allow rule)" : MANDATE_DISCLOSURE[mandate];
|
|
121
122
|
}
|
|
122
123
|
export function persistedRuleMandateOf(marks) {
|
|
123
124
|
const word = irreversibilityMandateOf(marks);
|
|
@@ -224,10 +225,26 @@ export const MODE_ARBITRATION = {
|
|
|
224
225
|
unstated: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: true, hookAskFloor: false },
|
|
225
226
|
ordinary: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: true, hookAskFloor: false },
|
|
226
227
|
};
|
|
228
|
+
export const ASK_FACES = Object.freeze(["shell", "other"]);
|
|
229
|
+
export function askFaceOf(toolName) {
|
|
230
|
+
return isShellFaceToolName(toolName) ? "shell" : "other";
|
|
231
|
+
}
|
|
232
|
+
const RELEASED_ON_SHELL_FACE = new Set(["write_protection_unresolved"]);
|
|
233
|
+
function bypassImmuneOn(c, face) {
|
|
234
|
+
return MODE_ARBITRATION[c].bypassImmune && !(face === "shell" && RELEASED_ON_SHELL_FACE.has(c));
|
|
235
|
+
}
|
|
227
236
|
const CLASSIFIER_WITHHELD_ON_ROOTS = new Set(["probe"]);
|
|
228
237
|
export const RRA_EXPLAINERS = new Set(["dangerous_delete", "unverifiable_delete", "transcript_write", "org_rule", "governance_unavailable", "rule_unreadable", "denial_limit_fallback", "classifier_no_ruling", "ancestor", "artifact_shared_read", "requires_user_interaction"]);
|
|
229
|
-
|
|
230
|
-
|
|
238
|
+
const RELEASABLE_BY_FACE = Object.freeze({
|
|
239
|
+
shell: Object.freeze(ASK_CLASSES.filter((c) => !bypassImmuneOn(c, "shell"))),
|
|
240
|
+
other: Object.freeze(ASK_CLASSES.filter((c) => !bypassImmuneOn(c, "other"))),
|
|
241
|
+
});
|
|
242
|
+
export function releasableAskClassesFor(face) {
|
|
243
|
+
if (!ASK_FACES.includes(face))
|
|
244
|
+
throw new TypeError(`releasableAskClassesFor: "${String(face)}" is not an ask face (${ASK_FACES.join(", ")})`);
|
|
245
|
+
return RELEASABLE_BY_FACE[face];
|
|
246
|
+
}
|
|
247
|
+
export const MODE_RELEASE_REASONS = Object.freeze(["rule_unreadable", "write_protection_unresolved"]);
|
|
231
248
|
const MODE_RELEASE_REASON_SET = new Set(MODE_RELEASE_REASONS);
|
|
232
249
|
export function isModeReleaseReason(v) {
|
|
233
250
|
return MODE_RELEASE_REASON_SET.has(v);
|
|
@@ -278,14 +295,14 @@ export function decisionAskClassesOf(d) {
|
|
|
278
295
|
out.add(c);
|
|
279
296
|
return out;
|
|
280
297
|
}
|
|
281
|
-
export function foldArbitrationRow(classes, readFace) {
|
|
298
|
+
export function foldArbitrationRow(classes, readFace, askFace) {
|
|
282
299
|
if (classes.size === 0)
|
|
283
300
|
return MODE_ARBITRATION.ordinary;
|
|
284
301
|
const withheld = readFace === "roots" && [...classes].some((c) => CLASSIFIER_WITHHELD_ON_ROOTS.has(c));
|
|
285
302
|
let bypassImmune = false, classifierEligible = !withheld, sandboxAdmission = true, ruleMayClear = true, hookAskFloor = false;
|
|
286
303
|
for (const c of classes) {
|
|
287
304
|
const row = MODE_ARBITRATION[c];
|
|
288
|
-
bypassImmune ||=
|
|
305
|
+
bypassImmune ||= bypassImmuneOn(c, askFace);
|
|
289
306
|
classifierEligible &&= row.classifierEligible;
|
|
290
307
|
sandboxAdmission &&= row.sandboxAdmission;
|
|
291
308
|
ruleMayClear &&= row.ruleMayClear;
|
|
@@ -340,7 +357,7 @@ export function classifyAsk(d, facts) {
|
|
|
340
357
|
if (classes.size === 0)
|
|
341
358
|
classes.add("ordinary");
|
|
342
359
|
const askClass = ASK_CLASSES.find((c) => classes.has(c));
|
|
343
|
-
return { classes, askClass, row: foldArbitrationRow(classes, facts.readFace) };
|
|
360
|
+
return { classes, askClass, row: foldArbitrationRow(classes, facts.readFace, facts.askFace) };
|
|
344
361
|
}
|
|
345
362
|
export function headlessDenialMessage(toolName, askMessage) {
|
|
346
363
|
return `Permission for this tool use was denied: it requires interactive approval, and permission prompts are not available in this session (nobody can be asked to approve "${toolName}" in this run — headless auto-deny). The action was NOT performed. Do not claim it succeeded, and do not retry it in this session — report the limitation to the user, or suggest an alternative. What was requested: ${askMessage}`;
|
|
@@ -379,7 +396,7 @@ export function arbitrateByMode(mode, c, disclosure, call) {
|
|
|
379
396
|
case "dontAsk":
|
|
380
397
|
return has("hook") && !c.row.hookAskFloor ? { kind: "ask" } : { kind: "denied_by_mode", mode, askClass: c.askClass, classes };
|
|
381
398
|
case "bypassPermissions":
|
|
382
|
-
if (has("write_protection_unresolved"))
|
|
399
|
+
if (has("write_protection_unresolved") && c.row.bypassImmune)
|
|
383
400
|
return { kind: "denied_by_mode", mode, askClass: c.askClass, classes };
|
|
384
401
|
if (c.row.bypassImmune)
|
|
385
402
|
return { kind: "ask" };
|
|
@@ -1095,6 +1095,22 @@ export interface CheckpointState {
|
|
|
1095
1095
|
* plain on-disk state that survives a process-local suspend (worktrees included — the tree stays on disk).
|
|
1096
1096
|
*/
|
|
1097
1097
|
handsCwd?: string;
|
|
1098
|
+
/**
|
|
1099
|
+
* The hands band's ROOT at suspend (the canonical root its tools resolve a root-relative spelling against — the
|
|
1100
|
+
* `root` every adjudicated request of the leg carries). Read by ONE seat: the pre-claim recheck of an edited parked
|
|
1101
|
+
* call, which has no environment yet and judges the edit at the row's coordinates (`handsRoot` + {@link handsCwd}).
|
|
1102
|
+
* After the claim the resumed leg uses its own live root. Absent on a row written before this field (that recheck then
|
|
1103
|
+
* reads the root as unknown, as before) and when the task has no hands. An ignorable optional field: an older reader
|
|
1104
|
+
* that does not know it resumes the row as it always did, so it raises no row format.
|
|
1105
|
+
*/
|
|
1106
|
+
handsRoot?: string;
|
|
1107
|
+
/**
|
|
1108
|
+
* The home the leg's caller policies installed their `~/` words at (the execution environment's declared home, absolute),
|
|
1109
|
+
* beside {@link handsRoot}: the pre-claim recheck installs the edit-base policy at the row's root and this home, the way
|
|
1110
|
+
* the leg did. Absent on a row written before this field, on a hands-less task and on an environment that declares no
|
|
1111
|
+
* absolute home — a `~/` row then reads unanchored there (never the engine process's home). Ignorable, like `handsRoot`.
|
|
1112
|
+
*/
|
|
1113
|
+
handsHome?: string;
|
|
1098
1114
|
/**
|
|
1099
1115
|
* the ACTIVE EnterWorktree session at suspend (worktree.ts keeps it in a shared
|
|
1100
1116
|
* serializable ref, not a closure-only var, precisely so it lands here). Without it a resume LOST the
|
|
@@ -1166,6 +1182,21 @@ export interface CheckpointState {
|
|
|
1166
1182
|
* the v2 contract are refused, even when every admitted set en route was EMPTY. Absent on
|
|
1167
1183
|
* trees that never had a governance surface. */
|
|
1168
1184
|
orgAdmissionGoverned?: true;
|
|
1185
|
+
/** The chain's top leg's root and home at suspend (data half): the anchors of the person's global / session path rows
|
|
1186
|
+
* and of every `~/` row on this chain (a project row anchors at its own scope root). Absent on older checkpoints and on
|
|
1187
|
+
* a top leg ⇒ the resumed leg is its own top. Rebased with the workspace on a restore elsewhere; a value that is not
|
|
1188
|
+
* an absolute path anchors nothing (the rows it would anchor read `unreadable`). */
|
|
1189
|
+
pathRuleAnchors?: {
|
|
1190
|
+
readonly root: string;
|
|
1191
|
+
readonly home?: string;
|
|
1192
|
+
};
|
|
1193
|
+
/** Where each parent constraint the suspended leg ran under was installed, position for position (data half; `null`: an
|
|
1194
|
+
* entry with no path rules). A resume that re-supplies the chain uninstalled installs each entry there. Rebased with the
|
|
1195
|
+
* workspace; a value that is not an absolute path installs nothing (the entry is installed at the chain top). */
|
|
1196
|
+
pathRuleInstallations?: ReadonlyArray<{
|
|
1197
|
+
readonly root: string;
|
|
1198
|
+
readonly home?: string;
|
|
1199
|
+
} | null>;
|
|
1169
1200
|
requiresParentConstraint: boolean;
|
|
1170
1201
|
/** How many opaque parent constraints the suspended leg ran under (present iff `requiresParentConstraint`).
|
|
1171
1202
|
* The resume pre-CAS ladder rejects a re-supply whose `parentConstraints.length` differs
|
|
@@ -2036,14 +2067,20 @@ export declare function summarizeCheckpoint(cp: Checkpoint): CheckpointSummary;
|
|
|
2036
2067
|
* steer seat, which no worker version reads and the host decides itself, or a retired mode-seed member of
|
|
2037
2068
|
* `inheritedGate`, which an engine at 7.38.x or earlier still reads once).
|
|
2038
2069
|
* · `invalid_outcome` pre-CAS row-integrity arms: `"real_approval_damaged"`, `"real_approval_forged"`,
|
|
2039
|
-
* `"read_face_damaged"`, `"read_face_forged"`, `"write_face_damaged"`, `"skill_grants_damaged"`, `"constraint_chain_missing"` (all terminal for the row's
|
|
2070
|
+
* `"read_face_damaged"`, `"read_face_forged"`, `"write_face_damaged"`, `"skill_grants_damaged"`, `"constraint_chain_missing"`, `"hands_coordinates_damaged"` (all terminal for the row's
|
|
2040
2071
|
* current bytes — no worker version redeems a damaged row).
|
|
2041
2072
|
*/
|
|
2042
2073
|
export type CheckpointErrorReason = "version_newer" | "env_factory_missing" | "governed_unwired" | "origin_missing" | "ledger_event_unknown" | "seat_retired" | "real_approval_damaged" | "real_approval_forged" | "read_face_damaged" | "read_face_forged"
|
|
2043
2074
|
/** `state.writeFace` is present but not a well-formed section. No `_forged` twin: the section is
|
|
2044
2075
|
* legal-and-absent at every version, so there is no version to judge a fabrication against — and a
|
|
2045
2076
|
* fabricated "open" still needs the LIVE resolution to say open too. */
|
|
2046
|
-
| "write_face_damaged" | "skill_grants_damaged" | "constraint_chain_missing"
|
|
2077
|
+
| "write_face_damaged" | "skill_grants_damaged" | "constraint_chain_missing"
|
|
2078
|
+
/** `state.handsRoot` / `state.handsHome` / `state.handsCwd` is present but not an absolute path (the row's hands' coordinates). */
|
|
2079
|
+
| "hands_coordinates_damaged"
|
|
2080
|
+
/** On `resume.parent_constraint_mismatch`: a re-supplied chain entry whose path rules depend on the leg that installed it has
|
|
2081
|
+
* no installation record (a chain written before the record existed) / a record that does not cover the chain entry for
|
|
2082
|
+
* entry / a record member naming no absolute root. */
|
|
2083
|
+
| "installation_record_absent" | "installation_record_misaligned" | "installation_record_invalid";
|
|
2047
2084
|
/**
|
|
2048
2085
|
* The codes a RESUME's pre-claim configuration screen carries as `detail.code` of a `checkpoint.config_refused` — the seven
|
|
2049
2086
|
* words prepare's mode, output-style, approval-reach, invoked-skill and recorded-mode doors throw, held unchanged so one refusal
|
|
@@ -20,9 +20,19 @@
|
|
|
20
20
|
*
|
|
21
21
|
* The fix is not a fourth arm in the lanes: it is that "which base does this tool resolve against, and what
|
|
22
22
|
* does it target when the slot is empty" is a FACT OF THE TOOL, so the tool declares it once and both
|
|
23
|
-
* sides read the declaration. {@link pathTargetBaseOf} is the shared SELECTION (which base)
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* sides read the declaration. {@link pathTargetBaseOf} is the shared SELECTION (which base).
|
|
24
|
+
*
|
|
25
|
+
* ## The views of one target
|
|
26
|
+
*
|
|
27
|
+
* A target is seen three ways: its LEXICAL view (the spelling joined to the selected base, `.` / `..` folded —
|
|
28
|
+
* zero IO), its CANONICAL view (the same spelling resolved by the execution environment: symlinks, aliases of the
|
|
29
|
+
* tree, the disk's own case) and the HOPS of its leaf's symlink chain. {@link readPathViews} is the one producer of
|
|
30
|
+
* those views — the tool's own resolution and its deny judgment read it, and so does {@link readCallTarget}, which
|
|
31
|
+
* a Runner stamp point calls once per adjudicated call and hands to every rule lane as
|
|
32
|
+
* `ToolCallRequest.targetReading`. Every rule lane reads a call's target through ONE reader, {@link callTargetViewsOf}:
|
|
33
|
+
* the reading's views when the call carries one, else the call's own lexical view (a bare request). The judgment over
|
|
34
|
+
* those views is the deny tables' (`pathRowsReach`): any view a deny or ask row reaches is reached; an allow row admits a
|
|
35
|
+
* call only when it admits every view of the declared reading.
|
|
26
36
|
*
|
|
27
37
|
* ## What it does NOT decide
|
|
28
38
|
*
|
|
@@ -35,7 +45,9 @@
|
|
|
35
45
|
* rule family reaches it, and the rule grammar is derived from the catalog's path targets), so it has no
|
|
36
46
|
* declaration to read; the day it declares one, it reads the same selector as everything else.
|
|
37
47
|
*/
|
|
38
|
-
import { type PathFamily, type PathIdentity, type Win32NamespaceRefusal } from "../tools/fs/safety.js";
|
|
48
|
+
import { canonicalizeTarget, type PathFamily, type PathIdentity, type Win32NamespaceRefusal } from "../tools/fs/safety.js";
|
|
49
|
+
import type { TargetViews } from "../tools/fs/read-deny.js";
|
|
50
|
+
import type { ExecutionEnv } from "../internal/harness-types.js";
|
|
39
51
|
import type { ToolCallPathTarget } from "./tool-face.js";
|
|
40
52
|
import { type ToolCallLike } from "./tool-registry.js";
|
|
41
53
|
/** The bases of ONE CALL a target resolves against: the task root, and the live tracked working directory
|
|
@@ -114,3 +126,123 @@ export declare function effectivePathSpellingOf(call: ToolCallLike, pt: ToolCall
|
|
|
114
126
|
} | {
|
|
115
127
|
readonly refused: Win32NamespaceRefusal;
|
|
116
128
|
} | undefined;
|
|
129
|
+
/**
|
|
130
|
+
* ONE reading of a call's path slot as VIEWS. `path`: the LEXICAL view (the spelling joined to the selected base and
|
|
131
|
+
* folded — a native spelling of the tree's family), the CANONICAL view when the environment answered (absent ⇒ not asked
|
|
132
|
+
* or no answer: an absent view takes no part in a judgment and is never a refusal) and the HOPS of the leaf's symlink
|
|
133
|
+
* chain when the environment declares one. `unreadable` / `refused` read as in {@link PathTargetReading}.
|
|
134
|
+
*/
|
|
135
|
+
export type CallTargetView = {
|
|
136
|
+
readonly kind: "path";
|
|
137
|
+
readonly lexical: string;
|
|
138
|
+
readonly canonical?: string;
|
|
139
|
+
readonly hops?: readonly string[];
|
|
140
|
+
} | {
|
|
141
|
+
readonly kind: "unreadable";
|
|
142
|
+
readonly reason: string;
|
|
143
|
+
} | {
|
|
144
|
+
readonly kind: "refused";
|
|
145
|
+
readonly reason: Win32NamespaceRefusal;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* A call's TARGET READING: the views of each reading of its path slot — `protective`, the tightening direction's
|
|
149
|
+
* ({@link protectivePathTargetOf}), and `declared`, the allow direction's ({@link declaredPathTargetOf}; absent on a
|
|
150
|
+
* request that carries no face). It carries no root and no cwd: the call's own `root` / `cwd` are the only copies.
|
|
151
|
+
*/
|
|
152
|
+
export interface CallTargetReading {
|
|
153
|
+
readonly protective?: CallTargetView;
|
|
154
|
+
readonly declared?: CallTargetView;
|
|
155
|
+
/** Where each location on the way to a rule row's head that this leg's lanes hold LEADS in the leg's environment (a
|
|
156
|
+
* link, a firmlink, an alias of the tree) — the rows' twins are judged there too. A fact of the judging leg, taken
|
|
157
|
+
* once per location per leg; absent when no row head leads elsewhere. */
|
|
158
|
+
readonly leads?: ReadonlyMap<string, string>;
|
|
159
|
+
}
|
|
160
|
+
/** The environment's answer the canonical view rests on — the throat's own, so the tool keeps reading the refusal it gave. */
|
|
161
|
+
export type CanonicalAnswer = Awaited<ReturnType<typeof canonicalizeTarget>>;
|
|
162
|
+
/** The three views of one spelling as {@link readPathViews} produces them. */
|
|
163
|
+
export interface PathViews {
|
|
164
|
+
readonly lexical: PathTargetReading;
|
|
165
|
+
/** `undefined` ⇒ the environment was not asked (none supplied, or a spelling this reading cannot locate). */
|
|
166
|
+
readonly canonical?: CanonicalAnswer;
|
|
167
|
+
readonly hops: readonly string[];
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The ONE producer of a target's views: `spelled` against `base` (the base the tool's declaration selected), under the
|
|
171
|
+
* tree `bases` names. Lexical through the same resolution {@link effectivePathTargetOf} reads; canonical through the
|
|
172
|
+
* throat's own canonicalizer; the hops (when `opts.hops` and the canonical view answered) through the leaf's chain. The
|
|
173
|
+
* tool's resolution and deny judgment read this, and so does {@link readCallTarget}; an environment that THROWS
|
|
174
|
+
* propagates here (each caller holds its own posture: the tool refuses, the reading leaves the view absent).
|
|
175
|
+
*/
|
|
176
|
+
export declare function readPathViews(env: ExecutionEnv | undefined, spelled: string, base: string | undefined, bases: ToolPathBases, signal?: AbortSignal, opts?: {
|
|
177
|
+
readonly hops?: boolean;
|
|
178
|
+
}): Promise<PathViews>;
|
|
179
|
+
/** The identity of an ABSOLUTE spelling under the tree `bases` names — the target side's one prelude (family, namespace
|
|
180
|
+
* screen, identity) for a path a judge already holds (a canonical key, a hop, a lexical view). `undefined` when the
|
|
181
|
+
* spelling is not an absolute path of that tree. */
|
|
182
|
+
export declare function absoluteTargetIdentityOf(spelled: string, bases: ToolPathBases): PathIdentity | undefined;
|
|
183
|
+
/**
|
|
184
|
+
* A call's target reading under ONE call's facts: its `root` / `cwd` (the bases the stamp point reads off the leg) and
|
|
185
|
+
* the execution environment `env` (absent ⇒ the lexical views alone — the pre-claim recheck, which has no environment).
|
|
186
|
+
* A canonical view the environment cannot answer (or throws on) is absent, never a refusal (an absent view does not vote).
|
|
187
|
+
* The two readings share their views when they select the same spelling against the same base (one canonicalization).
|
|
188
|
+
*/
|
|
189
|
+
export declare function readCallTarget(env: ExecutionEnv | undefined, call: ToolCallLike & {
|
|
190
|
+
readonly root?: string | undefined;
|
|
191
|
+
readonly cwd?: string | undefined;
|
|
192
|
+
}, signal?: AbortSignal, leads?: ReadonlyMap<string, string | undefined>): Promise<CallTargetReading>;
|
|
193
|
+
/**
|
|
194
|
+
* `req` with its arguments replaced by `args` and — when `req` carries a reading of its own args — a reading of the NEW
|
|
195
|
+
* args taken the same way (same environment, same root / cwd, same face). Every seat that rewrites a request's arguments
|
|
196
|
+
* and hands it on (a composition's rewrite cascade, a re-judgment of the final or an edited form) goes through here, so the
|
|
197
|
+
* next layer never judges a request its reading does not describe. `signal` is the CURRENT call's: a reading never carries
|
|
198
|
+
* one call's cancellation into another's. `undefined` ⇒ nothing to re-read: use `{ ...req, args }`.
|
|
199
|
+
*/
|
|
200
|
+
export declare function withArgsReading<R extends {
|
|
201
|
+
readonly args: unknown;
|
|
202
|
+
readonly targetReading?: CallTargetReading | undefined;
|
|
203
|
+
}>(req: R, args: unknown, signal: AbortSignal | undefined): Promise<R> | undefined;
|
|
204
|
+
/** The request's target reading, when it describes the request's own `args` (the object it was taken for); otherwise
|
|
205
|
+
* nothing — a rule lane then judges the lexical view it always judged. Every rule lane reads the reading through here. */
|
|
206
|
+
export declare function callTargetReadingOf(req: {
|
|
207
|
+
readonly args: unknown;
|
|
208
|
+
readonly targetReading?: CallTargetReading | undefined;
|
|
209
|
+
}): CallTargetReading | undefined;
|
|
210
|
+
/**
|
|
211
|
+
* THE one reader of a call's target for every rule lane, in one direction's reading (`tighten` = the protective reading,
|
|
212
|
+
* `allow` = the declared one): the views of the call's target reading when it carries one of its own args (lexical,
|
|
213
|
+
* canonical, the leaf chain's hops), else the call's own lexical view — resolved against the call's `root` / `cwd` when it
|
|
214
|
+
* carries either, else against `bare` (a host's direct call of a policy: the bases it declared). `undefined` = the call
|
|
215
|
+
* names no path in this reading (or a spelling the throat refuses — no rule speaks about it); an `unreadable` answer =
|
|
216
|
+
* the call names a path this reading cannot locate.
|
|
217
|
+
*/
|
|
218
|
+
export declare function callTargetViewsOf(req: ToolCallLike & {
|
|
219
|
+
readonly root?: string | undefined;
|
|
220
|
+
readonly cwd?: string | undefined;
|
|
221
|
+
readonly targetReading?: CallTargetReading | undefined;
|
|
222
|
+
}, direction: "tighten" | "allow", bare?: ToolPathBases): TargetViews | undefined;
|
|
223
|
+
/**
|
|
224
|
+
* A leg's STAMP seat: `req` with a target reading of its own args, or `undefined` when there is nothing to stamp — the
|
|
225
|
+
* leg's lanes hold no path rule, the request already carries a reading of its args, or its args are not an object. The
|
|
226
|
+
* reading is taken under the request's own `root` / `cwd` when it carries them, else the leg's (the same values every
|
|
227
|
+
* Runner seat stamps), and memoized per args object within one call (its `toolCallId`), so every lane reading one call
|
|
228
|
+
* costs one reading. `undefined`
|
|
229
|
+
* rather than a resolved request, so a seat with nothing to do adds no suspension point.
|
|
230
|
+
*/
|
|
231
|
+
export type CallTargetStamp = <R extends ToolCallLike & {
|
|
232
|
+
readonly toolCallId?: string;
|
|
233
|
+
readonly root?: string | undefined;
|
|
234
|
+
readonly cwd?: string | undefined;
|
|
235
|
+
readonly targetReading?: CallTargetReading | undefined;
|
|
236
|
+
}>(req: R, signal?: AbortSignal) => Promise<R & {
|
|
237
|
+
readonly targetReading: CallTargetReading;
|
|
238
|
+
}> | undefined;
|
|
239
|
+
/** The stamp seat of one leg: its execution environment, its root and live working directory, whether its lanes hold a
|
|
240
|
+
* path rule (read per call — an activated skill can add one), and where the heads of the rows its lanes hold lead in its
|
|
241
|
+
* environment (resolved once per location per leg). */
|
|
242
|
+
export declare function legCallTargetStamp(leg: {
|
|
243
|
+
readonly env: ExecutionEnv;
|
|
244
|
+
readonly root: string;
|
|
245
|
+
readonly cwd: () => string | undefined;
|
|
246
|
+
readonly judgesPaths: () => boolean;
|
|
247
|
+
readonly leads: () => Promise<ReadonlyMap<string, string | undefined>>;
|
|
248
|
+
}): CallTargetStamp;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { isAbsoluteForFamily, joinForFamily, pathFamilyOf, pathIdentityOf, win32NamespaceScreen } from "../tools/fs/safety.js";
|
|
2
|
-
import { pathTargetValue } from "./tool-registry.js";
|
|
1
|
+
import { canonicalizeTarget, isAbsoluteForFamily, joinForFamily, pathFamilyOf, pathIdentityOf, spellingOfIdentity, win32NamespaceScreen } from "../tools/fs/safety.js";
|
|
2
|
+
import { declaredPathTargetOf, pathTargetValue, protectivePathTargetOf } from "./tool-registry.js";
|
|
3
|
+
import { symlinkChainHops } from "./path-identity.js";
|
|
4
|
+
import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, withGateTimeout } from "./gate-bounded-await.js";
|
|
3
5
|
export function pathTargetBaseOf(pt, bases) {
|
|
4
6
|
return (pt?.base ?? "cwd") === "root" ? bases.root : (bases.cwd ?? bases.root);
|
|
5
7
|
}
|
|
@@ -73,3 +75,109 @@ function readingOf(r) {
|
|
|
73
75
|
function unreadableBase(base) {
|
|
74
76
|
return { kind: "unreadable", reason: base === undefined ? "the call's path is relative to a base that was not supplied" : `the base this call resolves against (${JSON.stringify(base)}) is not a fully-qualified path of this tree's family` };
|
|
75
77
|
}
|
|
78
|
+
export async function readPathViews(env, spelled, base, bases, signal, opts) {
|
|
79
|
+
const lexical = resolveSpelling(spelled, base, bases);
|
|
80
|
+
const family = pathFamilyOf(bases);
|
|
81
|
+
if (env === undefined || family === undefined || lexical?.kind === "unreadable")
|
|
82
|
+
return { lexical, hops: [] };
|
|
83
|
+
const canonical = await canonicalizeTarget(env, spelled, signal, family, base);
|
|
84
|
+
const climbs = (s) => s !== undefined && (family === "win" ? /(^|[\\/])\.\.([\\/]|$)/ : /(^|\/)\.\.(\/|$)/).test(s);
|
|
85
|
+
const followsALink = canonical.ok && (climbs(spelled) || climbs(base) || lexical?.kind !== "path" || absoluteTargetIdentityOf(canonical.key, bases) !== lexical.identity);
|
|
86
|
+
const hops = opts?.hops === true && followsALink ? await withGateTimeout(symlinkChainHops(env, spelled, signal, family, base), DEFAULT_GATE_AWAIT_TIMEOUT_MS, signal, "canonicalChain").catch(() => []) : [];
|
|
87
|
+
return { lexical, canonical, hops };
|
|
88
|
+
}
|
|
89
|
+
export function absoluteTargetIdentityOf(spelled, bases) {
|
|
90
|
+
const r = pathIdentityOf(spelled, pathFamilyOf(bases), bases.root ?? bases.cwd);
|
|
91
|
+
return r.ok ? r.identity : undefined;
|
|
92
|
+
}
|
|
93
|
+
const READ_FOR = new WeakMap();
|
|
94
|
+
const REREAD = new WeakMap();
|
|
95
|
+
export async function readCallTarget(env, call, signal, leads) {
|
|
96
|
+
const bases = { ...(call.root !== undefined ? { root: call.root } : {}), ...(call.cwd !== undefined ? { cwd: call.cwd } : {}) };
|
|
97
|
+
const family = pathFamilyOf(bases);
|
|
98
|
+
const views = new Map();
|
|
99
|
+
const viewOf = (pt) => {
|
|
100
|
+
const sel = effectivePathSpellingOf(call, pt, bases);
|
|
101
|
+
if (sel === undefined)
|
|
102
|
+
return Promise.resolve(undefined);
|
|
103
|
+
if ("refused" in sel)
|
|
104
|
+
return Promise.resolve({ kind: "refused", reason: sel.refused });
|
|
105
|
+
const target = sel.spelled !== undefined ? { spelled: sel.spelled, base: sel.base } : sel.base !== undefined && isAbsoluteForFamily(family, sel.base) ? { spelled: sel.base, base: undefined } : undefined;
|
|
106
|
+
if (target === undefined)
|
|
107
|
+
return Promise.resolve(unreadableBase(sel.base));
|
|
108
|
+
const { spelled, base } = target;
|
|
109
|
+
const key = JSON.stringify([spelled, base ?? null]);
|
|
110
|
+
const prior = views.get(key);
|
|
111
|
+
if (prior !== undefined)
|
|
112
|
+
return prior;
|
|
113
|
+
const read = readPathViews(env, spelled, base, bases, signal, { hops: true })
|
|
114
|
+
.catch(() => ({ lexical: resolveSpelling(spelled, base, bases), hops: [] }))
|
|
115
|
+
.then((v) => viewFrom(v, family));
|
|
116
|
+
views.set(key, read);
|
|
117
|
+
return read;
|
|
118
|
+
};
|
|
119
|
+
const protective = await viewOf(protectivePathTargetOf(call));
|
|
120
|
+
const declared = call.face === undefined ? undefined : await viewOf(declaredPathTargetOf(call));
|
|
121
|
+
const led = leads === undefined ? undefined : [...leads].filter((e) => e[1] !== undefined && e[1] !== e[0]);
|
|
122
|
+
const reading = Object.freeze({ ...(protective !== undefined ? { protective } : {}), ...(declared !== undefined ? { declared } : {}), ...(led !== undefined && led.length > 0 ? { leads: new Map(led) } : {}) });
|
|
123
|
+
if (typeof call.args === "object" && call.args !== null)
|
|
124
|
+
READ_FOR.set(reading, call.args);
|
|
125
|
+
REREAD.set(reading, (args, rereadSignal) => readCallTarget(env, { ...call, args }, rereadSignal, leads));
|
|
126
|
+
return reading;
|
|
127
|
+
}
|
|
128
|
+
export function withArgsReading(req, args, signal) {
|
|
129
|
+
const reading = callTargetReadingOf(req);
|
|
130
|
+
const reread = reading === undefined ? undefined : REREAD.get(reading);
|
|
131
|
+
return reread === undefined ? undefined : reread(args, signal).then((targetReading) => ({ ...req, args, targetReading }));
|
|
132
|
+
}
|
|
133
|
+
function viewFrom(v, family) {
|
|
134
|
+
if (v.lexical === undefined)
|
|
135
|
+
return undefined;
|
|
136
|
+
if (v.lexical.kind !== "path")
|
|
137
|
+
return Object.freeze({ ...v.lexical });
|
|
138
|
+
const canonical = v.canonical?.ok === true ? v.canonical.key : undefined;
|
|
139
|
+
return Object.freeze({ kind: "path", lexical: spellingOfIdentity(v.lexical.identity, family), ...(canonical !== undefined ? { canonical } : {}), ...(v.hops.length > 0 ? { hops: Object.freeze([...v.hops]) } : {}) });
|
|
140
|
+
}
|
|
141
|
+
export function callTargetReadingOf(req) {
|
|
142
|
+
const r = req.targetReading;
|
|
143
|
+
return r !== undefined && READ_FOR.get(r) === req.args ? r : undefined;
|
|
144
|
+
}
|
|
145
|
+
export function callTargetViewsOf(req, direction, bare = {}) {
|
|
146
|
+
const reading = callTargetReadingOf(req);
|
|
147
|
+
if (reading !== undefined) {
|
|
148
|
+
const v = direction === "tighten" ? reading.protective : reading.declared;
|
|
149
|
+
if (v === undefined || v.kind === "refused")
|
|
150
|
+
return undefined;
|
|
151
|
+
if (v.kind === "unreadable")
|
|
152
|
+
return { spellings: [], unreadable: v.reason };
|
|
153
|
+
const spellings = [v.lexical];
|
|
154
|
+
for (const s of [v.canonical, ...(v.hops ?? [])])
|
|
155
|
+
if (s !== undefined && !spellings.includes(s))
|
|
156
|
+
spellings.push(s);
|
|
157
|
+
return { spellings };
|
|
158
|
+
}
|
|
159
|
+
const own = req.root !== undefined || req.cwd !== undefined ? { ...(req.root !== undefined ? { root: req.root } : {}), ...(req.cwd !== undefined ? { cwd: req.cwd } : {}) } : bare;
|
|
160
|
+
const target = effectivePathTargetOf(req, direction === "tighten" ? protectivePathTargetOf(req) : declaredPathTargetOf(req), own);
|
|
161
|
+
if (target === undefined || target.kind === "refused")
|
|
162
|
+
return undefined;
|
|
163
|
+
if (target.kind === "unreadable")
|
|
164
|
+
return { spellings: [], unreadable: target.reason };
|
|
165
|
+
return { spellings: [spellingOfIdentity(target.identity, pathFamilyOf(own))] };
|
|
166
|
+
}
|
|
167
|
+
export function legCallTargetStamp(leg) {
|
|
168
|
+
const taken = new WeakMap();
|
|
169
|
+
return (req, signal) => {
|
|
170
|
+
const args = req.args;
|
|
171
|
+
if (typeof args !== "object" || args === null || callTargetReadingOf(req) !== undefined || !leg.judgesPaths())
|
|
172
|
+
return undefined;
|
|
173
|
+
const root = req.root ?? leg.root;
|
|
174
|
+
const cwd = req.cwd ?? leg.cwd();
|
|
175
|
+
const prior = taken.get(args);
|
|
176
|
+
const reading = prior !== undefined && prior.root === root && prior.cwd === cwd && prior.toolName === req.toolName && prior.toolCallId === req.toolCallId && prior.face === req.face
|
|
177
|
+
? prior.reading
|
|
178
|
+
: leg.leads().then((leads) => readCallTarget(leg.env, { toolName: req.toolName, args, ...(req.face !== undefined ? { face: req.face } : {}), root, ...(cwd !== undefined ? { cwd } : {}) }, signal, leads));
|
|
179
|
+
if (prior?.reading !== reading)
|
|
180
|
+
taken.set(args, { root, cwd, toolName: req.toolName, toolCallId: req.toolCallId, face: req.face, reading });
|
|
181
|
+
return reading.then((targetReading) => ({ ...req, targetReading }));
|
|
182
|
+
};
|
|
183
|
+
}
|
|
@@ -368,6 +368,11 @@ export interface EngineNotice {
|
|
|
368
368
|
* (one call's own rule check — every later call reads again, so the rows apply as soon as a read succeeds). Once
|
|
369
369
|
* per run, whichever read fails first; the trace keeps every read's line (`permission.rule_store_unreadable`).
|
|
370
370
|
* `detail: { at, sessionId, runId }`. Audience `user`.
|
|
371
|
+
* - `"permission.rule_row_dropped"` — the person's store served project-scoped rows whose scope root is not a
|
|
372
|
+
* complete absolute path (only a foreign writer or a hand edit makes one; every engine write face refuses it): they
|
|
373
|
+
* take no part in this run's checks — a deny or ask row among them protects nothing — until they are rewritten or
|
|
374
|
+
* removed. Once per run, by whichever read met them first. `detail: { rows, count, sessionId, runId }` — `rows`
|
|
375
|
+
* the first eight `{ rule, scopeRoot }` (each bounded), `count` all of them. Audience `user`.
|
|
371
376
|
* - `"config.durable_gate_unavailable"` — a durable-approval MANDATE is in force on a leg with NO
|
|
372
377
|
* checkpoint store: the per-principal `forceDurableGate` entitlement, or the task's own
|
|
373
378
|
* `durableApproval` opt-in with no live approver seat (`durableMandateSourceOf`, the predicate the
|
|
@@ -829,11 +834,14 @@ export interface EngineNotice {
|
|
|
829
834
|
* boot; deletes in it will fail until the operator restores the bit. Audience `"operator"`; `detail: { layers,
|
|
830
835
|
* remedy }` — layer directory names and the one command. No session. Once per boot.
|
|
831
836
|
* - `"server.model_unreachable"` — a model call of this leg ended in error and its last request failed to
|
|
832
|
-
* connect. Audience `"user"`; `detail: { code, targetHost, remedy, sessionId, proxyInUse? }` — `code`
|
|
833
|
-
* `UND_ERR_CONNECT_TIMEOUT` / `ECONNREFUSED` / `ENOTFOUND` / `EAI_AGAIN` / `EHOSTUNREACH` / `ENETUNREACH`;
|
|
837
|
+
* connect. Audience `"user"`; `detail: { code, targetHost, remedy, sessionId, taskId?, proxyInUse? }` — `code`
|
|
838
|
+
* one of `UND_ERR_CONNECT_TIMEOUT` / `ECONNREFUSED` / `ENOTFOUND` / `EAI_AGAIN` / `EHOSTUNREACH` / `ENETUNREACH`;
|
|
834
839
|
* `targetHost` the model endpoint's `host:port`; `remedy` the constant `"check VPN / proxy; restart if the
|
|
835
|
-
* proxy changed since boot"`; `
|
|
836
|
-
*
|
|
840
|
+
* proxy changed since boot"`; `taskId` (server 7.121.0+) the run resource id of the leg this notice belongs to —
|
|
841
|
+
* absent on a streaming leg that has no run store, and the SAME value on a resumed leg and its original (a
|
|
842
|
+
* per-leg dedup folds the two into one line); `proxyInUse` present only when that request went through a proxy,
|
|
843
|
+
* its value the variable's name (`HTTP_PROXY` or `HTTPS_PROXY`), never the proxy address. No `runId`. Once per
|
|
844
|
+
* leg per target.
|
|
837
845
|
*
|
|
838
846
|
* Deliberately NOT a notice family: brain retry/reconnect liveness (a rate limit, a 5xx, a
|
|
839
847
|
* transient network failure being retried). Those are per-attempt liveness frames with their own
|
package/dist/core/gate-fold.js
CHANGED
|
@@ -2,7 +2,7 @@ import { inlineUntrusted } from "./untrusted-text.js";
|
|
|
2
2
|
import { markTightenMessage } from "./safety-axis-vocab.js";
|
|
3
3
|
import { PROBE_REASON_MAX, normalizeProbeCause } from "./checkpoint-store.js";
|
|
4
4
|
import { normalizeReadRootCandidate } from "./read-root-candidate.js";
|
|
5
|
-
import {} from "./runner/gate-exit.js";
|
|
5
|
+
import { passAskFace } from "./runner/gate-exit.js";
|
|
6
6
|
import { traceHookCrash } from "./gate-crash-trace.js";
|
|
7
7
|
import { writeProtectionMandated, writeProtectionTightenDecision, writeProtectionUnresolved } from "./write-protect.js";
|
|
8
8
|
import { createGateDeadline, awaitWriteProtectionJudgment, gateWaitFailureOf, withGateTimeout } from "./gate-bounded-await.js";
|
|
@@ -13,6 +13,9 @@ export async function runGateFold(pass) {
|
|
|
13
13
|
const { input, toolName, toolCallId, callSignal, ledger, notifier, adjudicate } = pass;
|
|
14
14
|
const boundedAwaitDeadlineMs = createGateDeadline(input, notifier);
|
|
15
15
|
pass.req = { toolName, args: pass.currentInput, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) };
|
|
16
|
+
const stamped = input.stampCallTarget?.(pass.req, callSignal ?? input.abortSignal);
|
|
17
|
+
if (stamped !== undefined)
|
|
18
|
+
pass.req = await stamped;
|
|
16
19
|
pass.decision = adjudicate ? refuseOutOfContractDecision(await (callSignal !== undefined ? adjudicate(pass.req, callSignal) : adjudicate(pass.req))) : { action: "allow" };
|
|
17
20
|
{
|
|
18
21
|
const refusedBy = ledger.fromDecision(pass.decision);
|
|
@@ -164,7 +167,7 @@ export async function runGateFold(pass) {
|
|
|
164
167
|
pass.writeProtected = true;
|
|
165
168
|
if (verdict !== undefined && writeProtectionUnresolved(verdict))
|
|
166
169
|
pass.writeProtectionUnresolved = true;
|
|
167
|
-
const decision = verdict === undefined ? undefined : writeProtectionTightenDecision(toolName, verdict);
|
|
170
|
+
const decision = verdict === undefined ? undefined : writeProtectionTightenDecision(toolName, verdict, passAskFace(pass));
|
|
168
171
|
if (decision !== undefined && writeProtectionStanding === "allow") {
|
|
169
172
|
pass.decision = decision;
|
|
170
173
|
if (decision.action === "ask")
|