@sema-agent/core 7.4.0 → 7.5.1
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 +69 -0
- package/dist/agents/cascade.d.ts +2 -1
- package/dist/agents/peer-notices.d.ts +11 -1
- package/dist/agents/peer-session-drain.js +2 -0
- package/dist/agents/verify.d.ts +2 -1
- package/dist/core/ask-origin.d.ts +130 -0
- package/dist/core/ask-origin.js +35 -0
- package/dist/core/auto-mode-arming.d.ts +40 -1
- package/dist/core/auto-mode-arming.js +51 -3
- package/dist/core/auto-mode.d.ts +87 -10
- package/dist/core/auto-mode.js +34 -6
- package/dist/core/checkpoint-store.d.ts +41 -6
- package/dist/core/checkpoint-store.js +8 -0
- package/dist/core/hooks.d.ts +63 -19
- package/dist/core/hooks.js +37 -16
- package/dist/core/mcp.d.ts +47 -55
- package/dist/core/mcp.js +89 -31
- package/dist/core/park-selfcheck.js +3 -0
- package/dist/core/permission-rule-consent.d.ts +2 -11
- package/dist/core/permission-rule-consent.js +13 -62
- package/dist/core/permission-rule-org.d.ts +70 -54
- package/dist/core/permission-rule-org.js +47 -61
- package/dist/core/permission-rule-provider.d.ts +162 -0
- package/dist/core/permission-rule-provider.js +148 -0
- package/dist/core/permission-rule-session.d.ts +23 -19
- package/dist/core/permission-rule-session.js +5 -5
- package/dist/core/permission-rule-store.d.ts +46 -21
- package/dist/core/permission-rule-store.js +13 -6
- package/dist/core/permission-rule-sync.d.ts +2 -1
- package/dist/core/permission-rule-sync.js +11 -0
- package/dist/core/runner/assemble-result.d.ts +3 -2
- package/dist/core/runner/checkpoint-scope.d.ts +32 -0
- package/dist/core/runner/checkpoint-scope.js +4 -0
- package/dist/core/runner/contracts.d.ts +1878 -0
- package/dist/core/runner/contracts.js +1 -0
- package/dist/core/runner/denial-limit-arms.d.ts +57 -31
- package/dist/core/runner/denial-limit-arms.js +42 -17
- package/dist/core/runner/derived-route-fallback.d.ts +34 -0
- package/dist/core/runner/derived-route-fallback.js +16 -0
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +1 -1
- package/dist/core/runner/prepare-announce-once.d.ts +83 -0
- package/dist/core/runner/prepare-announce-once.js +105 -0
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +170 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +255 -0
- package/dist/core/runner/prepare-config-doors.d.ts +2 -10
- package/dist/core/runner/prepare-defer-classify.d.ts +86 -0
- package/dist/core/runner/prepare-defer-classify.js +107 -0
- package/dist/core/runner/prepare-delegation-surface.d.ts +104 -0
- package/dist/core/runner/prepare-delegation-surface.js +144 -0
- package/dist/core/runner/prepare-execution-env.d.ts +54 -0
- package/dist/core/runner/prepare-execution-env.js +86 -0
- package/dist/core/runner/prepare-file-history.d.ts +95 -0
- package/dist/core/runner/prepare-file-history.js +383 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +6 -8
- package/dist/core/runner/prepare-hands-readface.js +3 -3
- package/dist/core/runner/prepare-inherited-gate.d.ts +268 -0
- package/dist/core/runner/prepare-inherited-gate.js +266 -0
- package/dist/core/runner/prepare-listings.d.ts +77 -0
- package/dist/core/runner/prepare-listings.js +76 -0
- package/dist/core/runner/prepare-lsp.d.ts +55 -0
- package/dist/core/runner/prepare-lsp.js +27 -0
- package/dist/core/runner/prepare-memory.d.ts +1 -1
- package/dist/core/runner/prepare-offload-wrappers.d.ts +62 -0
- package/dist/core/runner/prepare-offload-wrappers.js +45 -0
- package/dist/core/runner/prepare-permission-rules.d.ts +132 -0
- package/dist/core/runner/prepare-permission-rules.js +140 -0
- package/dist/core/runner/prepare-project-context.d.ts +131 -0
- package/dist/core/runner/prepare-project-context.js +150 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +138 -0
- package/dist/core/runner/prepare-prompt-inputs.js +141 -0
- package/dist/core/runner/prepare-protocol-tools.d.ts +91 -0
- package/dist/core/runner/prepare-protocol-tools.js +182 -0
- package/dist/core/runner/prepare-question-face.d.ts +119 -0
- package/dist/core/runner/prepare-question-face.js +83 -0
- package/dist/core/runner/prepare-run-refs.d.ts +89 -0
- package/dist/core/runner/prepare-run-refs.js +39 -0
- package/dist/core/runner/prepare-safety-scan.d.ts +3 -2
- package/dist/core/runner/prepare-task.d.ts +11 -1815
- package/dist/core/runner/prepare-task.js +138 -2542
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +111 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +219 -0
- package/dist/core/runner/prepare-wiring-manifest.d.ts +184 -0
- package/dist/core/runner/prepare-wiring-manifest.js +240 -0
- package/dist/core/runner/prepare-workspace-restore.d.ts +1 -27
- package/dist/core/runner/prepare-workspace-restore.js +1 -22
- package/dist/core/runner/rollback-stack.d.ts +32 -0
- package/dist/core/runner/rollback-stack.js +30 -0
- package/dist/core/runner/runtask.d.ts +11 -2
- package/dist/core/runner/runtask.js +27 -9
- package/dist/core/runner/workspace-path.d.ts +33 -0
- package/dist/core/runner/workspace-path.js +22 -0
- package/dist/core/sensitive-path-policy.d.ts +16 -0
- package/dist/core/sensitive-path-policy.js +1 -1
- package/dist/core/tool-policy.d.ts +57 -9
- package/dist/core/tool-policy.js +11 -0
- package/dist/core/types.d.ts +63 -51
- package/dist/core/wiring-manifest.d.ts +40 -3
- package/dist/core/wiring-manifest.js +4 -3
- package/dist/core/write-protect.d.ts +13 -2
- package/dist/core/write-protect.js +58 -29
- package/dist/engine/harness/types.d.ts +38 -16
- package/dist/engine/harness/types.js +25 -1
- package/dist/engine/session/session.d.ts +3 -11
- package/dist/index.d.ts +10 -6
- package/dist/index.js +9 -5
- package/dist/internal/harness.d.ts +1 -0
- package/dist/stores/file/adoption/adopt.d.ts +1 -1
- package/dist/stores/file/adoption/marker.d.ts +1 -1
- package/dist/stores/file/permission-rule-adopt.js +4 -3
- package/dist/stores/file/permission-rule-store.d.ts +65 -25
- package/dist/stores/file/permission-rule-store.js +215 -37
- package/dist/stores/file/task-list-store.d.ts +1 -1
- package/dist/tools/fs/read-face.d.ts +1 -1
- package/package.json +8 -2
- package/test/export-surface.snapshot.json +76 -28
|
@@ -32,7 +32,7 @@ export const RECOMMENDED_SENSITIVE_PATTERNS = [
|
|
|
32
32
|
".zsh_history",
|
|
33
33
|
];
|
|
34
34
|
const CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32";
|
|
35
|
-
function dataRootSpellings(explicit) {
|
|
35
|
+
export function dataRootSpellings(explicit) {
|
|
36
36
|
const raw = explicit ?? process.env.AGENT_DATA_DIR ?? join(homedir(), ".ai-agent");
|
|
37
37
|
const home = homedir();
|
|
38
38
|
const tilde = expandHomeTilde(raw, home);
|
|
@@ -211,14 +211,18 @@ export type PermissionResult = {
|
|
|
211
211
|
/** #548 (additive): this ask is the classifier DENIAL-LIMIT fallback — the block that reached a
|
|
212
212
|
* bound (CC 2.1.250 `FO`, count-then-judge) handed to a person instead of being denied.
|
|
213
213
|
* ENGINE-STAMPED beside `requiresRealApproval: true` and `decisionReason: "classifier"` at the
|
|
214
|
-
* classifier block sites (the gate's own and the inherited-lane arms); carries the
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
|
|
214
|
+
* classifier block sites (the gate's own and the inherited-lane arms); carries the COUNTS ONLY —
|
|
215
|
+
* the auto-deny window is a fact of the ask's ROUTE, so this type has no member to hold one
|
|
216
|
+
* ({@link import("./auto-mode.js").UnarmedDenialLimitFallback}) and the window appears first on
|
|
217
|
+
* the {@link AskRequest} the mint station builds, iff that request goes to a live function
|
|
218
|
+
* approver. Consumers: the gate's classifier step refuses to re-judge an ask carrying it (the
|
|
219
|
+
* classifier already spoke; rules and classifier stand BEFORE this ask, never after), and the ask
|
|
220
|
+
* resolver arms its deadline from the REQUEST's `autoDenyAfterMs`. Carried onto the approval
|
|
221
|
+
* request through one of the two route stations. A policy that self-declares it only ever opts
|
|
222
|
+
* its own ask OUT of classifier resolution and INTO the fallback's bounded lane (tightening — the
|
|
223
|
+
* same safe direction as `matchedAskRule`); it cannot state a window of its own, because the
|
|
224
|
+
* route it would be a window for has not been chosen yet. */
|
|
225
|
+
denialLimitFallback?: import("./auto-mode.js").UnarmedDenialLimitFallback;
|
|
222
226
|
/** #144 disclosure (additive): a persisted allow rule MATCHED this call but could not clear the
|
|
223
227
|
* ask, because the ask is MANDATED (operator shellGate:"always", or the tool's own
|
|
224
228
|
* egress/irreversibility marks) rather than a classifier's hesitation — "allow rules silence
|
|
@@ -277,6 +281,17 @@ export type PermissionResult = {
|
|
|
277
281
|
* value nobody stamps over can only shrink or annotate the card's ADVISORY offers — every
|
|
278
282
|
* offered rule still passes the validator at redemption. */
|
|
279
283
|
segmentCoverage?: readonly import("./permission-rule-model.js").SegmentCoverage[];
|
|
284
|
+
/** WHICH AUTHORITY raised this ask — one word from the closed {@link import("./ask-origin.js").AskOrigin}
|
|
285
|
+
* set (org rule / org unavailable / hook / explicit ask rule / the classifier's denial-limit
|
|
286
|
+
* fallback / the reserved question tool / a marked call / plain policy). ENGINE-STAMPED inside the
|
|
287
|
+
* gate at the same single stamp point as `ruleEvidence`, derived from what the gate's own layers
|
|
288
|
+
* did ({@link import("./ask-origin.js").askOriginOf}) — a policy that self-declares it is overwritten
|
|
289
|
+
* there, so the member is never a channel a policy can loosen through. Consumers: the gate's
|
|
290
|
+
* auto-mode classifier step admits an ask by ONE table lookup on this word
|
|
291
|
+
* ({@link import("./ask-origin.js").classifierMayAnswer}) instead of a chain of per-kind
|
|
292
|
+
* exclusions; an approval surface may render the card by it. Optional on the TYPE only because a
|
|
293
|
+
* decision is caller-composable; every ask the gate hands to its resolver carries it. */
|
|
294
|
+
origin?: import("./ask-origin.js").AskOrigin;
|
|
280
295
|
} | {
|
|
281
296
|
action: "deny";
|
|
282
297
|
updatedInput?: unknown;
|
|
@@ -1190,8 +1205,25 @@ export interface AskRequest {
|
|
|
1190
1205
|
* this ask's own auto-deny window. Two readers: a card renders it as a countdown; {@link resolveAsk}
|
|
1191
1206
|
* arms its deadline from `autoDenyAfterMs` (> 0 ⇒ an unanswered function approver auto-denies at
|
|
1192
1207
|
* that deadline — `settledBy:"timeout"`, `resolution:"window_expired"`, `autoDenied:true`). Filled
|
|
1193
|
-
* by the gate from the decision, never a caller/worker-settable field.
|
|
1208
|
+
* by the gate from the decision, never a caller/worker-settable field. This is the ARMED type: the
|
|
1209
|
+
* decision's member carries the counts alone, and the only two ways to reach this one are the route
|
|
1210
|
+
* stations — `AutoModeDenialTracker.armTimedWindow` (a hand-out to a live function approver, the one
|
|
1211
|
+
* writer of a non-zero window) and `unarmedWindow` (every route that cannot count down). An adapter
|
|
1212
|
+
* that forwards a decision's member here does not compile, which is the point: the window is the
|
|
1213
|
+
* route's fact, and forwarding it silently is how a configured window becomes a wait with none. */
|
|
1194
1214
|
readonly denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
1215
|
+
/** (additive) WHICH AUTHORITY raised this ask — the wire twin of the {@link PermissionResult} ask-arm
|
|
1216
|
+
* member of the same name, one word from the closed {@link import("./ask-origin.js").AskOrigin} set.
|
|
1217
|
+
* The gate's own mint station copies the engine-stamped word; the three inherited-lane stations
|
|
1218
|
+
* derive it from the ancestor's decision under the station facts (a self-declared value on that
|
|
1219
|
+
* decision is never read). An approver renders the card by it; a durable park persists the same word
|
|
1220
|
+
* on its row (`PendingAction.tool_approval.origin`). Filled by the mint station, never written by a
|
|
1221
|
+
* caller/worker directly — a policy can only STEER the derivation toward a stricter class by
|
|
1222
|
+
* self-declaring one of its tighten-only inputs (`decisionReason: "hook"`, `matchedAskRule`,
|
|
1223
|
+
* `denialLimitFallback`), never toward `policy`; a card that attributes an ask to a hook or a rule is
|
|
1224
|
+
* therefore reading the deployment's own claim about its own ask, which is the direction that cannot
|
|
1225
|
+
* loosen anything. */
|
|
1226
|
+
readonly origin?: import("./ask-origin.js").AskOrigin;
|
|
1195
1227
|
/** Engine-judged risk axes of the action awaiting approval, for the human decision surface: a
|
|
1196
1228
|
* coarse `requiresRealApproval` cannot tell "cannot be undone" from "data leaves the machine",
|
|
1197
1229
|
* and those call for different scrutiny. Filled by the gate from the SAME resolved axes the
|
|
@@ -1383,6 +1415,22 @@ export declare function withDelegationProvenance(onAsk: OnAsk, delegation: AskDe
|
|
|
1383
1415
|
* consultation". A non-wrapper value (including `"allow"`/`"deny"`/undefined) is its own identity.
|
|
1384
1416
|
*/
|
|
1385
1417
|
export declare function askApproverIdentity(onAsk: unknown): unknown;
|
|
1418
|
+
/**
|
|
1419
|
+
* Is this `onAsk` seat a LIVE APPROVER — a person the run can actually reach?
|
|
1420
|
+
*
|
|
1421
|
+
* Only a function is. A blanket `"allow"`/`"deny"` string is a policy setting (the same reading the
|
|
1422
|
+
* interaction-posture door gives it), and an absent seat is nobody at all; neither can answer an ask, so
|
|
1423
|
+
* neither may outrank a durable park.
|
|
1424
|
+
*
|
|
1425
|
+
* ONE definition on purpose. Two sites must agree on this — the run's own sync-first decline, and the
|
|
1426
|
+
* mandate frozen into the chain its children inherit — and when they were two hand-written booleans they
|
|
1427
|
+
* drifted: the decline narrowed to the function test while the freeze kept testing mere presence, and
|
|
1428
|
+
* each still read correctly on its own, so nothing pointed at the pair. A blanket seat was therefore not
|
|
1429
|
+
* an approver for the parent's asks and an approver for its children's. Declared here, in the vocabulary
|
|
1430
|
+
* beside {@link askApproverIdentity}, because the two sites are now two prepare phases (the inherited-gate
|
|
1431
|
+
* fold and the wiring station) plus the orchestrator, and each reaches DOWN for the one definition.
|
|
1432
|
+
*/
|
|
1433
|
+
export declare function isLiveApproverSeat(onAsk: OnAsk | undefined): boolean;
|
|
1386
1434
|
/** EXACT structural clone for the approval boundary (alias isolation) — fail-closed, never lossy,
|
|
1387
1435
|
* never identity: a JSON round-trip would silently DROP unclonable members (presented ≠ executing =
|
|
1388
1436
|
* consent misrepresentation) and an identity fallback would hand the approver the live executing
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -133,6 +133,10 @@ export function constraintChainDigest(chain) {
|
|
|
133
133
|
}
|
|
134
134
|
const ALLOW = { action: "allow" };
|
|
135
135
|
const RETIRED_TEXT_FIELD = "reason";
|
|
136
|
+
const RETIRED_WINDOW_MEMBER = "autoDenyAfterMs";
|
|
137
|
+
const RETIRED_WINDOW_MEMBER_DENY_MESSAGE = `a permission decision's "denialLimitFallback" carries "${RETIRED_WINDOW_MEMBER}" — the decision names the counts only; ` +
|
|
138
|
+
`the auto-deny window is decided by the ask's route (a live approver arms it, every other route runs none) and cannot be ` +
|
|
139
|
+
`declared by a policy; denied fail-closed rather than silently replacing the declared number`;
|
|
136
140
|
const RETIRED_TEXT_FIELD_DENY_MESSAGE = `a permission decision carries the retired "${RETIRED_TEXT_FIELD}" field — rename it to "message" (the one text field ` +
|
|
137
141
|
`a decision carries); denied fail-closed rather than executing a decision whose text this layer cannot read`;
|
|
138
142
|
export function refuseOutOfContractDecision(d, opts) {
|
|
@@ -141,6 +145,10 @@ export function refuseOutOfContractDecision(d, opts) {
|
|
|
141
145
|
if (Object.prototype.hasOwnProperty.call(d, RETIRED_TEXT_FIELD)) {
|
|
142
146
|
return { action: "deny", message: RETIRED_TEXT_FIELD_DENY_MESSAGE, decisionReason: "rule" };
|
|
143
147
|
}
|
|
148
|
+
const fb = d.denialLimitFallback;
|
|
149
|
+
if (typeof fb === "object" && fb !== null && fb.autoDenyAfterMs !== undefined) {
|
|
150
|
+
return { action: "deny", message: RETIRED_WINDOW_MEMBER_DENY_MESSAGE, decisionReason: "rule" };
|
|
151
|
+
}
|
|
144
152
|
if (opts?.reasonIsNonInput === true)
|
|
145
153
|
return d;
|
|
146
154
|
const dr = d.decisionReason;
|
|
@@ -839,6 +847,9 @@ export function withDelegationProvenance(onAsk, delegation) {
|
|
|
839
847
|
export function askApproverIdentity(onAsk) {
|
|
840
848
|
return typeof onAsk === "function" ? (delegatedApproverRoot.get(onAsk) ?? onAsk) : onAsk;
|
|
841
849
|
}
|
|
850
|
+
export function isLiveApproverSeat(onAsk) {
|
|
851
|
+
return typeof onAsk === "function";
|
|
852
|
+
}
|
|
842
853
|
export function tryCloneArgs(v) {
|
|
843
854
|
try {
|
|
844
855
|
const value = structuredClone(v);
|
package/dist/core/types.d.ts
CHANGED
|
@@ -662,7 +662,7 @@ export interface ToolExecuteContext {
|
|
|
662
662
|
* at all; a park under a screening entry announces the requirement on `onError`
|
|
663
663
|
* (classification `screening-constraint-in-durable-chain`) at park time.
|
|
664
664
|
*/
|
|
665
|
-
inheritedGate?: import("./runner/
|
|
665
|
+
inheritedGate?: import("./runner/contracts.js").InheritedGate;
|
|
666
666
|
};
|
|
667
667
|
/**
|
|
668
668
|
* design/176 — the peer-chain SEED for a message-driven (tier-3) revival: the hop chain of the
|
|
@@ -1046,7 +1046,7 @@ export interface ToolExecuteContext {
|
|
|
1046
1046
|
* read-only, NEVER a model/tool argument (same posture as {@link activeSkillScope}). Undefined when the
|
|
1047
1047
|
* tool runs outside a Runner task.
|
|
1048
1048
|
*/
|
|
1049
|
-
inheritedGateForChildren?: () => import("./runner/
|
|
1049
|
+
inheritedGateForChildren?: () => import("./runner/contracts.js").InheritedGate;
|
|
1050
1050
|
/**
|
|
1051
1051
|
* design/180 half A — the delegation runtime-provenance ARMING face. Runner-filled; a delegation
|
|
1052
1052
|
* tool calls it at spawn time: a non-undefined return means this (parent) run is armed (it mounts
|
|
@@ -1492,6 +1492,14 @@ export interface AgentDefinition {
|
|
|
1492
1492
|
export interface McpServerSpec {
|
|
1493
1493
|
/** Stable name; tool names are namespaced as `<name>__<tool>` to avoid collisions. */
|
|
1494
1494
|
name: string;
|
|
1495
|
+
/**
|
|
1496
|
+
* Where this declaration CAME FROM, in the declaring side's own words (a shell's `plugin` / `user` /
|
|
1497
|
+
* `project` / `local` config tiers, or anything else the deployment finds useful). Core reads
|
|
1498
|
+
* nothing off it: it is echoed verbatim (single-line, length-bounded) onto the wiring manifest's
|
|
1499
|
+
* per-server read face (`WiringManifest.mcp[].source`) so a consumer can group servers by their
|
|
1500
|
+
* declaring tier without keeping a side table keyed on names. Optional; absent stays absent.
|
|
1501
|
+
*/
|
|
1502
|
+
source?: string;
|
|
1495
1503
|
transport: {
|
|
1496
1504
|
kind: "stdio";
|
|
1497
1505
|
command: string;
|
|
@@ -4495,6 +4503,22 @@ export type TaskEvent = ({
|
|
|
4495
4503
|
* `settledBy`/reason instead). A consumer must not read a semantic out of the absence.
|
|
4496
4504
|
*/
|
|
4497
4505
|
resolution?: import("./tool-policy.js").AskDenyResolution;
|
|
4506
|
+
/**
|
|
4507
|
+
* #557 (additive) — `true` ⇔ this frame closes a call the classifier DENIAL-LIMIT fallback ask
|
|
4508
|
+
* auto-denied itself: the ask's own window (`AskRequest.denialLimitFallback.autoDenyAfterMs`,
|
|
4509
|
+
* 120 s by default) elapsed with nobody answering. Such a frame also reads `settledBy:"timeout"`
|
|
4510
|
+
* + `resolution:"window_expired"` — the SAME two words the approval factory's own window
|
|
4511
|
+
* (`createApprovalPolicy.approvalTimeoutMs`) settles with — and this bit is the discriminant: the
|
|
4512
|
+
* factory's deadline never carries it, so a shell that sees the pair without it is looking at the
|
|
4513
|
+
* host-configured approval window having elapsed, and with it at the classifier fallback's own
|
|
4514
|
+
* countdown having run out. A DURABLE park's expiry is neither: it settles on the decide lane with
|
|
4515
|
+
* its own `settledBy`/reason and carries no `resolution` word at all (see that field's doc above).
|
|
4516
|
+
* Minted at the resolver's deadline arm and carried on
|
|
4517
|
+
* the engine-owned settlement sideband (never read out of the tool result, never writable by a
|
|
4518
|
+
* tool or policy — a marker on an executing call is dropped with a defect reported). ABSENT on
|
|
4519
|
+
* everything else; a consumer must not read a semantic out of the absence.
|
|
4520
|
+
*/
|
|
4521
|
+
autoDenied?: true;
|
|
4498
4522
|
/**
|
|
4499
4523
|
* design/252 G-7 — WHOSE settlement that was: the identifier the approval channel reported for
|
|
4500
4524
|
* the party that ended this wait, beside the {@link settledBy} word that says what KIND of end
|
|
@@ -6872,64 +6896,50 @@ export interface RunnerDeps {
|
|
|
6872
6896
|
*/
|
|
6873
6897
|
sessionPolicyStore?: import("./session-policy-store.js").SessionPolicyStore;
|
|
6874
6898
|
/**
|
|
6875
|
-
* design/
|
|
6876
|
-
*
|
|
6877
|
-
*
|
|
6878
|
-
*
|
|
6879
|
-
*
|
|
6880
|
-
*
|
|
6881
|
-
*
|
|
6882
|
-
*
|
|
6883
|
-
* pinned by a registered-caller scan
|
|
6884
|
-
* mints a rule around consent.
|
|
6885
|
-
*
|
|
6886
|
-
*
|
|
6887
|
-
*
|
|
6888
|
-
*
|
|
6889
|
-
*
|
|
6890
|
-
*
|
|
6891
|
-
|
|
6892
|
-
|
|
6899
|
+
* design/389 — the ONE permission-rule seam: the unified store, partitioned by source
|
|
6900
|
+
* (`org | user | project | session`), built by `createPermissionRuleStoreProvider({ durable?, org?, session? })`.
|
|
6901
|
+
*
|
|
6902
|
+
* It is the one seam in this file that LOOSENS, and it is shaped so it can only do so within limits the
|
|
6903
|
+
* engine holds. The provider hands out a store whose ONE read query answers every partition with each
|
|
6904
|
+
* row's source and effective status; the write face is an exported BACKEND CONTRACT for the durable
|
|
6905
|
+
* partition (an out-of-repo store twin builds against the same definitions instead of mirroring them),
|
|
6906
|
+
* but the ENGINE reaches it only on the consent lanes — redemption of a confirmed human decision, the
|
|
6907
|
+
* tighten-delete, and the sync join — a wiring invariant pinned by a registered-caller scan
|
|
6908
|
+
* (test/permission-rule-writer-callers); no exported convenience mints a rule around consent. Personal
|
|
6909
|
+
* rules are consumed post-fold, in the gate's ask branch, and never resolve an ask carrying
|
|
6910
|
+
* `requiresRealApproval` or one a PreToolUse hook raised.
|
|
6911
|
+
*
|
|
6912
|
+
* The ORG partition (present iff the provider was composed with an `org` config — the governance
|
|
6913
|
+
* declaration, design/182 §7.4) is the outermost TIGHTENING authority: every tool call is adjudicated
|
|
6914
|
+
* against the org's published deny/ask snapshot BEFORE the ask-resolution chain (org deny > org ask >
|
|
6915
|
+
* personal allow rule > bare ask), and while the org partition cannot adjudicate the WHOLE decision
|
|
6916
|
+
* boundary fails closed — every terminal allow becomes a real-approval ask and both ask→allow seams are
|
|
6917
|
+
* disarmed. The SESSION partition (present iff composed with one) is where `{kind:"session"}` grants
|
|
6918
|
+
* land and are read from, spliced in front of the durable rows on every adjudication of that session.
|
|
6919
|
+
*
|
|
6920
|
+
* Omitted ⇒ no rule lane, no org layer, no session source: no rules are read, no field is added to any
|
|
6921
|
+
* ask, and the decision path is byte-identical to a build without it. An unauthenticated task (no
|
|
6922
|
+
* `principal`) resolves to zero personal rules rather than to a shared bucket (the org partition still
|
|
6923
|
+
* adjudicates), and a store whose durable partition cannot be read yields zero rules plus a disclosure
|
|
6924
|
+
* — a loosening face fails toward asking.
|
|
6925
|
+
*/
|
|
6926
|
+
permissionRuleStore?: import("./permission-rule-provider.js").PermissionRuleStoreProvider;
|
|
6893
6927
|
/**
|
|
6894
6928
|
* design/182 §4.5 (F-011) — declare that this deployment keeps its permission rules in the
|
|
6895
6929
|
* IDENTITY-LESS local bucket: a task with no `principal` resolves rules through the provider's
|
|
6896
6930
|
* `forLocalOwner()` face instead of resolving to zero rules.
|
|
6897
6931
|
*
|
|
6898
|
-
* A DECLARATION, never an inference
|
|
6899
|
-
*
|
|
6900
|
-
*
|
|
6901
|
-
*
|
|
6902
|
-
*
|
|
6932
|
+
* A DECLARATION, never an inference: the local bucket is "this machine's owner", a fact only the
|
|
6933
|
+
* deployment knows. Omitted/`false` ⇒ an unauthenticated task reads no rules. Declared `true` while the
|
|
6934
|
+
* wired provider has no local-owner face (or while no provider is wired at all) is a configuration
|
|
6935
|
+
* contradiction and is refused loudly at prepare, rather than silently degrading to "the rules this
|
|
6936
|
+
* person approved stopped applying".
|
|
6903
6937
|
*
|
|
6904
6938
|
* A local-owner bucket cannot cloud-sync (syncing is an authenticated act); it is adopted into a
|
|
6905
6939
|
* principal bucket by `adoptFilePermissionRuleStore`, after which the local-owner face resolves the
|
|
6906
6940
|
* adopted principal's bucket forever.
|
|
6907
6941
|
*/
|
|
6908
6942
|
localOwnerRules?: boolean;
|
|
6909
|
-
/**
|
|
6910
|
-
* design/382 §4.3 — the SESSION-RULE OVERLAY: where `{kind:"session"}` scoped allow rules live
|
|
6911
|
-
* (the session's own state, never the persisted store). Wired, the gate's persisted-rule lane
|
|
6912
|
-
* splices the CURRENT session's overlay rows in front of the store's `list()` on every
|
|
6913
|
-
* adjudication — session > project > global, the narrowest-first reporting order — and threads the
|
|
6914
|
-
* session's identity as the eligibility context's third axis; a host lands rows into it through
|
|
6915
|
-
* the consent protocol (`RuleConsentDeps.sessionRules`, a session-scope candidate's redemption).
|
|
6916
|
-
* Omitted ⇒ the session dimension does not exist at this gate and the lane is byte-identical.
|
|
6917
|
-
* See `permission-rule-session.ts` for the contract and the reference implementation.
|
|
6918
|
-
*/
|
|
6919
|
-
sessionPermissionRules?: import("./permission-rule-session.js").SessionRuleOverlay;
|
|
6920
|
-
/**
|
|
6921
|
-
* design/182 §7 — the ORG rule overlay for an org-GOVERNED deployment. Constructed with
|
|
6922
|
-
* `createOrgRuleOverlay` (that constructor is the boot gate: a governed declaration with no snapshot
|
|
6923
|
-
* provider refuses to boot), so a value here IS the declaration — nothing is inferred from wiring.
|
|
6924
|
-
*
|
|
6925
|
-
* Present ⇒ every tool call is adjudicated against the org's published deny/ask snapshot BEFORE the
|
|
6926
|
-
* ask-resolution chain (org deny > org ask > personal allow rule > bare ask), and while the overlay
|
|
6927
|
-
* cannot adjudicate (never installed / past the staleness bound / a refused rollback) the WHOLE
|
|
6928
|
-
* decision boundary fails closed: every terminal allow becomes a real-approval ask and both
|
|
6929
|
-
* ask→allow seams are disarmed. Omitted ⇒ the org layer does not exist and the decision path is
|
|
6930
|
-
* byte-identical to a build without it.
|
|
6931
|
-
*/
|
|
6932
|
-
permissionRuleOrg?: import("./permission-rule-org.js").OrgRuleOverlay;
|
|
6933
6943
|
/**
|
|
6934
6944
|
* design/182 §9 — DECLARE that this deployment drives cloud sync (`syncPermissionRules`) for the
|
|
6935
6945
|
* wired rule store. Purely a disclosure input: it is reported as `permissionRules.syncWired` on the
|
|
@@ -7064,8 +7074,10 @@ export interface RunnerDeps {
|
|
|
7064
7074
|
* — the block that reaches a bound is itself the one that becomes an `ask` (the 3rd consecutive
|
|
7065
7075
|
* block asks). That ask carries `requiresRealApproval: true` (no automatic lane may clear it — not
|
|
7066
7076
|
* a sandbox admission, not an inherited resolver, not a blanket `onAsk:"allow"`) plus the additive
|
|
7067
|
-
* `denialLimitFallback` member with the counts and its own auto-deny window; an
|
|
7068
|
-
* auto-denies after `autoDenyAfterMs` (default 120s; `0` = no
|
|
7077
|
+
* `denialLimitFallback` member with the counts and its own auto-deny window; an ask handed to a LIVE
|
|
7078
|
+
* function approver and left unanswered auto-denies after `autoDenyAfterMs` (default 120s; `0` = no
|
|
7079
|
+
* window) — the window is armed at that hand-out only, so a durably PARKED fallback carries `0`
|
|
7080
|
+
* (nothing counts down on that lane). A classifier allow, or a
|
|
7069
7081
|
* person's allow of the fallback ask, zeroes the consecutive count; reaching the total bound
|
|
7070
7082
|
* zeroes everything. With no approver wired at all (headless), the fallback has nowhere to go and
|
|
7071
7083
|
* the run STOPS with `TaskResult.errorCode = "classifier.denial_limit"` (a notice of the same
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import type { RunnerDeps, TaskSpec } from "./types.js";
|
|
24
24
|
import { type StoreDurability } from "./checkpoint-store.js";
|
|
25
|
+
import type { McpFailureCode } from "./mcp.js";
|
|
25
26
|
export type { StoreDurability };
|
|
26
27
|
/** The manifest-face projection of {@link StoreDurability}: `"declared_durable"` (the name says it —
|
|
27
28
|
* a DECLARATION relayed, not a verification) vs `"process_local"` (declared so, or fail-closed
|
|
@@ -78,6 +79,26 @@ export type ParkLaneReason = "no_checkpoint_store" | "no_durable_approval_opt_in
|
|
|
78
79
|
*/
|
|
79
80
|
export declare const AUTO_MODE_ARM_REASONS: readonly ["armed", "no_intent", "no_face", "denied", "resolver_fault", "latch_open"];
|
|
80
81
|
export type AutoModeArmReason = (typeof AUTO_MODE_ARM_REASONS)[number];
|
|
82
|
+
/**
|
|
83
|
+
* One declared MCP server's materialization result for THIS leg — the per-server connection-state
|
|
84
|
+
* read face on {@link WiringManifest.mcp}. `status` is the connect-time verdict of this leg's
|
|
85
|
+
* materialize (a snapshot, never a heartbeat — the same semantics as `MaterializedMcp.statuses`,
|
|
86
|
+
* which it is projected from): `connected` (tools listed; `toolCount` = the mounted roster size after
|
|
87
|
+
* intake), `failed` (the connect or the tool listing threw — `errorCode` names the class, `error` the
|
|
88
|
+
* neutralized, length-bounded remote text), `skipped` (declared on the spec but this leg produced no
|
|
89
|
+
* status for it — the materialization did not reach it). `source` is the DECLARING side's own label
|
|
90
|
+
* (`McpServerSpec.source`: plugin / user / project / local, or anything the deployment writes),
|
|
91
|
+
* echoed back so a shell can group servers by where the declaration came from; core reads nothing
|
|
92
|
+
* off it. Ordered as declared.
|
|
93
|
+
*/
|
|
94
|
+
export interface WiringManifestMcpEntry {
|
|
95
|
+
name: string;
|
|
96
|
+
source?: string;
|
|
97
|
+
status: "connected" | "failed" | "skipped";
|
|
98
|
+
errorCode?: McpFailureCode;
|
|
99
|
+
error?: string;
|
|
100
|
+
toolCount: number;
|
|
101
|
+
}
|
|
81
102
|
/**
|
|
82
103
|
* design/173 §2.2/§8.4 — the wiring manifest. Consumers MUST ignore unknown fields
|
|
83
104
|
* (`schemaVersion` bumps only on a semantic break of an EXISTING field). The TaskEvent face is the
|
|
@@ -178,8 +199,8 @@ export interface WiringManifest {
|
|
|
178
199
|
* reads off the manifest rather than infers. `false` on a deployment that does not sync — same
|
|
179
200
|
* present-and-false shape as `storeWired`, for the same reason. */
|
|
180
201
|
syncWired: boolean;
|
|
181
|
-
/** design/182 §7 — this deployment DECLARED org governance (
|
|
182
|
-
* constructor refuses
|
|
202
|
+
/** design/182 §7 / design/389 — this deployment DECLARED org governance (the store was composed with
|
|
203
|
+
* an `org` partition, whose constructor refuses a declaration with no snapshot source). `true` also means
|
|
183
204
|
* the fail-closed availability contract is armed: while no org snapshot can be adjudicated
|
|
184
205
|
* against, every terminal allow tightens to a real-approval ask. */
|
|
185
206
|
orgGoverned: boolean;
|
|
@@ -225,6 +246,19 @@ export interface WiringManifest {
|
|
|
225
246
|
armed: boolean;
|
|
226
247
|
reason: AutoModeArmReason;
|
|
227
248
|
};
|
|
249
|
+
/**
|
|
250
|
+
* EFFECTIVE half only, and ALWAYS present on an engine-minted effective manifest (the `autoMode`
|
|
251
|
+
* posture: every leg has an answer — an empty array is "this leg declared no servers", absence is
|
|
252
|
+
* an older mint or an external derivation). The per-server MCP connection-state read face: one
|
|
253
|
+
* {@link WiringManifestMcpEntry} per server the leg's frozen spec declared, in declaration order,
|
|
254
|
+
* projected from THIS leg's materialization (`MaterializedMcp.statuses`) — a connect-time snapshot
|
|
255
|
+
* of "did this leg connect", never a live health signal. A shell that shows per-server state
|
|
256
|
+
* (connected / failed with a cause a person can act on / tool counts, grouped by declaring
|
|
257
|
+
* `source`) reads it here instead of inferring from the roster or the failure notices. NOT part of
|
|
258
|
+
* {@link configFingerprint}: connection state is a fact about this leg's attempt, not about the
|
|
259
|
+
* assembly, and legs of one assembly must fingerprint alike whether or not a server was up.
|
|
260
|
+
*/
|
|
261
|
+
mcp?: readonly WiringManifestMcpEntry[];
|
|
228
262
|
/** EFFECTIVE half only — a short, non-sensitive fingerprint (sha256 prefix over the canonical
|
|
229
263
|
* JSON of this manifest's own resolved facts; every field here is an enum/boolean/count, no
|
|
230
264
|
* secrets) so an operator can correlate legs that ran under the same resolved assembly. */
|
|
@@ -293,10 +327,13 @@ export interface WiringFacts {
|
|
|
293
327
|
armed: boolean;
|
|
294
328
|
reason: AutoModeArmReason;
|
|
295
329
|
};
|
|
330
|
+
/** Effective half only — see {@link WiringManifest.mcp}; the static half materializes nothing.
|
|
331
|
+
* Copied entry-wise onto the manifest (the caller's array is never aliased). */
|
|
332
|
+
mcp?: readonly WiringManifestMcpEntry[];
|
|
296
333
|
}
|
|
297
334
|
/** Named view of the deps seats the static half reads (a `Pick` of the real {@link RunnerDeps} —
|
|
298
335
|
* single-source shapes, no parallel hand-copied interface). */
|
|
299
|
-
export type StaticWiringDeps = Pick<RunnerDeps, "onAsk" | "onQuestion" | "interactionPosture" | "onElicit" | "checkpointStore" | "sessionStore" | "backgroundAgentStore" | "onBackgroundChildEvent" | "lockedConfig" | "compliancePostureResolver" | "memoryScopeAdmission" | "retentionPolicy" | "permissionRuleStore" | "permissionRuleSyncWired"
|
|
336
|
+
export type StaticWiringDeps = Pick<RunnerDeps, "onAsk" | "onQuestion" | "interactionPosture" | "onElicit" | "checkpointStore" | "sessionStore" | "backgroundAgentStore" | "onBackgroundChildEvent" | "lockedConfig" | "compliancePostureResolver" | "memoryScopeAdmission" | "retentionPolicy" | "permissionRuleStore" | "permissionRuleSyncWired">;
|
|
300
337
|
/** Named view of the spec seats the static half reads (a `Pick` of the real {@link TaskSpec}). */
|
|
301
338
|
export type StaticWiringSpec = Pick<TaskSpec, "onAsk" | "onQuestion" | "checkpointStore" | "durableApproval" | "mcp" | "interactiveTools" | "interactionPosture">;
|
|
302
339
|
/**
|
|
@@ -126,9 +126,10 @@ export function deriveWiringManifest(facts) {
|
|
|
126
126
|
},
|
|
127
127
|
...(facts.half === "effective" && facts.modelGate !== undefined ? { modelGate: { class: facts.modelGate.class, removed: [...facts.modelGate.removed], restore: facts.modelGate.restore } } : {}),
|
|
128
128
|
...(facts.half === "effective" && facts.autoMode !== undefined ? { autoMode: readAutoModeFact(facts.autoMode) } : {}),
|
|
129
|
+
...(facts.half === "effective" && facts.mcp !== undefined ? { mcp: facts.mcp.map((e) => ({ ...e })) } : {}),
|
|
129
130
|
};
|
|
130
131
|
if (facts.half === "effective") {
|
|
131
|
-
const { leg: _leg, ...assembly } = manifest;
|
|
132
|
+
const { leg: _leg, mcp: _mcp, ...assembly } = manifest;
|
|
132
133
|
const { provenance: _askSeat, ...askForHash } = assembly.ask;
|
|
133
134
|
const { provenance: _questionSeat, ...questionForHash } = assembly.question;
|
|
134
135
|
manifest.configFingerprint = createHash("sha256")
|
|
@@ -211,9 +212,9 @@ export function describeStaticWiring(deps, spec = {}) {
|
|
|
211
212
|
sessionDurability: resolveDeclaredDurability(deps.sessionStore, "sessionStore"),
|
|
212
213
|
backgroundAgentStoreWired: deps.backgroundAgentStore !== undefined,
|
|
213
214
|
subagentTranscriptTier: resolveSubagentTranscriptTier(deps.backgroundAgentStore !== undefined, deps.sessionStore),
|
|
214
|
-
permissionRuleStoreWired: deps.permissionRuleStore
|
|
215
|
+
permissionRuleStoreWired: deps.permissionRuleStore?.partitions.durable === true,
|
|
215
216
|
permissionRuleSyncWired: deps.permissionRuleSyncWired === true,
|
|
216
|
-
permissionRuleOrgGoverned: deps.
|
|
217
|
+
permissionRuleOrgGoverned: deps.permissionRuleStore?.partitions.org === true,
|
|
217
218
|
hostChildEventSinkWired: deps.onBackgroundChildEvent !== undefined,
|
|
218
219
|
lockedConfigWired: deps.lockedConfig !== undefined,
|
|
219
220
|
complianceWired: deps.compliancePostureResolver !== undefined,
|
|
@@ -57,8 +57,16 @@ export interface WriteProtectionMatcher {
|
|
|
57
57
|
* Compile the write-protection judge for a deployment configuration. Returns `undefined` when the
|
|
58
58
|
* resolved table is EMPTY (`[]` replacement) — the caller then mounts no tighten at all, keeping an
|
|
59
59
|
* opted-out deployment's decision path byte-identical to a build without this layer.
|
|
60
|
+
*
|
|
61
|
+
* `opts.dataRoot` names the engine data root for the passthrough in the module header (same seat and
|
|
62
|
+
* same meaning as `createSensitivePathPolicy`'s): omitted ⇒ the knob its consumers read
|
|
63
|
+
* (`$AGENT_DATA_DIR ?? ~/.ai-agent`), read ONCE here at compile so the judge stays pure and
|
|
64
|
+
* synchronous. The passthrough is a property of the JUDGE, not of the default table — a deployment
|
|
65
|
+
* that replaces the table wholesale gets the same law over its own rows.
|
|
60
66
|
*/
|
|
61
|
-
export declare function compileWriteProtection(entries?: readonly WriteProtectedEntry[]
|
|
67
|
+
export declare function compileWriteProtection(entries?: readonly WriteProtectedEntry[], opts?: {
|
|
68
|
+
dataRoot?: string;
|
|
69
|
+
}): WriteProtectionMatcher | undefined;
|
|
62
70
|
/**
|
|
63
71
|
* Build the ENGINE-FILLED gate input (`ToolGateInput.writeProtectionCheck`): covered tools are the
|
|
64
72
|
* path-confinable write set (Write/Edit/NotebookEdit — the shared spelling), the judged target is
|
|
@@ -69,5 +77,8 @@ export declare function compileWriteProtection(entries?: readonly WriteProtected
|
|
|
69
77
|
* the tool's own schema validation refuses it before any write); the fail-closed treatment of the
|
|
70
78
|
* unresolvable case belongs to the containment gates (fs-write-gate-policy documents that split).
|
|
71
79
|
* Returns `undefined` when the resolved table is empty — nothing to judge, mount nothing.
|
|
80
|
+
* `opts.dataRoot` is the judge's own seat, passed straight through ({@link compileWriteProtection}).
|
|
72
81
|
*/
|
|
73
|
-
export declare function createWriteProtectionCheck(entries?: readonly WriteProtectedEntry[]
|
|
82
|
+
export declare function createWriteProtectionCheck(entries?: readonly WriteProtectedEntry[], opts?: {
|
|
83
|
+
dataRoot?: string;
|
|
84
|
+
}): ((toolName: string, args: unknown) => WriteProtectedHit | null) | undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { writeTargetPath } from "../tools/fs/safety.js";
|
|
1
|
+
import { isWinFormPath, writeTargetPath, isUncPath } from "../tools/fs/safety.js";
|
|
2
2
|
import { PATH_CONFINABLE_WRITE_TOOLS } from "./runner/session-rule-policy.js";
|
|
3
|
+
import { dataRootSpellings } from "./sensitive-path-policy.js";
|
|
3
4
|
const freezeTable = (rows) => Object.freeze(rows.map((r) => Object.freeze(r)));
|
|
4
5
|
export const WRITE_PROTECTED_DEFAULT_TABLE = freezeTable([
|
|
5
6
|
{ name: ".gitconfig", kind: "basename" },
|
|
@@ -51,6 +52,8 @@ export const WRITE_PROTECTED_DEFAULT_TABLE = freezeTable([
|
|
|
51
52
|
{ name: ".config/git", kind: "segment-run" },
|
|
52
53
|
{ name: ".ssh", kind: "segment" },
|
|
53
54
|
{ name: ".gnupg", kind: "segment" },
|
|
55
|
+
{ name: ".sema.json", kind: "basename" },
|
|
56
|
+
{ name: ".sema", kind: "segment" },
|
|
54
57
|
]);
|
|
55
58
|
export function foldWriteProtectCase(s) {
|
|
56
59
|
return s.toLowerCase().replace(/ı/g, "i").replace(/ſ/g, "s");
|
|
@@ -110,10 +113,56 @@ export function resolveWriteProtectedTable(entries) {
|
|
|
110
113
|
}
|
|
111
114
|
return freezeTable(out);
|
|
112
115
|
}
|
|
113
|
-
|
|
116
|
+
function pathViewSegments(path, win32, seps = BOTH_SEPARATOR_FAMILIES, anchorFloor = 0) {
|
|
117
|
+
const segs = [];
|
|
118
|
+
for (const raw of path.split(seps)) {
|
|
119
|
+
if (raw.length === 0)
|
|
120
|
+
continue;
|
|
121
|
+
const dot = !win32 || /^\.{1,2}$/.test(raw) ? raw : raw.replace(/ +$/, "");
|
|
122
|
+
if (dot === ".")
|
|
123
|
+
continue;
|
|
124
|
+
if (dot === "..") {
|
|
125
|
+
const last = segs[segs.length - 1];
|
|
126
|
+
if (segs.length > anchorFloor && last !== "..")
|
|
127
|
+
segs.pop();
|
|
128
|
+
else if (segs.length > anchorFloor || anchorFloor === 0)
|
|
129
|
+
segs.push("..");
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const stripped = win32 ? raw.replace(/[. ]+$/, "") : raw;
|
|
133
|
+
segs.push(stripped.length > 0 ? stripped : raw);
|
|
134
|
+
}
|
|
135
|
+
return segs;
|
|
136
|
+
}
|
|
137
|
+
const BOTH_SEPARATOR_FAMILIES = /[\\/]/;
|
|
138
|
+
const POSIX_SEPARATOR_FAMILY = /\//;
|
|
139
|
+
function uncAnchorSegments(path) {
|
|
140
|
+
const third = path[2];
|
|
141
|
+
return isUncPath(path) && third !== undefined && third !== "\\" && third !== "/" ? 2 : 0;
|
|
142
|
+
}
|
|
143
|
+
function relativeUnderDataRoot(path, roots) {
|
|
144
|
+
for (const root of roots) {
|
|
145
|
+
if (root.segments.length === 0)
|
|
146
|
+
continue;
|
|
147
|
+
if (root.win ? !isWinFormPath(path) : isWinFormPath(path) || !path.startsWith("/"))
|
|
148
|
+
continue;
|
|
149
|
+
const seps = root.win ? BOTH_SEPARATOR_FAMILIES : POSIX_SEPARATOR_FAMILY;
|
|
150
|
+
const segs = pathViewSegments(path, false, seps, uncAnchorSegments(path));
|
|
151
|
+
if (segs.length < root.segments.length)
|
|
152
|
+
continue;
|
|
153
|
+
if (root.segments.every((s, i) => segs[i] === s))
|
|
154
|
+
return segs.slice(root.segments.length).join("/");
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
export function compileWriteProtection(entries, opts) {
|
|
114
159
|
const rows = resolveWriteProtectedTable(entries);
|
|
115
160
|
if (rows.length === 0)
|
|
116
161
|
return undefined;
|
|
162
|
+
const rootViews = dataRootSpellings(opts?.dataRoot).map((r) => {
|
|
163
|
+
const win = isWinFormPath(r);
|
|
164
|
+
return { win, segments: pathViewSegments(r, false, win ? BOTH_SEPARATOR_FAMILIES : POSIX_SEPARATOR_FAMILY, uncAnchorSegments(r)) };
|
|
165
|
+
});
|
|
117
166
|
const basenames = new Map();
|
|
118
167
|
const segments = new Map();
|
|
119
168
|
const runs = [];
|
|
@@ -134,30 +183,11 @@ export function compileWriteProtection(entries) {
|
|
|
134
183
|
return {
|
|
135
184
|
rows,
|
|
136
185
|
matchPath(path) {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (raw.length === 0)
|
|
141
|
-
continue;
|
|
142
|
-
const dot = !win32 || /^\.{1,2}$/.test(raw) ? raw : raw.replace(/ +$/, "");
|
|
143
|
-
if (dot === ".")
|
|
144
|
-
continue;
|
|
145
|
-
if (dot === "..") {
|
|
146
|
-
const last = segs[segs.length - 1];
|
|
147
|
-
if (segs.length > 0 && last !== "..")
|
|
148
|
-
segs.pop();
|
|
149
|
-
else
|
|
150
|
-
segs.push("..");
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
const stripped = win32 ? raw.replace(/[. ]+$/, "") : raw;
|
|
154
|
-
segs.push(foldWriteProtectCase(stripped.length > 0 ? stripped : raw));
|
|
155
|
-
}
|
|
156
|
-
return segs;
|
|
157
|
-
};
|
|
158
|
-
const judge = (segs) => {
|
|
159
|
-
if (segs.length === 0)
|
|
186
|
+
const judged = relativeUnderDataRoot(path, rootViews) ?? path;
|
|
187
|
+
const judge = (raw) => {
|
|
188
|
+
if (raw.length === 0)
|
|
160
189
|
return null;
|
|
190
|
+
const segs = raw.map(foldWriteProtectCase);
|
|
161
191
|
const base = basenames.get(segs[segs.length - 1] ?? "");
|
|
162
192
|
if (base !== undefined)
|
|
163
193
|
return { name: base, kind: "basename" };
|
|
@@ -175,13 +205,12 @@ export function compileWriteProtection(entries) {
|
|
|
175
205
|
}
|
|
176
206
|
return null;
|
|
177
207
|
};
|
|
178
|
-
|
|
179
|
-
return judge(posix) ?? judge(viewOf(true));
|
|
208
|
+
return judge(pathViewSegments(judged, false)) ?? judge(pathViewSegments(judged, true));
|
|
180
209
|
},
|
|
181
210
|
};
|
|
182
211
|
}
|
|
183
|
-
export function createWriteProtectionCheck(entries) {
|
|
184
|
-
const matcher = compileWriteProtection(entries);
|
|
212
|
+
export function createWriteProtectionCheck(entries, opts) {
|
|
213
|
+
const matcher = compileWriteProtection(entries, opts);
|
|
185
214
|
if (matcher === undefined)
|
|
186
215
|
return undefined;
|
|
187
216
|
return (toolName, args) => {
|