@sema-agent/core 7.44.0 → 7.46.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 +418 -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 +65 -38
- package/dist/agents/tool-filter.d.ts +82 -5
- package/dist/agents/tool-filter.js +134 -4
- package/dist/core/agent-config-dir.d.ts +20 -0
- package/dist/core/agent-config-dir.js +49 -0
- package/dist/core/agent-definition.d.ts +2 -2
- package/dist/core/allow-entry.d.ts +30 -4
- package/dist/core/allow-entry.js +32 -5
- package/dist/core/ask-origin.d.ts +23 -4
- package/dist/core/ask-origin.js +11 -5
- package/dist/core/checkpoint-store.d.ts +41 -4
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/effective-path-target.d.ts +143 -10
- package/dist/core/effective-path-target.js +113 -9
- package/dist/core/engine-notice.d.ts +25 -3
- package/dist/core/engine-notice.js +12 -6
- package/dist/core/gate-fold.js +3 -0
- package/dist/core/gate-lanes.js +11 -3
- package/dist/core/gate-outcome.d.ts +5 -0
- package/dist/core/gate-outcome.js +2 -0
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +5 -0
- package/dist/core/memory-engine/content-origin.d.ts +0 -34
- package/dist/core/memory-engine/content-origin.js +9 -4
- package/dist/core/memory-engine/layout.d.ts +5 -1
- package/dist/core/memory-engine/layout.js +1 -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 +47 -18
- package/dist/core/permission-rule-model.js +38 -42
- 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 +22 -36
- package/dist/core/permission-rules.js +50 -40
- package/dist/core/real-approval.d.ts +9 -6
- package/dist/core/real-approval.js +5 -5
- package/dist/core/runner/child-tree.d.ts +110 -0
- package/dist/core/runner/child-tree.js +107 -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 +54 -2
- 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 +6 -0
- package/dist/core/runner/prepare-ask-lane.js +4 -2
- package/dist/core/runner/prepare-config-doors.js +3 -1
- package/dist/core/runner/prepare-execution-env.d.ts +5 -2
- package/dist/core/runner/prepare-execution-env.js +11 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +6 -0
- package/dist/core/runner/prepare-gate-stations.js +3 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
- package/dist/core/runner/prepare-hands-readface.js +6 -6
- 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-listings.d.ts +4 -4
- package/dist/core/runner/prepare-listings.js +2 -22
- package/dist/core/runner/prepare-park-ask.d.ts +5 -0
- package/dist/core/runner/prepare-park-ask.js +6 -4
- package/dist/core/runner/prepare-policy-chain.d.ts +16 -2
- package/dist/core/runner/prepare-policy-chain.js +14 -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 +41 -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/resume-policy-outcome.js +2 -1
- package/dist/core/runner/run-leg.js +5 -1
- package/dist/core/runner/run-notification-lane.js +2 -2
- package/dist/core/runner/runtask.d.ts +2 -0
- package/dist/core/runner/runtask.js +4 -2
- package/dist/core/runner/stream-steer-verb.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +32 -0
- package/dist/core/runner/sub-agent-deny-lists.js +121 -0
- package/dist/core/runner/synthetic-tools.d.ts +1 -1
- package/dist/core/runner/synthetic-tools.js +3 -2
- 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/session-policy-store.d.ts +6 -2
- package/dist/core/session-policy-store.js +2 -1
- 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/skill-grant.d.ts +10 -2
- package/dist/core/skill-grant.js +20 -4
- package/dist/core/synthetic-read-verdict.d.ts +3 -0
- package/dist/core/synthetic-read-verdict.js +3 -1
- package/dist/core/task-notification.d.ts +12 -1
- package/dist/core/task-notification.js +13 -1
- package/dist/core/task-spec.d.ts +5 -3
- package/dist/core/tool-catalog-entries.d.ts +21 -0
- package/dist/core/tool-catalog-entries.js +9 -0
- package/dist/core/tool-policy.d.ts +45 -12
- package/dist/core/tool-policy.js +18 -20
- package/dist/core/tool-spec.d.ts +23 -4
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/write-protect.d.ts +4 -0
- package/dist/core/write-protect.js +30 -9
- package/dist/index.d.ts +10 -6
- package/dist/index.js +9 -5
- package/dist/orchestration/run-spec.js +8 -7
- package/dist/orchestration/workflow.js +3 -2
- package/dist/shell/lex.d.ts +7 -0
- package/dist/shell/lex.js +17 -0
- package/dist/tools/fs/bash-program-position.d.ts +8 -87
- package/dist/tools/fs/bash-program-position.js +33 -69
- 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 +694 -0
- package/dist/tools/fs/program-read-model.js +1252 -0
- package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
- package/dist/tools/fs/program-read-model.rows.js +613 -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 +83 -0
- package/dist/tools/fs/removal-targets.js +236 -0
- 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 +85 -0
- package/dist/tools/fs/shell-read-argv.js +386 -0
- package/dist/tools/fs/shell-read-bindings.d.ts +273 -0
- package/dist/tools/fs/shell-read-bindings.js +746 -0
- package/dist/tools/fs/shell-read-check.js +23 -15
- package/dist/tools/fs/shell-read-establish.js +79 -5
- 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 +31 -7
- package/dist/tools/fs/shell-read-plan.js +299 -393
- package/dist/tools/fs/shell-read-word.d.ts +29 -26
- package/dist/tools/fs/shell-read-word.js +30 -38
- package/dist/tools/fs/shell-read-write-targets.d.ts +15 -37
- package/dist/tools/fs/shell-read-write-targets.js +83 -136
- package/dist/tools/fs/shell-reentry-text.d.ts +38 -6
- package/dist/tools/fs/shell-reentry-text.js +90 -53
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +49 -3
- package/wire-changes/core@7.45.0.json +1084 -0
- package/wire-changes/core@7.46.0.json +1522 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GateOutcome } from "./gate-outcome.js";
|
|
1
|
+
import type { GateOutcome, HostDecider } from "./gate-outcome.js";
|
|
2
2
|
import { type ExecutionOutcomeRecordWord } from "./checkpoint-execution-record.js";
|
|
3
3
|
import type { CheckpointPause } from "./pause-registry.js";
|
|
4
4
|
import type { SystemInjectionPriority } from "./task-notification.js";
|
|
@@ -625,7 +625,7 @@ export type ResumeOutcome = {
|
|
|
625
625
|
* authorization; a replay states its own facts.
|
|
626
626
|
*/
|
|
627
627
|
hostDecision: {
|
|
628
|
-
decidedBy:
|
|
628
|
+
decidedBy: HostDecider;
|
|
629
629
|
approver?: string;
|
|
630
630
|
};
|
|
631
631
|
/**
|
|
@@ -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
|
|
@@ -11,7 +11,7 @@ export const refusedOutOfContract = (message) => {
|
|
|
11
11
|
export const isEngineRefusal = (d) => typeof d === "object" && d !== null && ENGINE_REFUSALS.has(d);
|
|
12
12
|
const DECISION_REASONS = Object.freeze(["rule", "mode", "hook", "safety", "classifier", "persisted_rule", "sandbox", "org_rule", "org_unavailable", "read_only", "skill", "deployment_rule", "request_rule"]);
|
|
13
13
|
const DECISION_ACTIONS = Object.keys({ allow: 0, deny: 0, ask: 0 });
|
|
14
|
-
const GUARDS = Object.keys({ unverifiable_delete: 0, transcript_write: 0, artifact_shared_read: 0 });
|
|
14
|
+
const GUARDS = Object.keys({ dangerous_delete: 0, unverifiable_delete: 0, transcript_write: 0, artifact_shared_read: 0 });
|
|
15
15
|
const RULE_UNREADABLE_WORDS = Object.keys({ content_deny: 0, content_ask: 0, persisted_call: 0 });
|
|
16
16
|
const AUTHORED_BY_WORDS = Object.keys({ deployment: 0 });
|
|
17
17
|
const memberOf = (words) => {
|
|
@@ -20,22 +20,34 @@
|
|
|
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
|
*
|
|
29
39
|
* Nothing here judges anything: it answers "which absolute path is this call about". Containment, the
|
|
30
40
|
* sensitive-path deny face and the rule precedence walk are unchanged and still run where they ran.
|
|
31
|
-
* `undefined` means "this call
|
|
32
|
-
*
|
|
41
|
+
* `undefined` means "this call names no path" (no declaration, a non-string slot, an absent slot that targets
|
|
42
|
+
* nothing); a path the call does name whose base this reading lacks is `unreadable` — never a guess, never silence.
|
|
33
43
|
*
|
|
34
44
|
* RepoMap shares the search tools' root-relative `path` semantics but declares NO path target at all (no
|
|
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
|
|
@@ -71,8 +83,8 @@ export declare function splitAbsoluteGlobPattern(pattern: string, family: PathFa
|
|
|
71
83
|
} | undefined;
|
|
72
84
|
/**
|
|
73
85
|
* The reading of a call's path slot under ONE declaration: `path` = the absolute target's identity; `unreadable`
|
|
74
|
-
* = the slot spells a target but the base it needs is not a usable base of
|
|
75
|
-
* only, as the ask a person clears — never to the allow arm); `refused` = a namespace refusal of the
|
|
86
|
+
* = the slot spells a target (or targets its base) but the base it needs is not supplied or not a usable base of
|
|
87
|
+
* this tree (fed to TIGHTENING rows only, as the ask a person clears — never to the allow arm); `refused` = a namespace refusal of the
|
|
76
88
|
* spelling itself (a rule speaks about no such spelling: no verdict, no ask — the fs throat delivers the
|
|
77
89
|
* refusal with the same word); `undefined` = no target in this reading.
|
|
78
90
|
*/
|
|
@@ -97,8 +109,9 @@ export type PathTargetReading = {
|
|
|
97
109
|
* ③ otherwise a `patternParam`'s ABSOLUTE pattern supplies its own base directory;
|
|
98
110
|
* ④ otherwise the base itself (the tool searches all of it).
|
|
99
111
|
*
|
|
100
|
-
* A base the
|
|
101
|
-
*
|
|
112
|
+
* A base the caller did not supply yields `unreadable`: a relative spelling with nothing to resolve against
|
|
113
|
+
* names a file this reading cannot locate — claiming it names `/<spelling>` would be a fabrication, and
|
|
114
|
+
* claiming it names nothing would let every rule about that file stay silent.
|
|
102
115
|
*/
|
|
103
116
|
export declare function effectivePathTargetOf(call: ToolCallLike, pt: ToolCallPathTarget | undefined, bases: ToolPathBases): PathTargetReading;
|
|
104
117
|
/**
|
|
@@ -113,3 +126,123 @@ export declare function effectivePathSpellingOf(call: ToolCallLike, pt: ToolCall
|
|
|
113
126
|
} | {
|
|
114
127
|
readonly refused: Win32NamespaceRefusal;
|
|
115
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
|
}
|
|
@@ -31,10 +33,8 @@ export function effectivePathTargetOf(call, pt, bases) {
|
|
|
31
33
|
if (spelling.spelled !== undefined)
|
|
32
34
|
return resolveSpelling(spelling.spelled, spelling.base, bases);
|
|
33
35
|
const declaredBase = spelling.base;
|
|
34
|
-
if (declaredBase === undefined)
|
|
35
|
-
return undefined;
|
|
36
36
|
const family = pathFamilyOf(bases);
|
|
37
|
-
return isAbsoluteForFamily(family, declaredBase) ? readingOf(pathIdentityOf(declaredBase, family, bases.root ?? bases.cwd)) : unreadableBase(declaredBase);
|
|
37
|
+
return declaredBase !== undefined && isAbsoluteForFamily(family, declaredBase) ? readingOf(pathIdentityOf(declaredBase, family, bases.root ?? bases.cwd)) : unreadableBase(declaredBase);
|
|
38
38
|
}
|
|
39
39
|
export function effectivePathSpellingOf(call, pt, bases) {
|
|
40
40
|
if (pt === undefined)
|
|
@@ -63,9 +63,7 @@ function resolveSpelling(spelled, declaredBase, bases) {
|
|
|
63
63
|
const tree = bases.root ?? bases.cwd;
|
|
64
64
|
if (isAbsoluteForFamily(family, screened.spelled))
|
|
65
65
|
return readingOf(pathIdentityOf(screened.spelled, family, tree));
|
|
66
|
-
if (declaredBase === undefined || family === undefined)
|
|
67
|
-
return undefined;
|
|
68
|
-
if (!isAbsoluteForFamily(family, declaredBase))
|
|
66
|
+
if (declaredBase === undefined || family === undefined || !isAbsoluteForFamily(family, declaredBase))
|
|
69
67
|
return unreadableBase(declaredBase);
|
|
70
68
|
return readingOf(pathIdentityOf(joinForFamily(family, declaredBase, screened.spelled), family, tree));
|
|
71
69
|
}
|
|
@@ -75,5 +73,111 @@ function readingOf(r) {
|
|
|
75
73
|
return r.reason === "relative" || r.reason === "unanchored" ? undefined : { kind: "refused", reason: r.reason };
|
|
76
74
|
}
|
|
77
75
|
function unreadableBase(base) {
|
|
78
|
-
return { kind: "unreadable", reason: `the base this call resolves against (${JSON.stringify(base)}) is not a fully-qualified path of this tree's family` };
|
|
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` };
|
|
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
|
+
};
|
|
79
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
|
|
@@ -394,8 +399,9 @@ export interface EngineNotice {
|
|
|
394
399
|
* frame's two count keys). They are NOT redelivered (a steer aimed at a finished run must not
|
|
395
400
|
* fire at the next one — unlike ENGINE notes, which pend per session); the loud half of the
|
|
396
401
|
* #257 contract's "accepted = enqueued, not consumed" sentence;
|
|
397
|
-
* `detail: {
|
|
398
|
-
*
|
|
402
|
+
* `detail: { <countKey>, cause, taskId?, sessionId?, runId? }` (the `code` and the `countKey` of each family are the
|
|
403
|
+
* table {@link USER_INPUT_UNDRAINED_FAMILIES}, keyed by the lane the input was queued on — `injectionLaneOf` — and the
|
|
404
|
+
* minter writes exactly those; `cause` on both rows, closed `USER_INPUT_UNDRAINED_CAUSES`: `"leg_end"` — this engine's arm, the
|
|
399
405
|
* leg ended with it still owed; `"crashed"` — minted by the SERVING LAYER, not this engine, before the
|
|
400
406
|
* session's next run, for inputs it had accepted whose consumption was not confirmed before the process died: their queue was the
|
|
401
407
|
* serving layer's, so only it can count them, and it picks the family by the LANE the input was queued on (a `later`
|
|
@@ -404,7 +410,7 @@ export interface EngineNotice {
|
|
|
404
410
|
* per-run identity, the key this account is actually about — see
|
|
405
411
|
* {@link undrainedUserInputNotices}; all omitted when the caller has none, never fabricated). A delegated child
|
|
406
412
|
* leg is announced on its PARENT's session as well — the direct parent only — as a second row per family,
|
|
407
|
-
* `detail: {
|
|
413
|
+
* `detail: { <countKey>, cause, taskId?, sessionId: <the parent's>, childSessionId }` (no `runId`: nothing
|
|
408
414
|
* carries the parent's), message tail `(a child leg, <childSessionId>)`; `childSessionId` present = a child reported it.
|
|
409
415
|
* Per-run, at most once per family (the terminal sweep is a single site) — which is why
|
|
410
416
|
* `taskId`/`sessionId` cannot key it: both answer the same value for two runs of one session.
|
|
@@ -971,6 +977,22 @@ export declare function settleOwedUserInput(account: OwedUserInputAccount, rowRe
|
|
|
971
977
|
*/
|
|
972
978
|
export declare const USER_INPUT_UNDRAINED_CAUSES: readonly ["leg_end", "crashed"];
|
|
973
979
|
export type UserInputUndrainedCause = (typeof USER_INPUT_UNDRAINED_CAUSES)[number];
|
|
980
|
+
/**
|
|
981
|
+
* The two families of the stranded-input account, keyed by the lane the input was queued on (`injectionLaneOf` maps a steer's
|
|
982
|
+
* tier to it): the notice `code` the family is announced under, and the `detail` key that carries its count
|
|
983
|
+
* (`detail[countKey]`, beside `cause`). {@link undrainedUserInputNotices} writes its rows with exactly these two spellings, so a
|
|
984
|
+
* serving layer that mints the `crashed` arm picks its family by lane and spells code and count key the way the engine does.
|
|
985
|
+
*/
|
|
986
|
+
export declare const USER_INPUT_UNDRAINED_FAMILIES: Readonly<{
|
|
987
|
+
readonly steer: Readonly<{
|
|
988
|
+
readonly code: "task.user_steer_undrained";
|
|
989
|
+
readonly countKey: "steer";
|
|
990
|
+
}>;
|
|
991
|
+
readonly followUp: Readonly<{
|
|
992
|
+
readonly code: "task.user_followup_undrained";
|
|
993
|
+
readonly countKey: "followUp";
|
|
994
|
+
}>;
|
|
995
|
+
}>;
|
|
974
996
|
/** Where a child leg's account is ALSO announced: its spawner's session, and that run's task id when the spawner carried one
|
|
975
997
|
* (it never carries the run id, and the row never invents one). */
|
|
976
998
|
interface ParentLegIds {
|
|
@@ -52,6 +52,12 @@ export function settleOwedUserInput(account, rowReturned, ids, onNotice) {
|
|
|
52
52
|
}
|
|
53
53
|
const SETTLED_OWED_ACCOUNTS = new WeakSet();
|
|
54
54
|
export const USER_INPUT_UNDRAINED_CAUSES = Object.freeze(["leg_end", "crashed"]);
|
|
55
|
+
const STEER_UNDRAINED = "task.user_steer_undrained";
|
|
56
|
+
const FOLLOWUP_UNDRAINED = "task.user_followup_undrained";
|
|
57
|
+
export const USER_INPUT_UNDRAINED_FAMILIES = Object.freeze({
|
|
58
|
+
steer: Object.freeze({ code: STEER_UNDRAINED, countKey: "steer" }),
|
|
59
|
+
followUp: Object.freeze({ code: FOLLOWUP_UNDRAINED, countKey: "followUp" }),
|
|
60
|
+
});
|
|
55
61
|
export function parentLegIds(internals) {
|
|
56
62
|
const parentSessionId = internals?.parentSessionId;
|
|
57
63
|
if (typeof parentSessionId !== "string" || parentSessionId === "")
|
|
@@ -73,17 +79,17 @@ export function undrainedUserInputNotices(counts, taskId, sessionId, runId, pare
|
|
|
73
79
|
const cause = "leg_end";
|
|
74
80
|
if (counts.steer > 0) {
|
|
75
81
|
const message = `${counts.steer} user steer(s) ${tail}`;
|
|
76
|
-
const count = { steer: counts.steer, cause };
|
|
77
|
-
own.push({ code:
|
|
82
|
+
const count = { [USER_INPUT_UNDRAINED_FAMILIES.steer.countKey]: counts.steer, cause };
|
|
83
|
+
own.push({ code: STEER_UNDRAINED, message, detail: { ...count, ...tid, ...sid, ...rid } });
|
|
78
84
|
if (toParent)
|
|
79
|
-
copies.push({ code:
|
|
85
|
+
copies.push({ code: STEER_UNDRAINED, message: `${message}${copyTail}`, detail: { ...count, ...ptid, sessionId: parentSession, childSessionId: sessionId } });
|
|
80
86
|
}
|
|
81
87
|
if (counts.followUp > 0) {
|
|
82
88
|
const message = `${counts.followUp} user follow-up(s) ${tail}`;
|
|
83
|
-
const count = { followUp: counts.followUp, cause };
|
|
84
|
-
own.push({ code:
|
|
89
|
+
const count = { [USER_INPUT_UNDRAINED_FAMILIES.followUp.countKey]: counts.followUp, cause };
|
|
90
|
+
own.push({ code: FOLLOWUP_UNDRAINED, message, detail: { ...count, ...tid, ...sid, ...rid } });
|
|
85
91
|
if (toParent)
|
|
86
|
-
copies.push({ code:
|
|
92
|
+
copies.push({ code: FOLLOWUP_UNDRAINED, message: `${message}${copyTail}`, detail: { ...count, ...ptid, sessionId: parentSession, childSessionId: sessionId } });
|
|
87
93
|
}
|
|
88
94
|
return [...own, ...copies];
|
|
89
95
|
}
|
package/dist/core/gate-fold.js
CHANGED
|
@@ -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);
|
package/dist/core/gate-lanes.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withArgsReading } from "./effective-path-target.js";
|
|
1
2
|
import { attestClassifierDenyCause, classifierDenyCauseOf, decisionText, refuseOutOfContractDecision } from "./tool-policy.js";
|
|
2
3
|
import { arbitrateByMode, classifyAsk, fileCommandOptionsSentence, persistedRuleMandateOf, ruleClearanceOf, safetyAxisOf, stampAskOrigin } from "./ask-origin.js";
|
|
3
4
|
import { classifierParseFailureDenyMessage, denialLimitFallbackMessage, rungDidNotRule, unarmedWindow } from "./auto-mode.js";
|
|
@@ -40,6 +41,7 @@ export async function runGateLanes(pass) {
|
|
|
40
41
|
writeProtected: pass.writeProtected === true,
|
|
41
42
|
writeProtectionUnresolved: pass.writeProtectionUnresolved === true,
|
|
42
43
|
});
|
|
44
|
+
const readFaceNow = input.readFace?.();
|
|
43
45
|
const askOriginFacts = (org, ruleCallUnreadable) => ({
|
|
44
46
|
contentQuestion: heldBy("lanes.contentQuestionClass", QUESTION_TOOL_NAMES, pass.req),
|
|
45
47
|
markedUnresolvable: markedUnresolvable(),
|
|
@@ -49,6 +51,7 @@ export async function runGateLanes(pass) {
|
|
|
49
51
|
mandateMarks: mandateMarksNow(),
|
|
50
52
|
...(input.toolMarkSources !== undefined ? { toolMarkSources: input.toolMarkSources } : {}),
|
|
51
53
|
...(input.a2aIsolatePeers === true ? { a2aIsolatePeers: true } : {}),
|
|
54
|
+
...(readFaceNow !== undefined ? { readFace: readFaceNow } : {}),
|
|
52
55
|
});
|
|
53
56
|
const classifyStanding = (unstamped, facts) => (pass.askClassification = classifyAsk(unstamped, facts));
|
|
54
57
|
let orgTightenCount = 0;
|
|
@@ -116,7 +119,8 @@ export async function runGateLanes(pass) {
|
|
|
116
119
|
pass.decision = orgAnswer.decision;
|
|
117
120
|
const ruleCallUnreadableOf = (lane) => lane?.unreadable === true && lane.reason !== undefined;
|
|
118
121
|
const readPersistedRules = (req) => readPersistedRuleLane(input.persistedRules, req, input.abortSignal);
|
|
119
|
-
const
|
|
122
|
+
const reread = pass.policyRewrite !== undefined ? withArgsReading(pass.req, pass.policyRewrite, pass.callSignal ?? input.abortSignal) : undefined;
|
|
123
|
+
const laneReq = reread !== undefined ? await reread : { ...pass.req, ...(pass.policyRewrite !== undefined ? { args: pass.policyRewrite } : {}) };
|
|
120
124
|
const laneAnswer = input.persistedRules !== undefined && wireOf("lanes.persistedLaneSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME ? await readPersistedRules(laneReq) : undefined;
|
|
121
125
|
{
|
|
122
126
|
const tightened = applyPersistedTightening(pass.decision, laneAnswer);
|
|
@@ -137,6 +141,8 @@ export async function runGateLanes(pass) {
|
|
|
137
141
|
if (pass.policyRewrite !== undefined) {
|
|
138
142
|
pass.currentInput = pass.policyRewrite;
|
|
139
143
|
pass.req.args = pass.policyRewrite;
|
|
144
|
+
if (laneReq.targetReading !== undefined)
|
|
145
|
+
pass.req.targetReading = laneReq.targetReading;
|
|
140
146
|
}
|
|
141
147
|
const ruleMayClear = () => ruleClearanceOf(pass.decision.action === "ask" ? pass.decision : undefined, mandateMarksNow()).ruleMayClear;
|
|
142
148
|
const classMayClear = () => pass.decision.action === "ask" && classifyAsk(pass.decision, askOriginFacts(orgAnswer.org, ruleCallUnreadableOf(laneAnswer))).row.ruleMayClear;
|
|
@@ -574,8 +580,10 @@ export async function runGateLanes(pass) {
|
|
|
574
580
|
break;
|
|
575
581
|
}
|
|
576
582
|
let editRuleCallUnreadable = false;
|
|
583
|
+
const editReread = withArgsReading(pass.req, editArgs, callSignal ?? input.abortSignal);
|
|
584
|
+
const editLaneReq = editReread !== undefined ? await editReread : { toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) };
|
|
577
585
|
if (input.persistedRules !== undefined && wireOf("lanes.persistedEditSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME) {
|
|
578
|
-
const editLane = await readPersistedRules(
|
|
586
|
+
const editLane = await readPersistedRules(editLaneReq);
|
|
579
587
|
editRuleCallUnreadable = ruleCallUnreadableOf(editLane);
|
|
580
588
|
const editTightened = applyPersistedTightening(recheck, editLane);
|
|
581
589
|
recheck = editTightened.decision;
|
|
@@ -586,7 +594,7 @@ export async function runGateLanes(pass) {
|
|
|
586
594
|
break;
|
|
587
595
|
}
|
|
588
596
|
}
|
|
589
|
-
const editSkillRefusal = skillRowRefusal(input.skillGrants,
|
|
597
|
+
const editSkillRefusal = skillRowRefusal(input.skillGrants, editLaneReq);
|
|
590
598
|
if (editSkillRefusal !== undefined) {
|
|
591
599
|
editDenied = editSkillRefusal;
|
|
592
600
|
pass.deniedBy = "policy";
|
|
@@ -125,6 +125,11 @@ export type SettlementParty = (typeof SETTLEMENT_PARTIES)[number];
|
|
|
125
125
|
export type SettlementPartiesMatchTheRecord = AssertAllKeysHandled<Exclude<Settlement["who"]["party"], SettlementParty> | Exclude<SettlementParty, Settlement["who"]["party"]>>;
|
|
126
126
|
/** Membership test for {@link SettlementParty}. */
|
|
127
127
|
export declare function isSettlementParty(v: unknown): v is SettlementParty;
|
|
128
|
+
/** Who REPORTED a durable decide (`hostDecision.decidedBy`): a person, the host's own SLA sweep at the row's deadline, or the
|
|
129
|
+
* deployment's policy — the closed set; a host's decide door mirrors the words it accepts from this one. */
|
|
130
|
+
export declare const HOST_DECIDERS: readonly ["person", "sla_timeout", "policy"];
|
|
131
|
+
export type HostDecider = (typeof HOST_DECIDERS)[number];
|
|
132
|
+
export declare const isHostDecider: (word: unknown) => word is HostDecider;
|
|
128
133
|
/** The DECIDER arms of {@link Settlement} — a person's and the deployment policy's, the two parties that
|
|
129
134
|
* answer an ask either way. Named so a site that both composes one and quotes its note can read the seat
|
|
130
135
|
* off the record instead of re-deriving it. */
|
|
@@ -25,6 +25,8 @@ const SETTLEMENT_PARTY_SET = new Set(SETTLEMENT_PARTIES);
|
|
|
25
25
|
export function isSettlementParty(v) {
|
|
26
26
|
return SETTLEMENT_PARTY_SET.has(v);
|
|
27
27
|
}
|
|
28
|
+
export const HOST_DECIDERS = Object.freeze(["person", "sla_timeout", "policy"]);
|
|
29
|
+
export const isHostDecider = (word) => HOST_DECIDERS.some((w) => w === word);
|
|
28
30
|
export function decidersNote(text) {
|
|
29
31
|
return text === "" ? undefined : text;
|
|
30
32
|
}
|