@sema-agent/core 7.50.2 → 7.51.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 +263 -0
- package/dist/agents/child-failure.d.ts +5 -16
- package/dist/agents/child-failure.js +4 -24
- package/dist/agents/subagent-steps.js +2 -2
- package/dist/agents/subagent.d.ts +4 -13
- package/dist/agents/subagent.js +94 -204
- package/dist/agents/tool-filter.d.ts +2 -2
- package/dist/brain/anthropic.d.ts +1 -1
- package/dist/brain/anthropic.js +7 -5
- package/dist/brain/open-responses.d.ts +1 -1
- package/dist/brain/open-responses.js +3 -2
- package/dist/brain/openai.d.ts +1 -1
- package/dist/brain/openai.js +3 -2
- package/dist/brain/request-route.d.ts +2 -2
- package/dist/brain/route-adjudicator.d.ts +16 -7
- package/dist/brain/route-adjudicator.js +23 -7
- package/dist/brain/route-conformance.js +23 -5
- package/dist/brain/stream-engine.js +9 -3
- package/dist/core/a2a.js +6 -8
- package/dist/core/allow-entry.d.ts +45 -26
- package/dist/core/allow-entry.js +48 -24
- package/dist/core/arg-summary.d.ts +8 -85
- package/dist/core/arg-summary.js +4 -386
- package/dist/core/ask-origin.d.ts +35 -33
- package/dist/core/ask-origin.js +9 -11
- package/dist/core/checkpoint-requirements.d.ts +6 -0
- package/dist/core/checkpoint-requirements.js +1 -0
- package/dist/core/checkpoint-store.d.ts +15 -34
- package/dist/core/checkpoint-store.js +5 -4
- package/dist/core/classifier-unavailable-arm.d.ts +12 -8
- package/dist/core/classifier-unavailable-arm.js +7 -2
- package/dist/core/credential-vocabulary.d.ts +3 -2
- package/dist/core/credential-vocabulary.js +27 -18
- package/dist/core/delegation-frames.d.ts +40 -15
- package/dist/core/delegation-frames.js +34 -0
- package/dist/core/engine-notice.d.ts +35 -11
- package/dist/core/gate-fold.js +9 -7
- package/dist/core/gate-lanes.js +6 -4
- package/dist/core/gate-outcome.d.ts +81 -15
- package/dist/core/gate-outcome.js +168 -67
- package/dist/core/governance-codes.d.ts +7 -1
- package/dist/core/governance-codes.js +9 -0
- package/dist/core/hooks.d.ts +4 -4
- package/dist/core/hooks.js +4 -3
- package/dist/core/mcp-connection-pool.d.ts +2 -0
- package/dist/core/mcp-connection-pool.js +2 -1
- package/dist/core/mcp-failure.js +2 -1
- package/dist/core/mcp-server-spec.d.ts +27 -5
- package/dist/core/mcp.d.ts +13 -38
- package/dist/core/mcp.js +28 -23
- package/dist/core/media-byte-cap.d.ts +25 -0
- package/dist/core/media-byte-cap.js +18 -2
- package/dist/core/memory-engine/consolidation-driver.js +1 -1
- package/dist/core/mode-policy.d.ts +25 -1
- package/dist/core/mode-policy.js +28 -13
- package/dist/core/own-property-receipt.d.ts +18 -0
- package/dist/core/own-property-receipt.js +39 -0
- package/dist/core/permission-rule-model.d.ts +32 -11
- package/dist/core/permission-rule-model.js +12 -7
- package/dist/core/persisted-rule-arms.d.ts +3 -3
- package/dist/core/prompt-section-seat.d.ts +54 -0
- package/dist/core/prompt-section-seat.js +49 -0
- package/dist/core/protocol-http-auth.d.ts +44 -0
- package/dist/core/protocol-http-auth.js +45 -0
- package/dist/core/read-face-seats.d.ts +30 -0
- package/dist/core/read-face-seats.js +38 -0
- package/dist/core/redaction-passes.d.ts +82 -0
- package/dist/core/redaction-passes.js +378 -0
- package/dist/core/remote-env.d.ts +4 -3
- package/dist/core/resume-startup-cause.d.ts +56 -0
- package/dist/core/resume-startup-cause.js +43 -0
- package/dist/core/rule-denial-sentence.d.ts +2 -2
- package/dist/core/runner/announce-once-ledger.js +2 -2
- package/dist/core/runner/classifier-ladder.d.ts +17 -13
- package/dist/core/runner/classifier-ladder.js +12 -10
- package/dist/core/runner/construction-doors.js +9 -20
- package/dist/core/runner/contracts.d.ts +4 -4
- package/dist/core/runner/gate-exit.js +2 -2
- package/dist/core/runner/memory-consolidation-write-judge.d.ts +53 -9
- package/dist/core/runner/memory-consolidation-write-judge.js +39 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +8 -13
- package/dist/core/runner/person-path-rows.d.ts +4 -3
- package/dist/core/runner/policy-throw.js +4 -4
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
- package/dist/core/runner/prepare-caps-and-workflow.js +8 -6
- package/dist/core/runner/prepare-gate-stations.js +4 -4
- package/dist/core/runner/prepare-hands-readface.js +1 -19
- package/dist/core/runner/prepare-inherited-gate.js +10 -9
- package/dist/core/runner/prepare-policy-chain.d.ts +3 -5
- package/dist/core/runner/prepare-policy-chain.js +10 -13
- package/dist/core/runner/prepare-project-context.d.ts +4 -4
- package/dist/core/runner/prepare-project-context.js +2 -1
- package/dist/core/runner/prepare-prompt-assembly.js +1 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +1 -1
- package/dist/core/runner/prepare-prompt-inputs.js +4 -4
- package/dist/core/runner/prepare-protocol-tools.d.ts +4 -3
- package/dist/core/runner/prepare-protocol-tools.js +3 -2
- package/dist/core/runner/prepare-task.js +6 -4
- package/dist/core/runner/prepare-turn-wiring.d.ts +16 -13
- package/dist/core/runner/prepare-turn-wiring.js +8 -7
- package/dist/core/runner/run-harness-handlers.js +2 -2
- package/dist/core/runner/run-leg.js +10 -9
- package/dist/core/runner/run-settle-and-teardown.js +5 -5
- package/dist/core/runner/run-turn-boundary.js +3 -2
- package/dist/core/runner/runtask.d.ts +1 -2
- package/dist/core/runner/runtask.js +8 -12
- package/dist/core/runner/session-rule-policy.d.ts +15 -11
- package/dist/core/runner/session-rule-policy.js +43 -33
- package/dist/core/runner/stream-lifecycle-verbs.js +2 -2
- package/dist/core/runner/stream-settle-backstop.js +2 -1
- package/dist/core/runner/sub-agent-deny-lists.d.ts +12 -8
- package/dist/core/runner/sub-agent-deny-lists.js +23 -12
- package/dist/core/runner/terminal-account.js +3 -3
- package/dist/core/runner/tool-output-projection.js +3 -8
- package/dist/core/runner/tool-result-media-seat.d.ts +19 -0
- package/dist/core/runner/tool-result-media-seat.js +13 -0
- package/dist/core/runner/turn-attachments.d.ts +13 -12
- package/dist/core/runner/turn-attachments.js +11 -7
- package/dist/core/runner-deps.d.ts +4 -3
- package/dist/core/safety-merge-corpus.js +21 -21
- package/dist/core/secret-env.js +1 -1
- package/dist/core/session-policy-store.d.ts +89 -72
- package/dist/core/session-policy-store.js +100 -93
- package/dist/core/shell-read-silence.d.ts +38 -4
- package/dist/core/shell-read-silence.js +102 -0
- package/dist/core/side-query.d.ts +7 -8
- package/dist/core/skill-grant-binding.d.ts +32 -11
- package/dist/core/skill-grant-binding.js +11 -3
- package/dist/core/skill-grant.d.ts +4 -3
- package/dist/core/skill-grant.js +5 -4
- package/dist/core/skills-directory.d.ts +12 -1
- package/dist/core/skills-directory.js +17 -8
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +3 -0
- package/dist/core/swappable-deps.d.ts +15 -1
- package/dist/core/swappable-deps.js +25 -1
- package/dist/core/synthetic-read-verdict.d.ts +16 -14
- package/dist/core/synthetic-read-verdict.js +28 -8
- package/dist/core/task-notification.d.ts +13 -3
- package/dist/core/task-notification.js +12 -1
- package/dist/core/task-spec.d.ts +14 -15
- package/dist/core/tool-name-readings.d.ts +0 -4
- package/dist/core/tool-name-readings.js +0 -1
- package/dist/core/tool-policy.d.ts +40 -9
- package/dist/core/tool-policy.js +25 -7
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-egress.d.ts +60 -11
- package/dist/core/untrusted-egress.js +120 -9
- package/dist/core/untrusted-text.js +16 -0
- package/dist/core/wiring-manifest.d.ts +22 -8
- package/dist/core/wiring-manifest.js +12 -4
- package/dist/engine/harness/agent-harness.d.ts +2 -1
- package/dist/engine/harness/agent-harness.js +4 -1
- package/dist/engine/harness/types.d.ts +3 -0
- package/dist/engine/llm/types.d.ts +7 -9
- package/dist/engine/loop/agent-loop.d.ts +8 -3
- package/dist/engine/loop/agent-loop.js +26 -9
- package/dist/engine/loop/types.d.ts +10 -0
- package/dist/index.d.ts +17 -5
- package/dist/index.js +14 -5
- package/dist/orchestration/run-workflow-tool.js +8 -8
- package/dist/orchestration/workflow.js +2 -2
- package/dist/prompt-assembly/assemble.d.ts +3 -1
- package/dist/prompt-assembly/assemble.js +6 -0
- package/dist/prompts/cowork.d.ts +42 -0
- package/dist/prompts/cowork.js +529 -0
- package/dist/prompts/default.d.ts +14 -1
- package/dist/prompts/default.js +3 -0
- package/dist/stores/file/session-policy-store.d.ts +6 -3
- package/dist/stores/file/session-policy-store.js +5 -16
- package/dist/tools/artifact/artifact-tool.js +2 -0
- package/dist/tools/fs/image-admission.d.ts +57 -4
- package/dist/tools/fs/image-admission.js +85 -0
- package/dist/tools/fs/path-containment.d.ts +2 -0
- package/dist/tools/fs/path-containment.js +5 -0
- package/dist/tools/fs/shell-read-check.d.ts +31 -6
- package/dist/tools/fs/shell-read-check.js +2 -4
- package/dist/tools/fs/shell-read-environment.d.ts +10 -25
- package/dist/tools/fs/shell-read-environment.js +6 -18
- package/dist/tools/fs/shell-read-establish.d.ts +3 -2
- package/dist/tools/fs/shell-read-establish.js +5 -5
- package/dist/tools/fs/shell-read-judge.d.ts +38 -8
- package/dist/tools/fs/shell-read-judge.js +12 -10
- package/dist/tools/web.js +2 -2
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +94 -10
- package/wire-changes/core@7.51.0.json +2421 -0
|
@@ -14,26 +14,37 @@
|
|
|
14
14
|
* (mirrors `steer.trusted`); core never adjudicates operator-ness, it only honors the flag.
|
|
15
15
|
* - **CAS rev** (optimistic concurrency): a `putRules` with a stale `expectedRev` is rejected, so two replicas
|
|
16
16
|
* can't lose an update (mirrors the checkpoint store's `resolve` OCC).
|
|
17
|
-
* - **
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* - **one grammar**: the two rule buckets hold rule ENTRIES — the grammar every entry list reads, through its one reader
|
|
18
|
+
* (`readRuleEntry`, allow-entry.ts): a tool name, a protocol namespace, a shell command rule (`Bash(git push:*)`), a
|
|
19
|
+
* path rule, a name or host rule. Entries are stored as written; every station that validates, compares, matches or
|
|
20
|
+
* names one reads that reader's answer ({@link judgeSessionRulesWrite}, {@link loosenReasons}, the run-time layer).
|
|
21
|
+
* - **NOT A SANDBOX**: a shell command rule reads the command line the call carries (every program run in its connector
|
|
22
|
+
* chain); the real isolation boundary is the executionEnv, never these rules.
|
|
21
23
|
*/
|
|
22
|
-
import { type
|
|
23
|
-
/** The
|
|
24
|
+
import { type AllowEntryInertDetail, type AllowEntryInertReason } from "./allow-entry.js";
|
|
25
|
+
/** The per-session rules. All optional; an absent bucket imposes no constraint of that kind. */
|
|
24
26
|
export interface SessionPermissionRules {
|
|
25
|
-
/** If set, ONLY these
|
|
27
|
+
/** If set, ONLY the calls these entries admit are allowed — a narrowing allowlist: a call no entry admits is refused.
|
|
28
|
+
* Rule entries read in the allow direction (`Read`, `mcp__srv`, `Bash(git:*)`, `Edit(src/**)`). */
|
|
26
29
|
toolAllow?: string[];
|
|
27
|
-
/**
|
|
30
|
+
/** Entries whose calls are always refused (deny wins) — rule entries read in the deny direction (`Bash`, `Bash(rm:*)`,
|
|
31
|
+
* `mcp__srv__delete_*`, `Read(//etc/**)`). */
|
|
28
32
|
toolDeny?: string[];
|
|
29
33
|
/** If set, WRITE tools (`Write`/`Edit`) may only target paths within these dirs; a write-capable
|
|
30
34
|
* tool that can't be path-confined (e.g. `Bash`) is denied while this is set. Stored RAW; resolved at run time. */
|
|
31
35
|
allowDirs?: string[];
|
|
32
|
-
/** If set, ONLY these `Bash` command names (argv[0]) are allowed. */
|
|
33
|
-
commandAllow?: string[];
|
|
34
|
-
/** `Bash` command names (argv[0]) always denied. */
|
|
35
|
-
commandDeny?: string[];
|
|
36
36
|
}
|
|
37
|
+
/** Every bucket a rules object holds, and how its entries are read: a rule-entry list in one direction, or the write
|
|
38
|
+
* confinement's directories. A key outside this table is no bucket ({@link readSessionRulesShape} refuses it). */
|
|
39
|
+
declare const SESSION_RULE_BUCKETS: {
|
|
40
|
+
readonly toolAllow: "allow";
|
|
41
|
+
readonly toolDeny: "deny";
|
|
42
|
+
readonly allowDirs: "dirs";
|
|
43
|
+
};
|
|
44
|
+
/** A bucket that holds rule entries. */
|
|
45
|
+
export type SessionRuleEntryField = {
|
|
46
|
+
[K in keyof typeof SESSION_RULE_BUCKETS]: (typeof SESSION_RULE_BUCKETS)[K] extends "dirs" ? never : K;
|
|
47
|
+
}[keyof typeof SESSION_RULE_BUCKETS];
|
|
37
48
|
/** {@link SessionPermissionRules} plus the monotonic `rev` the store stamps (the OCC key). */
|
|
38
49
|
export interface StoredSessionRules extends SessionPermissionRules {
|
|
39
50
|
rev: number;
|
|
@@ -52,58 +63,42 @@ export interface PutRulesOptions {
|
|
|
52
63
|
* this from a verified, role-checked principal (mirrors `steer.trusted`). */
|
|
53
64
|
operator?: boolean;
|
|
54
65
|
}
|
|
55
|
-
/** The {@link SessionPolicyError.detail} of
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
export interface
|
|
59
|
-
field:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
/** The {@link SessionPolicyError.detail} of an `invalid_shape` refusal: which key, and what it holds — its TYPE, never its
|
|
67
|
+
* value (`a string`, `null`, `an array holding an entry that is not a string`), or `not a rule bucket` for a key outside
|
|
68
|
+
* the buckets. `field` is empty when the rules value itself is not an object. */
|
|
69
|
+
export interface SessionRuleShapeRefusal {
|
|
70
|
+
field: string;
|
|
71
|
+
got: string;
|
|
72
|
+
}
|
|
73
|
+
/** The {@link SessionPolicyError.detail} of an `inert_entry` refusal: which bucket, which entry, and why it reads as nothing —
|
|
74
|
+
* the entry reader's own answer (`readRuleEntry`), reason and facts unchanged. */
|
|
75
|
+
export interface SessionRuleEntryRefusal extends AllowEntryInertDetail {
|
|
76
|
+
field: SessionRuleEntryField;
|
|
77
|
+
entry: string;
|
|
78
|
+
reason: AllowEntryInertReason;
|
|
66
79
|
}
|
|
67
80
|
/** Typed error from {@link SessionPolicyStore.putRules}. `conflict` = stale `expectedRev`; `loosen_forbidden`
|
|
68
|
-
* = a non-operator write tried to relax the rules; `corrupt` = a backend could not read the
|
|
69
|
-
* rev mark
|
|
70
|
-
*
|
|
71
|
-
* ({@link
|
|
81
|
+
* = a non-operator write tried to relax the rules; `corrupt` = a backend could not read what it stored (a row, or the
|
|
82
|
+
* erase-surviving rev mark of {@link SessionPolicyStore.deleteBySession}) and refuses rather than read it as fewer rules or
|
|
83
|
+
* stamp an unproven rev; `invalid_shape` = the rules value is not an object of buckets, each a list of strings
|
|
84
|
+
* ({@link readSessionRulesShape}); `inert_entry` = a bucket holds an entry the entry reader reads as nothing. `detail` is
|
|
85
|
+
* present on the last two codes and only on them ({@link SessionRuleShapeRefusal}, {@link SessionRuleEntryRefusal}). */
|
|
72
86
|
export declare class SessionPolicyError extends Error {
|
|
73
|
-
readonly code: "conflict" | "loosen_forbidden" | "corrupt" | "
|
|
74
|
-
readonly detail?:
|
|
75
|
-
constructor(code: "conflict" | "loosen_forbidden" | "corrupt" | "
|
|
87
|
+
readonly code: "conflict" | "loosen_forbidden" | "corrupt" | "invalid_shape" | "inert_entry";
|
|
88
|
+
readonly detail?: (SessionRuleShapeRefusal | SessionRuleEntryRefusal) | undefined;
|
|
89
|
+
constructor(code: "conflict" | "loosen_forbidden" | "corrupt" | "invalid_shape" | "inert_entry", message: string, detail?: (SessionRuleShapeRefusal | SessionRuleEntryRefusal) | undefined);
|
|
76
90
|
}
|
|
77
|
-
/** Which buckets hold TOOL names (the ones a run's policy audit reads as names); a new bucket is given a role here. */
|
|
78
|
-
declare const HOLDS_TOOL_NAMES: {
|
|
79
|
-
readonly toolAllow: true;
|
|
80
|
-
readonly toolDeny: true;
|
|
81
|
-
readonly allowDirs: false;
|
|
82
|
-
readonly commandAllow: false;
|
|
83
|
-
readonly commandDeny: false;
|
|
84
|
-
};
|
|
85
|
-
type SessionRuleToolNameField = {
|
|
86
|
-
[K in keyof typeof HOLDS_TOOL_NAMES]: (typeof HOLDS_TOOL_NAMES)[K] extends true ? K : never;
|
|
87
|
-
}[keyof typeof HOLDS_TOOL_NAMES];
|
|
88
91
|
/** Construction options both bundled stores take. */
|
|
89
92
|
export interface SessionPolicyStoreOptions {
|
|
90
|
-
/** The names this deployment mounts its OWN tools under — the roster the store
|
|
91
|
-
* (
|
|
92
|
-
*
|
|
93
|
+
/** The names this deployment mounts its OWN tools under — the roster the store reads a rule entry's tool name against
|
|
94
|
+
* (`readRuleEntry`): a retired name declared here is an ordinary name to the write and to the loosening check alike.
|
|
95
|
+
* Absent = none. A name carrying `__` is refused, as it is for a deployment tool. */
|
|
93
96
|
mountedToolNames?: Iterable<string>;
|
|
94
97
|
}
|
|
95
98
|
/** A store's declared roster from {@link SessionPolicyStoreOptions.mountedToolNames} — the one reader both bundled stores
|
|
96
99
|
* (and so `FileStorageBackendOptions.sessionPolicy`) construct through, refusing loudly when it is not a list of plain
|
|
97
100
|
* tool names (a bare string would iterate by character). */
|
|
98
101
|
export declare function readMountedToolNames(value: unknown): ReadonlySet<string>;
|
|
99
|
-
/**
|
|
100
|
-
* The write judge for session rules, against `mountedToolNames` (the store's declaration; empty when it has none): every
|
|
101
|
-
* entry of a tool-name bucket must be `current` ({@link judgeRuleToolName}), or the write is refused (the fourth
|
|
102
|
-
* {@link SessionPolicyError} code) — such a name matches no tool, and a run's policy audit refuses to start with it.
|
|
103
|
-
* Operator writes included. Both bundled stores call it after normalizing and before {@link loosenReasons}; a durable
|
|
104
|
-
* backend calls it at the same point. The message names the entry and the way out, never a storage location.
|
|
105
|
-
*/
|
|
106
|
-
export declare function assertSessionRuleNamesCurrent(rules: SessionPermissionRules, mountedToolNames?: ReadonlySet<string>): void;
|
|
107
102
|
/**
|
|
108
103
|
* The seam a deployment implements for DURABLE, cross-replica per-session rules. Unset on `RunnerDeps` ⇒ the
|
|
109
104
|
* feature is OFF (no rules read, zero behavior change). Keyed on `(sessionId, principal-owner)`: core passes
|
|
@@ -118,11 +113,9 @@ export interface SessionRulesRecord {
|
|
|
118
113
|
}
|
|
119
114
|
export interface SessionPolicyStore {
|
|
120
115
|
getRules(sessionId: string, principal?: string): Promise<StoredSessionRules | null>;
|
|
121
|
-
/** Write the rules for a key
|
|
122
|
-
* operator writes included), then, for a non-operator write over an existing row, the
|
|
123
|
-
* tighten-only check
|
|
124
|
-
* tools under. A bucket that is not a list of strings (a bare `"rm"` for `commandDeny`) throws a `TypeError` from
|
|
125
|
-
* {@link normalizeRules} before any of those is judged, and nothing is written. */
|
|
116
|
+
/** Write the rules for a key through the one write judge ({@link judgeSessionRulesWrite}): CAS on `expectedRev`, the
|
|
117
|
+
* shape, every entry's reading (operator writes included), then, for a non-operator write over an existing row, the
|
|
118
|
+
* tighten-only check — each refusal a {@link SessionPolicyError}, and nothing is written. */
|
|
126
119
|
putRules(sessionId: string, principal: string | undefined, rules: SessionPermissionRules, opts?: PutRulesOptions): Promise<StoredSessionRules>;
|
|
127
120
|
/**
|
|
128
121
|
* 2c session-sync: every (principal, rules) record for `sessionId` across ALL principals — `getRules`
|
|
@@ -172,21 +165,47 @@ export interface SessionPolicyStore {
|
|
|
172
165
|
* backend should keep `allowDirs` to real directories; a future symlink-aware write-time canonicalize closes it.
|
|
173
166
|
*/
|
|
174
167
|
declare function lexWithin(root: string, p: string): boolean;
|
|
175
|
-
/** Normalize a rules object's `allowDirs` (lexical `posix.normalize`) so stored rules are `..`-free and the
|
|
176
|
-
* tighten diff compares the same canonical-ish form the runtime resolves. Every bucket must be a list of strings
|
|
177
|
-
* ({@link ruleList}); a bucket that is not one throws `TypeError`, so neither bundled store (nor a durable twin that
|
|
178
|
-
* normalizes through this function) can store a rule list the matcher would read differently from the one written. */
|
|
179
|
-
export declare function normalizeRules(rules: SessionPermissionRules): SessionPermissionRules;
|
|
180
168
|
/**
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
169
|
+
* THE ONE SHAPE READING of a rules value — what every station that takes one reads it through: the write (incoming rules),
|
|
170
|
+
* a store's read-back of its own rows, the tighten comparison's prior, the run-time layer's served row. A rules value is an
|
|
171
|
+
* object whose keys are buckets ({@link SessionPermissionRules}); each bucket present is a list of strings (`allowDirs`: of
|
|
172
|
+
* non-blank strings — a directory is no blank). A bucket's TYPE is all this reads: what an entry means is the entry reader's.
|
|
173
|
+
* The store's own stamps (`rev`, `gen`) are passed over, and a key holding `undefined` holds nothing. Anything else is a fault,
|
|
174
|
+
* named by key and by TYPE, never by value — a bare string is never one rule and never its characters (`"rm"` spread into
|
|
175
|
+
* `["r","m"]`), and a key outside the buckets is never dropped (a dropped deny is a deny nobody enforces).
|
|
176
|
+
*/
|
|
177
|
+
export declare function readSessionRulesShape(value: unknown): {
|
|
178
|
+
rules: SessionPermissionRules;
|
|
179
|
+
} | {
|
|
180
|
+
fault: SessionRuleShapeRefusal;
|
|
181
|
+
sentence: string;
|
|
182
|
+
};
|
|
183
|
+
/** The CANONICAL stored form of a rules value: its shape read ({@link readSessionRulesShape} — a fault refuses with
|
|
184
|
+
* `invalid_shape`), every list copied (detached from the caller's arrays), and `allowDirs` lexically `posix.normalize`d so the
|
|
185
|
+
* stored form is `..`-free and the tighten diff compares the form the runtime resolves. Both bundled stores and a durable twin
|
|
186
|
+
* stamp exactly this, so a structuredClone and a JSON round-trip of it are byte-identical. */
|
|
187
|
+
export declare function normalizeRules(rules: unknown): SessionPermissionRules;
|
|
188
|
+
/**
|
|
189
|
+
* THE ONE WRITE JUDGE every store applies before it stamps a row (both bundled stores, and a durable backend at the same point):
|
|
190
|
+
* the rules to stamp, or a {@link SessionPolicyError}, in this order — `conflict` (a stale `expectedRev` against `prior`'s rev),
|
|
191
|
+
* `invalid_shape` ({@link normalizeRules}), `inert_entry` (an entry the reader reads as nothing; operator writes included — a
|
|
192
|
+
* rule that names nothing guards nothing whoever writes it), then, for a non-operator write over an existing row,
|
|
193
|
+
* `loosen_forbidden` ({@link loosenReasons}) — or `corrupt` when that prior cannot be read as rules, which the comparison never
|
|
194
|
+
* reads as fewer. `mountedToolNames` = the store's declaration ({@link SessionPolicyStoreOptions.mountedToolNames}).
|
|
195
|
+
*/
|
|
196
|
+
export declare function judgeSessionRulesWrite(prior: StoredSessionRules | null, rules: unknown, opts: PutRulesOptions | undefined, mountedToolNames?: ReadonlySet<string>): SessionPermissionRules;
|
|
197
|
+
/**
|
|
198
|
+
* Pure tighten-only diff: the reasons `next` LOOSENS `prior` (empty ⇒ `next` is a pure tightening or no-op), over the EFFECTIVE
|
|
199
|
+
* entries of each rule bucket — the list as the run-time layer enforces it ({@link effectiveEntries}). An entry that reads as
|
|
200
|
+
* nothing is in neither side's effect, so removing one relaxes nothing and adding one widens nothing (the way a session's owner
|
|
201
|
+
* takes out an entry that names nothing); an allowlist a `*` voids admits nothing, so dropping the `*` widens. toolDeny: removing an effective entry loosens. toolAllow: removing the list
|
|
202
|
+
* (→ everything allowed) or adding an effective entry outside the prior set loosens; prior-undefined → any defined next only
|
|
203
|
+
* narrows. allowDirs: removing confinement, or adding a dir not within some prior dir, loosens (coarse raw containment).
|
|
204
|
+
* Entries compare as written. Both sides are read through {@link readSessionRulesShape} first: a side it cannot read (a key that
|
|
205
|
+
* is no bucket — a retired one included) throws `invalid_shape` rather than compare without it. `mountedToolNames` = the store's
|
|
206
|
+
* declaration ({@link SessionPolicyStoreOptions.mountedToolNames}; empty when it has none) — the write judge's too.
|
|
188
207
|
*/
|
|
189
|
-
export declare function loosenReasons(
|
|
208
|
+
export declare function loosenReasons(priorValue: SessionPermissionRules, nextValue: SessionPermissionRules, mountedToolNames?: ReadonlySet<string>): string[];
|
|
190
209
|
/**
|
|
191
210
|
* Does row `a` belong to the SAME lineage as row `b` — the one question every reader asks before it compares
|
|
192
211
|
* two rows' `rev`, asked in ONE place so a `gen` that is ABSENT cannot mean three different things at three
|
|
@@ -205,8 +224,6 @@ export declare function continuesLineage(a: {
|
|
|
205
224
|
}, b: {
|
|
206
225
|
gen?: number;
|
|
207
226
|
}): boolean;
|
|
208
|
-
/** strip any caller-passed `rev` from a rules object so the store is the sole authority on it. */
|
|
209
|
-
export declare function stripRev(rules: SessionPermissionRules): SessionPermissionRules;
|
|
210
227
|
/**
|
|
211
228
|
* In-process default {@link SessionPolicyStore} (single-replica). A deployment that needs cross-replica
|
|
212
229
|
* durability supplies its own backend with the SAME CAS-rev + tighten-only semantics. Single-threaded JS makes
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { posix } from "node:path";
|
|
2
2
|
import { isWinFormPath } from "../tools/fs/safety.js";
|
|
3
3
|
import { NAMESPACED_NAME_SHAPES, carriesNamespaceSeparator } from "./protocol-table.js";
|
|
4
|
-
import {
|
|
4
|
+
import { compileAllowList, inertEntryPhrase, readRuleEntry, renderListEntry } from "./allow-entry.js";
|
|
5
|
+
import { valueTypeLabel } from "./tool-name-list.js";
|
|
6
|
+
const SESSION_RULE_BUCKETS = { toolAllow: "allow", toolDeny: "deny", allowDirs: "dirs" };
|
|
7
|
+
const BUCKETS = Object.keys(SESSION_RULE_BUCKETS);
|
|
8
|
+
const ENTRY_FIELDS = BUCKETS.filter((f) => SESSION_RULE_BUCKETS[f] !== "dirs");
|
|
9
|
+
const STORE_STAMPS = new Set(["rev", "gen"]);
|
|
5
10
|
export class SessionPolicyError extends Error {
|
|
6
11
|
code;
|
|
7
12
|
detail;
|
|
@@ -12,8 +17,6 @@ export class SessionPolicyError extends Error {
|
|
|
12
17
|
this.name = "SessionPolicyError";
|
|
13
18
|
}
|
|
14
19
|
}
|
|
15
|
-
const HOLDS_TOOL_NAMES = { toolAllow: true, toolDeny: true, allowDirs: false, commandAllow: false, commandDeny: false };
|
|
16
|
-
const TOOL_NAME_FIELDS = Object.keys(HOLDS_TOOL_NAMES).filter((f) => HOLDS_TOOL_NAMES[f]);
|
|
17
20
|
const NO_DECLARED_TOOLS = new Set();
|
|
18
21
|
export function readMountedToolNames(value) {
|
|
19
22
|
if (value === undefined)
|
|
@@ -33,35 +36,6 @@ export function readMountedToolNames(value) {
|
|
|
33
36
|
}
|
|
34
37
|
return names;
|
|
35
38
|
}
|
|
36
|
-
export function assertSessionRuleNamesCurrent(rules, mountedToolNames = NO_DECLARED_TOOLS) {
|
|
37
|
-
for (const field of TOOL_NAME_FIELDS) {
|
|
38
|
-
for (const name of rules[field] ?? []) {
|
|
39
|
-
const verdict = judgeRuleToolName(name, mountedToolNames);
|
|
40
|
-
if (verdict.kind === "current")
|
|
41
|
-
continue;
|
|
42
|
-
const quoted = JSON.stringify(name);
|
|
43
|
-
const successor = verdict.kind === "unprefixed_namespace" ? undefined : verdict.currentName;
|
|
44
|
-
const guidance = verdict.kind === "unprefixed_namespace" ? undefined : verdict.guidance;
|
|
45
|
-
let message;
|
|
46
|
-
switch (verdict.kind) {
|
|
47
|
-
case "retired":
|
|
48
|
-
message = `session rules ${field} names ${quoted}, a retired tool name (${verdict.guidance}) — with it in place no run of this session could start, so nothing was written.${successor !== undefined ? ` Use the current name ${JSON.stringify(successor)}.` : ""} If this deployment mounts its own tool under ${quoted}, declare it in the store's mountedToolNames.`;
|
|
49
|
-
break;
|
|
50
|
-
case "unprefixed_namespace":
|
|
51
|
-
message = `session rules ${field} names ${quoted}, which carries the "__" namespace separator but no protocol prefix — no tool is named that way (protocol tools are named ${NAMESPACED_NAME_SHAPES}), and with it in place no run of this session could start, so nothing was written.`;
|
|
52
|
-
break;
|
|
53
|
-
case "unmintable_segment":
|
|
54
|
-
message = `session rules ${field} names ${quoted}, which can match no tool (${verdict.guidance}) — with it in place no run of this session could start, so nothing was written.${successor !== undefined ? ` Write ${JSON.stringify(successor)}.` : ""}`;
|
|
55
|
-
break;
|
|
56
|
-
default: {
|
|
57
|
-
const exhaustive = verdict;
|
|
58
|
-
return exhaustive;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
throw new SessionPolicyError("legacy_tool_name", message, { field, name, verdict: verdict.kind, ...(guidance !== undefined ? { guidance } : {}), ...(successor !== undefined ? { currentName: successor } : {}) });
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
39
|
function lexWithin(root, p) {
|
|
66
40
|
const fold = (x) => {
|
|
67
41
|
let n = isWinFormPath(x) ? x.replace(/\\/g, "/") : x;
|
|
@@ -75,61 +49,109 @@ function lexWithin(root, p) {
|
|
|
75
49
|
return true;
|
|
76
50
|
return np === nr || np.startsWith(nr + "/");
|
|
77
51
|
}
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
};
|
|
84
|
-
if (Array.isArray(value) && Array.from(value).every((entry) => typeof entry === "string")) {
|
|
85
|
-
if (value.some((entry) => entry.trim() === ""))
|
|
86
|
-
refuse("an array holding an empty or blank entry");
|
|
87
|
-
return value;
|
|
52
|
+
export function readSessionRulesShape(value) {
|
|
53
|
+
const fault = (field, got, sentence) => ({ fault: { field, got }, sentence });
|
|
54
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
55
|
+
const got = Array.isArray(value) ? "an array" : valueTypeLabel(value);
|
|
56
|
+
return fault("", got, `session rules must be an object of rule buckets (${BUCKETS.join(", ")}), got ${got}`);
|
|
88
57
|
}
|
|
89
|
-
|
|
58
|
+
const rules = {};
|
|
59
|
+
for (const [key, v] of Object.entries(value)) {
|
|
60
|
+
if (STORE_STAMPS.has(key) || v === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
if (!Object.hasOwn(SESSION_RULE_BUCKETS, key)) {
|
|
63
|
+
return fault(key, "not a rule bucket", `session rules hold ${JSON.stringify(key)}, which is not a rule bucket — the buckets are ${BUCKETS.join(", ")}, and a shell command rule is a toolDeny / toolAllow entry written Bash(<command>) (Bash(rm:*) refuses every run of rm)`);
|
|
64
|
+
}
|
|
65
|
+
const field = key;
|
|
66
|
+
const listed = Array.isArray(v) ? Array.from(v) : undefined;
|
|
67
|
+
const got = listed === undefined
|
|
68
|
+
? valueTypeLabel(v)
|
|
69
|
+
: !listed.every((e) => typeof e === "string")
|
|
70
|
+
? "an array holding an entry that is not a string"
|
|
71
|
+
: SESSION_RULE_BUCKETS[field] === "dirs" && listed.some((e) => e.trim() === "")
|
|
72
|
+
? "an array holding an empty or blank entry"
|
|
73
|
+
: undefined;
|
|
74
|
+
if (got !== undefined)
|
|
75
|
+
return fault(field, got, `session rules ${field} must be an array of ${SESSION_RULE_BUCKETS[field] === "dirs" ? "directory paths" : "rule entries"}, got ${got} — a bucket is written as a list (["Bash(rm:*)"], not "Bash(rm:*)")`);
|
|
76
|
+
rules[field] = listed;
|
|
77
|
+
}
|
|
78
|
+
return { rules };
|
|
90
79
|
}
|
|
91
80
|
export function normalizeRules(rules) {
|
|
81
|
+
const read = readSessionRulesShape(rules);
|
|
82
|
+
if ("fault" in read)
|
|
83
|
+
throw new SessionPolicyError("invalid_shape", read.sentence, read.fault);
|
|
92
84
|
const out = {};
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
out.toolAllow = [...toolAllow];
|
|
100
|
-
if (toolDeny !== undefined)
|
|
101
|
-
out.toolDeny = [...toolDeny];
|
|
102
|
-
if (commandAllow !== undefined)
|
|
103
|
-
out.commandAllow = [...commandAllow];
|
|
104
|
-
if (commandDeny !== undefined)
|
|
105
|
-
out.commandDeny = [...commandDeny];
|
|
106
|
-
if (allowDirs !== undefined)
|
|
107
|
-
out.allowDirs = allowDirs.map((d) => posix.normalize(d).replace(/\/+$/, "") || "/");
|
|
85
|
+
for (const field of BUCKETS) {
|
|
86
|
+
const list = read.rules[field];
|
|
87
|
+
if (list === undefined)
|
|
88
|
+
continue;
|
|
89
|
+
out[field] = SESSION_RULE_BUCKETS[field] === "dirs" ? list.map((d) => posix.normalize(d).replace(/\/+$/, "") || "/") : [...list];
|
|
90
|
+
}
|
|
108
91
|
return out;
|
|
109
92
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
93
|
+
function effectiveEntries(field, list, mountedToolNames) {
|
|
94
|
+
return compileAllowList(list ?? [], SESSION_RULE_BUCKETS[field], mountedToolNames).rows.map((r) => r.entry);
|
|
95
|
+
}
|
|
96
|
+
function assertEntriesRead(rules, mountedToolNames) {
|
|
97
|
+
for (const field of ENTRY_FIELDS) {
|
|
98
|
+
for (const entry of rules[field] ?? []) {
|
|
99
|
+
const read = readRuleEntry(entry, SESSION_RULE_BUCKETS[field], mountedToolNames);
|
|
100
|
+
if (read.kind === "row")
|
|
116
101
|
continue;
|
|
117
|
-
|
|
102
|
+
const { kind: _kind, ...why } = read;
|
|
103
|
+
void _kind;
|
|
104
|
+
const way = why.verdict === "retired"
|
|
105
|
+
? ` If this deployment mounts its own tool under ${renderListEntry(entry)}, declare it in the store's mountedToolNames.`
|
|
106
|
+
: why.currentName === undefined
|
|
107
|
+
? ` An entry is a tool name (Read; a protocol tool is named ${NAMESPACED_NAME_SHAPES}), a protocol namespace (mcp__<server>), or a rule in its tool's own grammar (Bash(<command>), Edit(<path>), Skill(<name>), WebFetch(domain:<host>)).`
|
|
108
|
+
: "";
|
|
109
|
+
const sentence = why.guidance !== undefined ? ` — ${why.guidance}` : "";
|
|
110
|
+
throw new SessionPolicyError("inert_entry", `session rules ${field} holds ${inertEntryPhrase({ entry, ...why })}, which ${SESSION_RULE_BUCKETS[field] === "deny" ? "refuses" : "admits"} nothing as written${sentence}; nothing was written.${way}`, { field, entry, ...why });
|
|
118
111
|
}
|
|
119
112
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
113
|
+
}
|
|
114
|
+
export function judgeSessionRulesWrite(prior, rules, opts, mountedToolNames = NO_DECLARED_TOOLS) {
|
|
115
|
+
if (opts?.expectedRev !== undefined && (prior?.rev ?? 0) !== opts.expectedRev) {
|
|
116
|
+
throw new SessionPolicyError("conflict", `session rules rev mismatch (expected ${opts.expectedRev}, have ${prior?.rev ?? 0})`);
|
|
117
|
+
}
|
|
118
|
+
const clean = normalizeRules(rules);
|
|
119
|
+
assertEntriesRead(clean, mountedToolNames);
|
|
120
|
+
if (prior !== null && opts?.operator !== true) {
|
|
121
|
+
const stored = readSessionRulesShape(prior);
|
|
122
|
+
if ("fault" in stored) {
|
|
123
|
+
throw new SessionPolicyError("corrupt", `the stored session rules cannot be read as rules (${stored.sentence}) — refusing to judge a non-operator write against them as fewer rules; an operator write replaces them`);
|
|
124
|
+
}
|
|
125
|
+
const loosens = loosenReasons(stored.rules, clean, mountedToolNames);
|
|
126
|
+
if (loosens.length > 0) {
|
|
127
|
+
throw new SessionPolicyError("loosen_forbidden", `a non-operator session-rule write may only tighten; refused: ${loosens.join("; ")}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return clean;
|
|
131
|
+
}
|
|
132
|
+
export function loosenReasons(priorValue, nextValue, mountedToolNames = NO_DECLARED_TOOLS) {
|
|
133
|
+
const shapeOf = (side, value) => {
|
|
134
|
+
const read = readSessionRulesShape(value);
|
|
135
|
+
if ("fault" in read)
|
|
136
|
+
throw new SessionPolicyError("invalid_shape", `the ${side} rules: ${read.sentence}`, read.fault);
|
|
137
|
+
return read.rules;
|
|
138
|
+
};
|
|
139
|
+
const prior = shapeOf("prior", priorValue);
|
|
140
|
+
const next = shapeOf("next", nextValue);
|
|
141
|
+
const reasons = [];
|
|
142
|
+
const keptDeny = new Set(effectiveEntries("toolDeny", next.toolDeny, mountedToolNames));
|
|
143
|
+
for (const x of effectiveEntries("toolDeny", prior.toolDeny, mountedToolNames))
|
|
144
|
+
if (!keptDeny.has(x))
|
|
145
|
+
reasons.push(`removed toolDeny ${renderListEntry(x)}`);
|
|
146
|
+
if (prior.toolAllow !== undefined) {
|
|
147
|
+
if (next.toolAllow === undefined)
|
|
148
|
+
reasons.push("removed toolAllow allowlist (all allowed)");
|
|
149
|
+
else {
|
|
150
|
+
const had = new Set(effectiveEntries("toolAllow", prior.toolAllow, mountedToolNames));
|
|
151
|
+
for (const x of effectiveEntries("toolAllow", next.toolAllow, mountedToolNames))
|
|
152
|
+
if (!had.has(x))
|
|
153
|
+
reasons.push(`widened toolAllow with ${renderListEntry(x)}`);
|
|
128
154
|
}
|
|
129
|
-
const ps = new Set(p);
|
|
130
|
-
for (const x of n)
|
|
131
|
-
if (!ps.has(x))
|
|
132
|
-
reasons.push(`widened ${field} with "${x}"`);
|
|
133
155
|
}
|
|
134
156
|
const pd = prior.allowDirs;
|
|
135
157
|
if (pd !== undefined && pd.length > 0) {
|
|
@@ -146,11 +168,6 @@ export function loosenReasons(prior, next, mountedToolNames = NO_DECLARED_TOOLS)
|
|
|
146
168
|
export function continuesLineage(a, b) {
|
|
147
169
|
return (a.gen ?? 0) === (b.gen ?? 0);
|
|
148
170
|
}
|
|
149
|
-
export function stripRev(rules) {
|
|
150
|
-
const { ...rest } = rules;
|
|
151
|
-
delete rest.rev;
|
|
152
|
-
return rest;
|
|
153
|
-
}
|
|
154
171
|
export class InMemorySessionPolicyStore {
|
|
155
172
|
map = new Map();
|
|
156
173
|
mountedToolNames;
|
|
@@ -168,17 +185,7 @@ export class InMemorySessionPolicyStore {
|
|
|
168
185
|
async putRules(sessionId, principal, rules, opts) {
|
|
169
186
|
const key = this.key(sessionId, principal);
|
|
170
187
|
const prior = this.map.get(key);
|
|
171
|
-
|
|
172
|
-
throw new SessionPolicyError("conflict", `session rules rev mismatch (expected ${opts.expectedRev}, have ${prior?.rev ?? 0})`);
|
|
173
|
-
}
|
|
174
|
-
const clean = normalizeRules(stripRev(rules));
|
|
175
|
-
assertSessionRuleNamesCurrent(clean, this.mountedToolNames);
|
|
176
|
-
if (prior && opts?.operator !== true) {
|
|
177
|
-
const loosens = loosenReasons(prior, clean, this.mountedToolNames);
|
|
178
|
-
if (loosens.length > 0) {
|
|
179
|
-
throw new SessionPolicyError("loosen_forbidden", `a non-operator session-rule write may only tighten; refused: ${loosens.join("; ")}`);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
188
|
+
const clean = judgeSessionRulesWrite(prior ?? null, rules, opts, this.mountedToolNames);
|
|
182
189
|
const mark = this.highWater.get(key) ?? { rev: 0, gen: 0 };
|
|
183
190
|
const floor = Math.max(prior?.rev ?? 0, mark.rev);
|
|
184
191
|
const gen = prior?.gen ?? mark.gen;
|
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
import type { ShellReadVerdict } from "../tools/fs/shell-read-check.js";
|
|
2
|
+
import { type ShellReadDisposition } from "../tools/fs/shell-read-judge.js";
|
|
2
3
|
import type { GatePass } from "./runner/gate-exit.js";
|
|
3
4
|
import type { AskGuard } from "./tool-policy.js";
|
|
5
|
+
/** One station answer, received and read: what a reader acts on (`kind`) and the answer it acts on (`verdict` — the engine's own
|
|
6
|
+
* copy, received once). `unbelieved`: the station's answer was not received or contradicted its own finding, and `verdict` is the
|
|
7
|
+
* engine's refusal of it (its sentence names the station's words only where they are words of the closed sets — nothing a station
|
|
8
|
+
* spelled reaches the model), reported once on the call's lane ({@link StationAnswerCall.report}). */
|
|
9
|
+
export interface StationAnswerReading {
|
|
10
|
+
readonly kind: ShellReadDisposition | "silence";
|
|
11
|
+
readonly verdict: ShellReadVerdict;
|
|
12
|
+
readonly unbelieved?: true;
|
|
13
|
+
}
|
|
14
|
+
/** The call a reader reads an answer for: its name (the refusal names it), its id, and the lane a station's failure is reported on
|
|
15
|
+
* (absent ⇒ unreported — the resumed leg reads the engine's own stations). */
|
|
16
|
+
export interface StationAnswerCall {
|
|
17
|
+
readonly toolName: string;
|
|
18
|
+
readonly toolCallId?: string;
|
|
19
|
+
readonly report?: (err: Error) => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Receive and read a station's answer — the ONE reading for every reader. The answer is received whole ({@link receivedAnswer}:
|
|
23
|
+
* the engine's one receipt for a caller's object, then each member by its kind — none of the caller's code runs) into the engine's
|
|
24
|
+
* copy every later read is of; an answer that is not received (an accessor, a member its prototype or a non-enumerable property
|
|
25
|
+
* supplies, a Proxy anywhere in it, a word that is no string, a mark that is not `true`, not an object) is not believed. Then, in order:
|
|
26
|
+
* · `refuse` stands, whatever beside it: no reading is stricter;
|
|
27
|
+
* · the station's SILENCE — the finding `unanswered`, or nothing found (`none`) beside a silence mark (`environmentSilent`,
|
|
28
|
+
* `rowsUnread`): the station made no finding, whatever disposition it spelled beside it (the engine's own station spells a
|
|
29
|
+
* vouch beside unread rows as `unanswered`);
|
|
30
|
+
* · a disposition the decision table does not give the finding on any seat, under any form of the question
|
|
31
|
+
* ({@link dispositionsOfRow}) — a `vouch` beside a deny hit, an ask beside one, a word outside the closed sets — is NOT believed:
|
|
32
|
+
* the station's answer contradicts its own finding, and the reader refuses the call;
|
|
33
|
+
* · otherwise the disposition, as the table gives it.
|
|
34
|
+
* A silence mark beside a finding the station DID make is not the silence: it classes that finding's question
|
|
35
|
+
* ({@link silencedBy}).
|
|
36
|
+
*/
|
|
37
|
+
export declare function readStationAnswer(answer: ShellReadVerdict, call: StationAnswerCall): StationAnswerReading;
|
|
4
38
|
/** Whose silence a station's answer carries, each its own class on the question it keeps or mints: unread rows are a rule that could
|
|
5
39
|
* not be read (the rule lane's class); the finding `unanswered` is, unless rows are unread, the environment's silence — shell text the
|
|
6
40
|
* engine cannot read (a guard) — and the marks only pick whose. Either is a question only a person answers; `probe` follows the finding. */
|
|
@@ -12,10 +46,10 @@ export declare function silencedBy(verdict: ShellReadVerdict, d: {
|
|
|
12
46
|
guards?: readonly AskGuard[];
|
|
13
47
|
requiresRealApproval?: true;
|
|
14
48
|
};
|
|
15
|
-
/** The
|
|
16
|
-
* spell `vouch` or `
|
|
17
|
-
* gains the silence's class; an allow becomes the silence's question (`ask.message`), one only a person
|
|
18
|
-
* no `probe` is stamped and no grant is offered — the station made no finding. */
|
|
49
|
+
/** The station's SILENCE ({@link readStationAnswer}), applied once, before any disposition arm: a station — a host-mounted one
|
|
50
|
+
* included — may spell `vouch`, `mandated` or `ordinary` beside it, and no word vouches for what nobody judged. A deny stands; an
|
|
51
|
+
* ask keeps its words and gains the silence's class; an allow becomes the silence's question (`ask.message`), one only a person
|
|
52
|
+
* answers. Nothing is admitted, no `probe` is stamped and no grant is offered — the station made no finding. */
|
|
19
53
|
export declare function applyUnansweredSilence(pass: GatePass, verdict: ShellReadVerdict, ask: {
|
|
20
54
|
readonly message: string;
|
|
21
55
|
readonly account: string | undefined;
|