@sema-agent/core 7.15.0 → 7.17.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 +202 -0
- package/dist/agents/builtin-agents.d.ts +3 -3
- package/dist/agents/cross-session-envelope.d.ts +0 -1
- package/dist/agents/cross-session-envelope.js +0 -1
- package/dist/agents/peer-notices.d.ts +0 -9
- package/dist/agents/peer-notices.js +0 -63
- package/dist/agents/subagent.js +7 -15
- package/dist/brain/request-params.d.ts +2 -2
- package/dist/config/defaults.d.ts +0 -7
- package/dist/config/defaults.js +0 -2
- package/dist/core/artifact-host.d.ts +218 -0
- package/dist/core/artifact-host.js +92 -0
- package/dist/core/ask-origin.d.ts +29 -3
- package/dist/core/ask-origin.js +17 -9
- package/dist/core/ask-question.d.ts +25 -0
- package/dist/core/ask-question.js +4 -0
- package/dist/core/ask-unresolvable-notice.d.ts +30 -21
- package/dist/core/ask-unresolvable-notice.js +16 -8
- package/dist/core/auto-compaction.d.ts +10 -10
- package/dist/core/auto-mode-arming.d.ts +2 -2
- package/dist/core/background-agent-store.d.ts +5 -5
- package/dist/core/background-shell.d.ts +1 -1
- package/dist/core/checkpoint-execution-record.d.ts +0 -3
- package/dist/core/checkpoint-execution-record.js +0 -4
- package/dist/core/checkpoint-store.d.ts +71 -99
- package/dist/core/checkpoint-store.js +9 -16
- package/dist/core/context-edit.d.ts +36 -104
- package/dist/core/context-edit.js +37 -60
- package/dist/core/effective-path-target.d.ts +68 -8
- package/dist/core/effective-path-target.js +42 -30
- package/dist/core/engine-notice.d.ts +46 -32
- package/dist/core/env-budget.d.ts +31 -0
- package/dist/core/env-budget.js +39 -0
- package/dist/core/exec-output-tail.d.ts +5 -5
- package/dist/core/file-history-store.js +3 -6
- package/dist/core/fs-write-gate-policy.d.ts +1 -1
- package/dist/core/fs-write-gate-policy.js +3 -3
- package/dist/core/gate-lanes.js +9 -15
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +4 -0
- package/dist/core/hands-band.d.ts +1 -1
- package/dist/core/hooks.d.ts +18 -17
- package/dist/core/lsp-session.d.ts +1 -1
- package/dist/core/lsp.d.ts +1 -1
- package/dist/core/lsp.js +5 -4
- package/dist/core/mcp.d.ts +5 -5
- package/dist/core/mcp.js +4 -48
- package/dist/core/memory-engine/delegation-settlement.d.ts +37 -12
- package/dist/core/memory-engine/delegation-settlement.js +128 -39
- package/dist/core/memory-engine/engine.d.ts +9 -8
- package/dist/core/memory-engine/engine.js +115 -120
- package/dist/core/memory-engine/file-backend.d.ts +17 -5
- package/dist/core/memory-engine/file-backend.js +78 -34
- package/dist/core/memory-engine/index.d.ts +2 -2
- package/dist/core/memory-engine/index.js +2 -2
- package/dist/core/memory-engine/layout.d.ts +152 -54
- package/dist/core/memory-engine/layout.js +441 -104
- package/dist/core/memory-engine/types.d.ts +8 -3
- package/dist/core/memory-engine/types.js +5 -0
- package/dist/core/memory-recall.d.ts +0 -8
- package/dist/core/memory.d.ts +1 -1
- package/dist/core/oracle-isolation.js +2 -2
- package/dist/core/parent-spec-seat.d.ts +66 -0
- package/dist/core/parent-spec-seat.js +47 -0
- package/dist/core/permission-rule-model.d.ts +11 -6
- package/dist/core/permission-rule-model.js +53 -37
- package/dist/core/permission-rules.js +8 -3
- package/dist/core/protocol-naming.d.ts +2 -2
- package/dist/core/remote-env.d.ts +1 -1
- package/dist/core/retired-keys.d.ts +19 -0
- package/dist/core/retired-keys.js +15 -0
- package/dist/core/runner/abort-race.d.ts +3 -3
- package/dist/core/runner/active-skill-scope.d.ts +2 -2
- package/dist/core/runner/active-skill-scope.js +3 -3
- package/dist/core/runner/assemble-result.d.ts +42 -40
- package/dist/core/runner/assemble-result.js +5 -4
- package/dist/core/runner/checkpoint-scope.d.ts +4 -4
- package/dist/core/runner/clock-and-limits.d.ts +3 -3
- package/dist/core/runner/compaction-call-options.d.ts +35 -99
- package/dist/core/runner/compaction-knobs.d.ts +4 -4
- package/dist/core/runner/compaction-seams.d.ts +5 -5
- package/dist/core/runner/contracts.d.ts +232 -233
- package/dist/core/runner/decide-continuation.d.ts +6 -6
- package/dist/core/runner/denial-limit-arms.d.ts +8 -8
- package/dist/core/runner/derived-route-fallback.d.ts +2 -2
- package/dist/core/runner/gate-exit.d.ts +6 -6
- package/dist/core/runner/gate-exit.js +15 -1
- package/dist/core/runner/git-status-frame.d.ts +7 -7
- package/dist/core/runner/halt-attribution.d.ts +35 -0
- package/dist/core/runner/halt-attribution.js +16 -0
- package/dist/core/runner/inherited-ask-grants.d.ts +6 -6
- package/dist/core/runner/memory-capture-optout.d.ts +1 -1
- package/dist/core/runner/memory-consolidation-driver.d.ts +4 -4
- package/dist/core/runner/memory-consolidation.d.ts +6 -8
- package/dist/core/runner/memory-consolidation.js +0 -1
- package/dist/core/runner/park-commit.d.ts +6 -6
- package/dist/core/runner/permission-rule-lanes.d.ts +19 -19
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +4 -4
- package/dist/core/runner/prepare-artifact.d.ts +53 -0
- package/dist/core/runner/prepare-artifact.js +77 -0
- package/dist/core/runner/prepare-ask-lane.d.ts +3 -3
- package/dist/core/runner/prepare-boundary-parks.d.ts +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +9 -7
- package/dist/core/runner/prepare-caps-and-workflow.js +2 -3
- package/dist/core/runner/prepare-config-doors.d.ts +24 -24
- package/dist/core/runner/prepare-config-doors.js +5 -2
- package/dist/core/runner/prepare-context-lane.d.ts +8 -8
- package/dist/core/runner/prepare-context-lane.js +2 -13
- package/dist/core/runner/prepare-defer-classify.d.ts +2 -2
- package/dist/core/runner/prepare-delegation-surface.d.ts +1 -1
- package/dist/core/runner/prepare-file-history.d.ts +3 -3
- package/dist/core/runner/prepare-file-history.js +3 -52
- package/dist/core/runner/prepare-gate-stations.d.ts +5 -5
- package/dist/core/runner/prepare-hands-readface.d.ts +35 -38
- package/dist/core/runner/prepare-hands-readface.js +3 -2
- package/dist/core/runner/prepare-inherited-gate.d.ts +9 -6
- package/dist/core/runner/prepare-inherited-gate.js +7 -8
- package/dist/core/runner/prepare-listings.d.ts +3 -3
- package/dist/core/runner/prepare-lsp.d.ts +3 -3
- package/dist/core/runner/prepare-memory-engine-session.d.ts +5 -5
- package/dist/core/runner/prepare-memory.d.ts +4 -6
- package/dist/core/runner/prepare-memory.js +10 -9
- package/dist/core/runner/prepare-offload-wrappers.d.ts +2 -2
- package/dist/core/runner/prepare-park-ask.d.ts +6 -6
- package/dist/core/runner/prepare-policy-chain.d.ts +7 -7
- package/dist/core/runner/prepare-policy-chain.js +15 -30
- package/dist/core/runner/prepare-prompt-assembly.js +1 -1
- package/dist/core/runner/prepare-prompt-inputs.d.ts +5 -5
- package/dist/core/runner/prepare-protocol-tools.d.ts +1 -1
- package/dist/core/runner/prepare-question-face.d.ts +4 -4
- package/dist/core/runner/prepare-run-refs.d.ts +11 -11
- package/dist/core/runner/prepare-safety-scan.d.ts +2 -2
- package/dist/core/runner/prepare-suspend-saga.d.ts +4 -4
- package/dist/core/runner/prepare-suspend-saga.js +0 -2
- package/dist/core/runner/prepare-task.d.ts +12 -12
- package/dist/core/runner/prepare-task.js +10 -9
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +2 -3
- package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -4
- package/dist/core/runner/prepare-wiring-manifest.js +9 -7
- package/dist/core/runner/prepare-workspace-restore.d.ts +1 -1
- package/dist/core/runner/remote-env-retry.d.ts +3 -3
- package/dist/core/runner/resume-admission.d.ts +1 -1
- package/dist/core/runner/resume-admission.js +2 -1
- package/dist/core/runner/resume-claim.d.ts +1 -1
- package/dist/core/runner/resume-internals-and-config.d.ts +1 -1
- package/dist/core/runner/resume-internals-and-config.js +4 -0
- package/dist/core/runner/resume-policy-outcome.d.ts +1 -1
- package/dist/core/runner/resume-preflight.d.ts +1 -1
- package/dist/core/runner/resume-review-outcome.d.ts +1 -1
- package/dist/core/runner/retired-runner-deps.d.ts +3 -0
- package/dist/core/runner/retired-runner-deps.js +7 -0
- package/dist/core/runner/run-brain-sinks.d.ts +1 -1
- package/dist/core/runner/run-clock-and-content.d.ts +4 -4
- package/dist/core/runner/run-compaction-machinery.d.ts +3 -3
- package/dist/core/runner/run-git-lane.d.ts +1 -1
- package/dist/core/runner/run-harness-handlers.d.ts +2 -2
- package/dist/core/runner/run-harness-handlers.js +2 -3
- package/dist/core/runner/run-identity-wiring.d.ts +4 -4
- package/dist/core/runner/run-leg.d.ts +3 -3
- package/dist/core/runner/run-notification-lane.d.ts +2 -2
- package/dist/core/runner/run-reasoning-seat.d.ts +2 -2
- package/dist/core/runner/run-settle-and-teardown.d.ts +5 -5
- package/dist/core/runner/run-settle-and-teardown.js +3 -1
- package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
- package/dist/core/runner/run-terminal-adoption.d.ts +5 -5
- package/dist/core/runner/runtask.d.ts +14 -14
- package/dist/core/runner/session-file-state-replay.d.ts +3 -3
- package/dist/core/runner/session-file-state-replay.js +3 -3
- package/dist/core/runner/session-rule-policy.d.ts +2 -2
- package/dist/core/runner/session-rule-policy.js +3 -4
- package/dist/core/runner/steer-admission.d.ts +2 -2
- package/dist/core/runner/stream-halt-verbs.d.ts +1 -1
- package/dist/core/runner/stream-halt-verbs.js +1 -1
- package/dist/core/runner/stream-reap.d.ts +1 -1
- package/dist/core/runner/stream-steer-verb.js +1 -4
- package/dist/core/runner/synthetic-tools.d.ts +8 -8
- package/dist/core/runner/teardown-bounded.d.ts +2 -2
- package/dist/core/runner/tool-defer-gate.d.ts +1 -1
- package/dist/core/runner/tool-disclosure.d.ts +33 -33
- package/dist/core/runner/tool-end-body.d.ts +3 -3
- package/dist/core/runner/tool-face-overlay.d.ts +2 -2
- package/dist/core/runner/tool-output-projection.d.ts +2 -2
- package/dist/core/runner/turn-attachments.d.ts +53 -53
- package/dist/core/runner/usage-accounting.d.ts +4 -4
- package/dist/core/runner/workspace-path.d.ts +1 -1
- package/dist/core/runner/workspace-path.js +3 -0
- package/dist/core/runner-deps.d.ts +30 -49
- package/dist/core/safe-notify.d.ts +1 -1
- package/dist/core/sensitive-path-policy.d.ts +1 -1
- package/dist/core/sensitive-path-policy.js +4 -4
- package/dist/core/session.d.ts +2 -2
- package/dist/core/skill-tool-specifier.d.ts +1 -1
- package/dist/core/skills-directory.d.ts +1 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
- package/dist/core/strategy-store.d.ts +8 -11
- package/dist/core/task-event.d.ts +20 -17
- package/dist/core/task-limits.d.ts +1 -1
- package/dist/core/task-notification.d.ts +5 -5
- package/dist/core/task-registry-agent.d.ts +5 -5
- package/dist/core/task-registry-monitor.d.ts +2 -2
- package/dist/core/task-registry-shared.d.ts +27 -27
- package/dist/core/task-registry-shared.js +3 -13
- package/dist/core/task-registry.d.ts +8 -8
- package/dist/core/task-result.d.ts +37 -37
- package/dist/core/task-spec.d.ts +5 -5
- package/dist/core/task-stream.d.ts +16 -11
- package/dist/core/tool-catalog-entries.d.ts +1 -0
- package/dist/core/tool-catalog-entries.js +5 -0
- package/dist/core/tool-conformance.d.ts +0 -2
- package/dist/core/tool-conformance.js +1 -3
- package/dist/core/tool-errors.d.ts +5 -0
- package/dist/core/tool-errors.js +1 -0
- package/dist/core/tool-policy.d.ts +51 -22
- package/dist/core/tool-policy.js +28 -5
- package/dist/core/tool-result-budget.d.ts +1 -1
- package/dist/core/tool-result-store.d.ts +11 -11
- package/dist/core/tool-spec.d.ts +34 -38
- package/dist/core/tools.d.ts +2 -2
- package/dist/core/trace.d.ts +12 -15
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/core/wiring-manifest.d.ts +4 -3
- package/dist/core/workflow-journal-store.d.ts +11 -11
- package/dist/engine/execution-env/node-execution-env.d.ts +3 -2
- package/dist/engine/execution-env/node-execution-env.js +4 -2
- package/dist/engine/harness/agent-harness.d.ts +47 -4
- package/dist/engine/harness/agent-harness.js +52 -9
- package/dist/engine/llm/types.d.ts +1 -1
- package/dist/engine/loop/agent-loop.js +15 -8
- package/dist/engine/loop/types.d.ts +5 -0
- package/dist/index.d.ts +21 -16
- package/dist/index.js +17 -12
- package/dist/internal/harness.d.ts +1 -0
- package/dist/orchestration/run-spec.d.ts +3 -0
- package/dist/orchestration/run-spec.js +3 -3
- package/dist/orchestration/run-workflow-tool.d.ts +12 -6
- package/dist/orchestration/run-workflow-tool.js +23 -5
- package/dist/orchestration/workflow-primitives.d.ts +4 -2
- package/dist/orchestration/workflow-primitives.js +1 -4
- package/dist/orchestration/workflow-types.d.ts +34 -4
- package/dist/orchestration/workflow-types.js +2 -0
- package/dist/orchestration/workflow.d.ts +39 -7
- package/dist/orchestration/workflow.js +234 -113
- package/dist/prompt-assembly/assemble.d.ts +2 -2
- package/dist/prompt-assembly/assemble.js +9 -9
- package/dist/prompt-assembly/composer.d.ts +6 -7
- package/dist/prompt-assembly/composer.js +10 -10
- package/dist/prompt-assembly/epoch.js +1 -1
- package/dist/prompt-assembly/event-registry.d.ts +0 -21
- package/dist/prompt-assembly/packs/sema-default.js +30 -30
- package/dist/prompt-assembly/types.d.ts +14 -14
- package/dist/prompts/coordinator.d.ts +0 -13
- package/dist/prompts/coordinator.js +0 -3
- package/dist/stores/file/checkpoint-store.js +1 -5
- package/dist/stores/file/file-history-store.js +2 -2
- package/dist/stores/file/permission-rule-store.d.ts +4 -35
- package/dist/stores/file/permission-rule-store.js +4 -148
- package/dist/tools/artifact/artifact-text.d.ts +62 -0
- package/dist/tools/artifact/artifact-text.js +68 -0
- package/dist/tools/artifact/artifact-tool.d.ts +52 -0
- package/dist/tools/artifact/artifact-tool.js +351 -0
- package/dist/tools/artifact/local-stub.d.ts +26 -0
- package/dist/tools/artifact/local-stub.js +296 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +114 -48
- package/dist/tools/fs/bash-readonly-classifier.js +171 -60
- package/dist/tools/fs/encoding.d.ts +31 -40
- package/dist/tools/fs/fs-bash.d.ts +49 -45
- package/dist/tools/fs/fs-bash.js +19 -27
- package/dist/tools/fs/fs-pdf.d.ts +8 -8
- package/dist/tools/fs/fs-search-tools.d.ts +4 -4
- package/dist/tools/fs/fs-search-tools.js +5 -2
- package/dist/tools/fs/fs-shared.d.ts +146 -158
- package/dist/tools/fs/fs-shared.js +3 -11
- package/dist/tools/fs/fs-write.d.ts +8 -8
- package/dist/tools/fs/gh-rate-limit.d.ts +13 -14
- package/dist/tools/fs/index.d.ts +78 -78
- package/dist/tools/fs/index.js +4 -1
- package/dist/tools/fs/notebook.d.ts +24 -25
- package/dist/tools/fs/pdf.d.ts +3 -3
- package/dist/tools/fs/read-deny.d.ts +22 -22
- package/dist/tools/fs/read-face.d.ts +21 -24
- package/dist/tools/fs/repo-map.d.ts +8 -8
- package/dist/tools/fs/safety.d.ts +233 -130
- package/dist/tools/fs/safety.js +131 -31
- package/dist/tools/fs/search.d.ts +63 -71
- package/dist/tools/fs/search.js +8 -6
- package/package.json +6 -2
- package/test/export-surface.snapshot.json +119 -39
|
@@ -394,6 +394,10 @@ export interface MaterializedFile {
|
|
|
394
394
|
/** True when the file belongs to a read-only inherited layer (chmod-protected, never harvested as a write). */
|
|
395
395
|
readonly: boolean;
|
|
396
396
|
}
|
|
397
|
+
/** The write face's home, for a call site that has established a write face exists (`writeScope !==
|
|
398
|
+
* null`). Throws on a read-only handle: a consumer that reaches here without a write face is a
|
|
399
|
+
* defect, and a template-literal or `join` over `null` would otherwise mint a `null/…` path. */
|
|
400
|
+
export declare function writableRootOf(handle: Pick<MemorySessionHandle, "writableRoot" | "writeScope">): string;
|
|
397
401
|
/**
|
|
398
402
|
* design/138 §2.2 — the session handle `materialize` returns and `harvest`/`rebaseline` consume.
|
|
399
403
|
* `materialized` IS the harvest diff domain (plus session-new files under `writableRoot`): an entry
|
|
@@ -405,9 +409,10 @@ export interface MemorySessionHandle {
|
|
|
405
409
|
* TODO(S1b): per-scope PHYSICAL roots (project scope in-repo + user scope in the home mount in ONE
|
|
406
410
|
* session) — `fileToScope` already keys absolute paths, so only the layout mapping needs the split. */
|
|
407
411
|
memoryDir: string;
|
|
408
|
-
/** The single directory the model may write memory into
|
|
409
|
-
*
|
|
410
|
-
|
|
412
|
+
/** The single directory the model may write memory into: the writeScope's home
|
|
413
|
+
* `<memoryDir>/<label>` (canonical). `null` iff there is no write face (`writeScope === null`) —
|
|
414
|
+
* a read-only layering has no directory the model may write, and no index is read from disk. */
|
|
415
|
+
writableRoot: string | null;
|
|
411
416
|
writeScope: string | null;
|
|
412
417
|
scopes: string[];
|
|
413
418
|
/** The materialized manifest = the harvest diff domain. */
|
|
@@ -1 +1,6 @@
|
|
|
1
1
|
export const MEMORY_ORIGIN_CAUSES = ["observed", "derived", "static", "unattributed"];
|
|
2
|
+
export function writableRootOf(handle) {
|
|
3
|
+
if (handle.writableRoot === null)
|
|
4
|
+
throw new Error("memory session handle has no write face (writeScope is null) — writableRoot is not available");
|
|
5
|
+
return handle.writableRoot;
|
|
6
|
+
}
|
|
@@ -177,14 +177,6 @@ export declare function composeSelectiveBody(manifestText: string, selected: Mem
|
|
|
177
177
|
* widen the public surface (and invite callers to parse the opaque format) for zero consumer. Treat the key
|
|
178
178
|
* as an opaque token: persist it, feed it back, compare for equality — never split it. */
|
|
179
179
|
export declare function encodeSurfacedKey(scope: string, id: string): string;
|
|
180
|
-
/** A manifest header tagged with the scope it came from (design/84 Seam A decision 5). */
|
|
181
|
-
export interface ScopedNoteHeader extends MemoryNoteHeader {
|
|
182
|
-
scope: string;
|
|
183
|
-
}
|
|
184
|
-
/** A full record tagged with its scope (for cross-scope getByIds). */
|
|
185
|
-
export interface ScopedNoteRecord extends MemoryNoteRecord {
|
|
186
|
-
scope: string;
|
|
187
|
-
}
|
|
188
180
|
export interface LayeredRecallOptions {
|
|
189
181
|
store: MemoryStore;
|
|
190
182
|
/** Ordered scopes (design/84 Seam A): list order = priority; the last is highest. */
|
package/dist/core/memory.d.ts
CHANGED
|
@@ -233,7 +233,7 @@ export type ConsolidationCapableStore = MemoryStore & Required<Pick<MemoryStore,
|
|
|
233
233
|
* (design/41): {@link MemoryStore.searchScored}/{@link MemoryStore.update}/{@link MemoryStore.delete}.
|
|
234
234
|
* When false, the Runner skips consolidation entirely (graceful no-op).
|
|
235
235
|
*
|
|
236
|
-
*
|
|
236
|
+
* A TYPE PREDICATE, so the guard's conclusion travels to the call sites. A bare boolean left
|
|
237
237
|
* every post-guard call site writing `store.update!(…)`, and a guard that later forgot one of the three
|
|
238
238
|
* would have been invisible — the `!`s keep compiling. Now dropping a method from the check turns the
|
|
239
239
|
* call sites red.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isRemoteExecutionEnv, isIsolated } from "./remote-env.js";
|
|
2
|
-
import { canonicalizeTarget } from "../tools/fs/safety.js";
|
|
2
|
+
import { canonicalizeTarget, pathFamilyOf } from "../tools/fs/safety.js";
|
|
3
3
|
function probeCommand(canonOraclePath) {
|
|
4
4
|
const q = `'${canonOraclePath.replace(/'/g, `'\\''`)}'`;
|
|
5
5
|
return `touch -- ${q}/.oracle-isolation-probe 2>/dev/null`;
|
|
@@ -31,7 +31,7 @@ export async function assertOracleIsolation(workerEnv, grader, opts) {
|
|
|
31
31
|
}
|
|
32
32
|
let writable;
|
|
33
33
|
try {
|
|
34
|
-
const canon = await canonicalizeTarget(workerEnv, oraclePath, opts?.signal);
|
|
34
|
+
const canon = await canonicalizeTarget(workerEnv, oraclePath, opts?.signal, pathFamilyOf({ root: oraclePath }));
|
|
35
35
|
if (!canon.ok) {
|
|
36
36
|
return { protected: false, isolationClass, reason: `cannot canonicalize oracle path "${oraclePath}": ${canon.message}` };
|
|
37
37
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The parent SPEC seat a run hands its children — the projection of the fields of the parent's own REBOUND spec
|
|
3
|
+
* that inherit copy-at-spawn (identity, process facts, deployment facts, the frozen question face) onto ONE value
|
|
4
|
+
* both delegation lanes copy onto a child spec: `ToolExecuteContext.parentSpecSeat` on the Task lane,
|
|
5
|
+
* `RunWorkflowToolDeps.parentSpecSeat` → `RunWorkflowOptions.parentSpecSeat` on the workflow lane. Minted once per
|
|
6
|
+
* prepare (the inherited-gate phase); applied by ONE function ({@link applyParentSpecSeat}) so the two lanes
|
|
7
|
+
* cannot drift on which fields travel or on the priority between a child's own value and the parent's.
|
|
8
|
+
*
|
|
9
|
+
* The checkpoint seat (`./checkpoint-seat.ts`) is the same shape for one field; this seat carries the rest.
|
|
10
|
+
*/
|
|
11
|
+
import type { OnQuestion } from "./ask-question.js";
|
|
12
|
+
import type { ToolExecuteContext } from "./tool-spec.js";
|
|
13
|
+
import type { TaskSpec } from "./types.js";
|
|
14
|
+
/** The fields of a spec the seat carries — one `Pick`, shared by the mint's input and the apply's target. */
|
|
15
|
+
export type ParentSpecSeatFields = Pick<TaskSpec, "principal" | "oneShot" | "clientContext" | "onQuestion" | "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "memoryPersistenceCapable">;
|
|
16
|
+
/**
|
|
17
|
+
* The seat a run hands its children. Every member is the parent's own value, frozen at mint (arrays copied and
|
|
18
|
+
* frozen; the facts object copied). Members present iff the parent carries them — an absent member is "nothing to
|
|
19
|
+
* inherit", never a value.
|
|
20
|
+
* · `principal` — the end-user identity (design/62: inherits verbatim, a worker cannot delegate under another).
|
|
21
|
+
* · `oneShot` — the process fact ("no later turn"): only `true` travels; it is a REMOVAL (see the apply law).
|
|
22
|
+
* · `clientContext` — the user's TZ/email facts (the child's environment block localizes the same way).
|
|
23
|
+
* · `onQuestion` — the parent's FROZEN question face (`spec.onQuestion ?? deps.onQuestion`, the value the parent's
|
|
24
|
+
* own AskUserQuestion resolves at): the child's question tool MOUNTS on it, so without it the child has no tool.
|
|
25
|
+
* · `additionalDirectories` / `additionalReadDirectories` — the fs-fence widening (deployment property).
|
|
26
|
+
* · `envFacts` — the sandbox facts (profile / egress / scratchpad).
|
|
27
|
+
* · `memoryPersistenceCapable` — the deployment's DECLARED persistence statement: `false` is a REMOVAL (floor),
|
|
28
|
+
* `true` a default a child's own declaration may narrow.
|
|
29
|
+
*/
|
|
30
|
+
export interface ParentSpecSeat {
|
|
31
|
+
readonly principal?: string;
|
|
32
|
+
readonly oneShot?: true;
|
|
33
|
+
readonly clientContext?: TaskSpec["clientContext"];
|
|
34
|
+
readonly onQuestion?: OnQuestion;
|
|
35
|
+
readonly additionalDirectories?: readonly string[];
|
|
36
|
+
readonly additionalReadDirectories?: readonly string[];
|
|
37
|
+
readonly envFacts?: TaskSpec["envFacts"];
|
|
38
|
+
readonly memoryPersistenceCapable?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** Mint the seat off the parent's REBOUND spec and its frozen question face (spec over deps — the caller resolves it once,
|
|
41
|
+
* the same value the parent's own question routing reads). Absent fields stay absent; `oneShot` only when `true`. */
|
|
42
|
+
export declare function parentSpecSeatOf(spec: Omit<ParentSpecSeatFields, "onQuestion">, frozenOnQuestion: OnQuestion | undefined): ParentSpecSeat;
|
|
43
|
+
/**
|
|
44
|
+
* Apply the seat to a child spec — ONE law for every member, both lanes:
|
|
45
|
+
* a value the child spec already carries WINS; the seat FILLS an unset one; the two REMOVAL values
|
|
46
|
+
* (`oneShot: true`, `memoryPersistenceCapable: false`) land UNCONDITIONALLY.
|
|
47
|
+
* "Already carries" is the child's own authorship — a deployment baseline, a trusted script, an agent definition's
|
|
48
|
+
* fold (the Task lane folds the definition's `memoryPersistenceCapable` before applying; the workflow lane applies at
|
|
49
|
+
* the launch site AFTER the agentType fold) — so a definition may narrow a parent `true` to `false`, a baseline-pinned
|
|
50
|
+
* principal stands, and nothing a child can author lifts a removal. Arrays and the facts object are copied per child
|
|
51
|
+
* (a retained child's frozen snapshot must not alias the parent's seat). Returns a NEW object; `child` is not mutated.
|
|
52
|
+
* No seat (a tool running outside a Runner task) ⇒ the child spec is returned as is.
|
|
53
|
+
*/
|
|
54
|
+
export declare function applyParentSpecSeat<S extends ParentSpecSeatFields>(child: S, seat: ParentSpecSeat | undefined): S;
|
|
55
|
+
/** The ctx keys that restate a seat member as THIS run's own fact (tools read them for scope / receipts / clamps). */
|
|
56
|
+
declare const SEAT_FACTS_ON_CTX: readonly ["principal", "oneShot", "onQuestion", "memoryPersistenceCapable"];
|
|
57
|
+
/**
|
|
58
|
+
* The seat a delegation boundary reads off a trusted ctx — ONE reader for both delegation tools. A Runner-filled ctx
|
|
59
|
+
* always carries the seat beside the run's own facts, and the two agree by construction (one mint). A HAND-BUILT ctx
|
|
60
|
+
* (a caller invoking a delegation tool's `execute` directly) that carries any of those facts WITHOUT the seat, or with
|
|
61
|
+
* a seat that disagrees with them, is REFUSED (`config.parent_spec_seat_missing`): spawning a principal-less child from a
|
|
62
|
+
* principal-bearing caller would offload into the shared namespace and re-key its journal — a silent tenant narrowing,
|
|
63
|
+
* never a fallback. A ctx with neither (a tool running outside a Runner task) hands down no seat.
|
|
64
|
+
*/
|
|
65
|
+
export declare function parentSpecSeatOfCtx(ctx: Pick<ToolExecuteContext, "parentSpecSeat" | (typeof SEAT_FACTS_ON_CTX)[number]>): ParentSpecSeat | undefined;
|
|
66
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function parentSpecSeatOf(spec, frozenOnQuestion) {
|
|
2
|
+
return Object.freeze({
|
|
3
|
+
...(spec.principal !== undefined ? { principal: spec.principal } : {}),
|
|
4
|
+
...(spec.oneShot === true ? { oneShot: true } : {}),
|
|
5
|
+
...(spec.clientContext !== undefined ? { clientContext: spec.clientContext } : {}),
|
|
6
|
+
...(frozenOnQuestion !== undefined ? { onQuestion: frozenOnQuestion } : {}),
|
|
7
|
+
...(spec.additionalDirectories !== undefined ? { additionalDirectories: Object.freeze([...spec.additionalDirectories]) } : {}),
|
|
8
|
+
...(spec.additionalReadDirectories !== undefined ? { additionalReadDirectories: Object.freeze([...spec.additionalReadDirectories]) } : {}),
|
|
9
|
+
...(spec.envFacts !== undefined ? { envFacts: Object.freeze({ ...spec.envFacts }) } : {}),
|
|
10
|
+
...(spec.memoryPersistenceCapable !== undefined ? { memoryPersistenceCapable: spec.memoryPersistenceCapable } : {}),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export function applyParentSpecSeat(child, seat) {
|
|
14
|
+
if (seat === undefined)
|
|
15
|
+
return child;
|
|
16
|
+
const fill = (key, value) => child[key] === undefined && value !== undefined ? { [key]: value } : {};
|
|
17
|
+
return {
|
|
18
|
+
...child,
|
|
19
|
+
...fill("principal", seat.principal),
|
|
20
|
+
...fill("clientContext", seat.clientContext),
|
|
21
|
+
...fill("onQuestion", seat.onQuestion),
|
|
22
|
+
...fill("additionalDirectories", (seat.additionalDirectories !== undefined ? [...seat.additionalDirectories] : undefined)),
|
|
23
|
+
...fill("additionalReadDirectories", (seat.additionalReadDirectories !== undefined ? [...seat.additionalReadDirectories] : undefined)),
|
|
24
|
+
...fill("envFacts", (seat.envFacts !== undefined ? { ...seat.envFacts } : undefined)),
|
|
25
|
+
...(seat.oneShot === true ? { oneShot: true } : {}),
|
|
26
|
+
...(seat.memoryPersistenceCapable === false ? { memoryPersistenceCapable: false } : fill("memoryPersistenceCapable", seat.memoryPersistenceCapable)),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const SEAT_FACTS_ON_CTX = ["principal", "oneShot", "onQuestion", "memoryPersistenceCapable"];
|
|
30
|
+
export function parentSpecSeatOfCtx(ctx) {
|
|
31
|
+
const seat = ctx.parentSpecSeat;
|
|
32
|
+
const facts = SEAT_FACTS_ON_CTX.filter((k) => ctx[k] !== undefined);
|
|
33
|
+
if (seat === undefined) {
|
|
34
|
+
if (facts.length === 0)
|
|
35
|
+
return undefined;
|
|
36
|
+
throw seatRefusal(`the tool context carries ${facts.join(" / ")} but no \`parentSpecSeat\` — a delegating caller hands its children the seat it was minted (a Runner-filled ctx always carries it); spawning without it would silently drop the identity`);
|
|
37
|
+
}
|
|
38
|
+
const disagree = facts.filter((k) => (k === "oneShot" ? (ctx.oneShot === true) !== (seat.oneShot === true) : ctx[k] !== seat[k]));
|
|
39
|
+
if (disagree.length > 0)
|
|
40
|
+
throw seatRefusal(`the tool context's ${disagree.join(" / ")} disagree with its \`parentSpecSeat\` — one mint, one value`);
|
|
41
|
+
return seat;
|
|
42
|
+
}
|
|
43
|
+
function seatRefusal(message) {
|
|
44
|
+
const e = new Error(`parentSpecSeat: ${message}`);
|
|
45
|
+
e.code = "config.parent_spec_seat_missing";
|
|
46
|
+
return e;
|
|
47
|
+
}
|
|
@@ -73,6 +73,8 @@
|
|
|
73
73
|
* `Bash(git status:*)`) — the last two are strict-side divergences from upstream, registered as such.
|
|
74
74
|
*/
|
|
75
75
|
import type { AssertAllKeysHandled } from "./ask-origin.js";
|
|
76
|
+
import { type PathFamily, type PathIdentity } from "../tools/fs/safety.js";
|
|
77
|
+
import type { PathTargetReading } from "./effective-path-target.js";
|
|
76
78
|
/**
|
|
77
79
|
* The closed set of rule BEHAVIORS — what a matching rule says about the call. The upstream vocabulary
|
|
78
80
|
* verbatim (`allow` / `deny` / `ask`, the three settings lists), in PRECEDENCE order: when rules of more
|
|
@@ -538,7 +540,7 @@ export type PathRuleBaseLabelCoversEveryBase = AssertAllKeysHandled<Exclude<Path
|
|
|
538
540
|
export declare function ruleBasesNeeded(rule: Pick<PersistedRule, "match" | "command">): readonly PathRuleBase[];
|
|
539
541
|
/** Is this base usable by {@link resolvePathPattern} — an absolute path with a lexical normal form? A rule
|
|
540
542
|
* compiler asks this at compile time so a pattern is refused rather than compiled inert. */
|
|
541
|
-
export declare function isUsablePathBase(base: string | undefined): boolean;
|
|
543
|
+
export declare function isUsablePathBase(base: string | undefined, family: PathFamily | undefined): boolean;
|
|
542
544
|
/**
|
|
543
545
|
* The PATH family's tightening reach — three-valued like the command family's ({@link programRunReachOf}):
|
|
544
546
|
* does this deny/ask rule reach the call's target path? The target is the caller's ALREADY-RESOLVED
|
|
@@ -555,7 +557,7 @@ export declare function isUsablePathBase(base: string | undefined): boolean;
|
|
|
555
557
|
* defect (a `~/` deny falling back to the engine host's home guarded the wrong directory; a `/…` deny with
|
|
556
558
|
* no root reached nothing at all).
|
|
557
559
|
*/
|
|
558
|
-
export declare function pathRuleReachOf(rule: Pick<PersistedRule, "match" | "command" | "behavior">, target:
|
|
560
|
+
export declare function pathRuleReachOf(rule: Pick<PersistedRule, "match" | "command" | "behavior">, target: PathIdentity, bases: PathRuleBases): ProgramRunReachOutcome;
|
|
559
561
|
/**
|
|
560
562
|
* Does this rule's command pattern admit `command`?
|
|
561
563
|
*
|
|
@@ -692,7 +694,7 @@ export declare function pathWithinRoot(path: string, root: string): boolean;
|
|
|
692
694
|
* bodies never come through here (their canonical spelling is unique and non-normal spellings are
|
|
693
695
|
* refused at the validator); this is for the comparison INPUT a caller derives from a live call.
|
|
694
696
|
*/
|
|
695
|
-
export declare function lexicalNormalAbsolutePathOf(path: string
|
|
697
|
+
export declare function lexicalNormalAbsolutePathOf(path: string, family: PathFamily | undefined, anchor?: string): PathIdentity | undefined;
|
|
696
698
|
/**
|
|
697
699
|
* design/382 §2.5 — the ONE admission predicate of the Read directory rule family, beside
|
|
698
700
|
* {@link ruleAdmitsCommand} as its path-lane sibling. Four consumers share it (the minting round
|
|
@@ -715,7 +717,10 @@ export declare function lexicalNormalAbsolutePathOf(path: string): string | unde
|
|
|
715
717
|
* there is nothing here for it to clear). As a DENY/ASK the same containment refuses or questions every
|
|
716
718
|
* path under the directory, on whichever path tool the rule names.
|
|
717
719
|
*/
|
|
718
|
-
export declare function directoryRuleAdmits(rule: Pick<PersistedRule, "match" | "command">, path:
|
|
720
|
+
export declare function directoryRuleAdmits(rule: Pick<PersistedRule, "match" | "command">, path: PathIdentity, bases: PathRuleBases): boolean;
|
|
721
|
+
/** The identity of a directory SPELLING (the cd resolver's `/`-rooted output) under the call's tree, for the
|
|
722
|
+
* consumers that hand {@link directoryRuleAdmits} a spelling; a rule minted from it stores this form. */
|
|
723
|
+
export declare function directoryIdentityOf(spelling: string, bases: PathRuleBases): PathIdentity | undefined;
|
|
719
724
|
/** Does a rule's scope cover a task running in `cwd` (and, for the session dimension, in the session
|
|
720
725
|
* named by `sessionId`)? A project rule needs a cwd to compare against; without one it covers nothing
|
|
721
726
|
* (fail-closed). A session rule (design/382 §4.3) covers a call iff the call's `sessionId` equals the
|
|
@@ -872,8 +877,8 @@ export declare function adjudicatePersistedPathRules(rules: readonly PersistedRu
|
|
|
872
877
|
cwd: string | undefined;
|
|
873
878
|
sessionId?: string;
|
|
874
879
|
}, targets: {
|
|
875
|
-
readonly tighten?:
|
|
876
|
-
readonly allow?:
|
|
880
|
+
readonly tighten?: PathTargetReading;
|
|
881
|
+
readonly allow?: PathTargetReading;
|
|
877
882
|
}, bases: PathRuleBases): PersistedRuleVerdict | undefined;
|
|
878
883
|
/**
|
|
879
884
|
* design/375 §5.2 — the per-segment coverage table for `command`: which segments an eligible rule
|
|
@@ -4,6 +4,7 @@ import { compileReadDeny } from "../tools/fs/read-deny.js";
|
|
|
4
4
|
import { carriesShellRedirection, parseLeadingCommandName, splitShellCompoundSegments } from "../tools/fs/bash-readonly-classifier.js";
|
|
5
5
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
6
6
|
import { pathTargetOf } from "./tool-registry.js";
|
|
7
|
+
import { carriesDotSegment, isAbsoluteForFamily, isIdentityDir, isPathIdentity, isShellRootedSpellingUnmapped, isUncIdentity, pathFamilyOf, pathIdentityOf } from "../tools/fs/safety.js";
|
|
7
8
|
import { readShellCommand } from "./shell-lexer.js";
|
|
8
9
|
export const RULE_BEHAVIORS = ["deny", "ask", "allow"];
|
|
9
10
|
const RULE_BEHAVIOR_SET = new Set(RULE_BEHAVIORS);
|
|
@@ -364,12 +365,18 @@ function baseValueOf(base, bases) {
|
|
|
364
365
|
}
|
|
365
366
|
}
|
|
366
367
|
function resolvePathPattern(pattern, bases, behavior) {
|
|
367
|
-
|
|
368
|
-
|
|
368
|
+
const family = pathFamilyOf(bases);
|
|
369
|
+
const tree = bases.root ?? bases.cwd;
|
|
370
|
+
if (pattern.startsWith("//")) {
|
|
371
|
+
const abs = pathIdentityOf("/" + pattern.slice(2), family, tree);
|
|
372
|
+
return abs.ok ? { resolved: abs.identity } : { missingBase: "root" };
|
|
373
|
+
}
|
|
369
374
|
const needed = ruleBasesNeeded({ match: "path", command: pattern })[0];
|
|
370
|
-
const
|
|
371
|
-
|
|
375
|
+
const baseValue = baseValueOf(needed, bases);
|
|
376
|
+
const base = baseValue !== undefined && isAbsoluteForFamily(family, baseValue) ? pathIdentityOf(baseValue, family, tree) : undefined;
|
|
377
|
+
if (base === undefined || !base.ok)
|
|
372
378
|
return { missingBase: needed };
|
|
379
|
+
const normal = base.identity;
|
|
373
380
|
const rest = pattern.startsWith("~/") ? pattern.slice(2) : pattern.startsWith("/") ? pattern.slice(1) : pattern.startsWith("./") ? pattern.slice(2) : pattern;
|
|
374
381
|
const peeled = rest.endsWith("/**") ? rest.slice(0, -3) : rest;
|
|
375
382
|
const bare = needed === "cwd" && peeled !== "" && !peeled.includes("/");
|
|
@@ -377,8 +384,8 @@ function resolvePathPattern(pattern, bases, behavior) {
|
|
|
377
384
|
const body = anyDepth ? "**/" + peeled : rest;
|
|
378
385
|
return { resolved: (normal === "/" ? "" : normal) + "/" + body };
|
|
379
386
|
}
|
|
380
|
-
export function isUsablePathBase(base) {
|
|
381
|
-
return base !== undefined &&
|
|
387
|
+
export function isUsablePathBase(base, family) {
|
|
388
|
+
return base !== undefined && isAbsoluteForFamily(family, base) && pathIdentityOf(base, family, base).ok;
|
|
382
389
|
}
|
|
383
390
|
function segmentGlobMatches(glob, segment) {
|
|
384
391
|
if (!glob.includes("*"))
|
|
@@ -422,15 +429,18 @@ function globSegmentsReach(globs, target) {
|
|
|
422
429
|
}
|
|
423
430
|
export function pathRuleReachOf(rule, target, bases) {
|
|
424
431
|
if (rule.match === "subpath")
|
|
425
|
-
return directoryRuleAdmits(rule, target) ? REACHED : NOT_REACHED;
|
|
432
|
+
return directoryRuleAdmits(rule, target, bases) ? REACHED : NOT_REACHED;
|
|
426
433
|
if (rule.match !== "path")
|
|
427
434
|
return NOT_REACHED;
|
|
428
|
-
|
|
435
|
+
const family = pathFamilyOf(bases);
|
|
436
|
+
if (!isPathIdentity(target, family))
|
|
429
437
|
return NOT_REACHED;
|
|
430
438
|
const r = resolvePathPattern(rule.command, bases, rule.behavior);
|
|
431
439
|
if ("missingBase" in r) {
|
|
432
|
-
return { reach: "unreadable", reason: `the rule is relative to ${PATH_RULE_BASE_LABEL[r.missingBase]} and this call supplies no absolute \`${r.missingBase}\` base to resolve it against` };
|
|
440
|
+
return { reach: "unreadable", reason: `the rule is relative to ${PATH_RULE_BASE_LABEL[r.missingBase]} and this call supplies no absolute \`${r.missingBase}\` base of this tree's family to resolve it against` };
|
|
433
441
|
}
|
|
442
|
+
if (isUncIdentity(r.resolved) !== isUncIdentity(target))
|
|
443
|
+
return NOT_REACHED;
|
|
434
444
|
const globs = r.resolved.split("/").filter((sg) => sg !== "");
|
|
435
445
|
const segments = target.split("/").filter((sg) => sg !== "");
|
|
436
446
|
return globSegmentsReach(globs, segments) ? REACHED : NOT_REACHED;
|
|
@@ -587,21 +597,9 @@ export function pathWithinRoot(path, root) {
|
|
|
587
597
|
const base = root.endsWith("/") ? root : root + "/";
|
|
588
598
|
return path.startsWith(base);
|
|
589
599
|
}
|
|
590
|
-
export function lexicalNormalAbsolutePathOf(path) {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
const out = [];
|
|
594
|
-
for (const seg of path.split("/")) {
|
|
595
|
-
if (seg === "" || seg === ".")
|
|
596
|
-
continue;
|
|
597
|
-
if (seg === "..") {
|
|
598
|
-
if (out.length > 0)
|
|
599
|
-
out.pop();
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
out.push(seg);
|
|
603
|
-
}
|
|
604
|
-
return out.length === 0 ? "/" : "/" + out.join("/");
|
|
600
|
+
export function lexicalNormalAbsolutePathOf(path, family, anchor) {
|
|
601
|
+
const r = pathIdentityOf(path, family, anchor);
|
|
602
|
+
return r.ok ? r.identity : undefined;
|
|
605
603
|
}
|
|
606
604
|
function isLexicalNormalAbsoluteDir(path) {
|
|
607
605
|
if (typeof path !== "string" || !path.startsWith("/") || path.startsWith("//"))
|
|
@@ -609,14 +607,22 @@ function isLexicalNormalAbsoluteDir(path) {
|
|
|
609
607
|
const segments = path.split("/").slice(1);
|
|
610
608
|
return segments.length > 0 && segments.every((s) => s !== "" && s !== "." && s !== "..");
|
|
611
609
|
}
|
|
612
|
-
export function directoryRuleAdmits(rule, path) {
|
|
610
|
+
export function directoryRuleAdmits(rule, path, bases) {
|
|
613
611
|
if (rule.match !== "subpath")
|
|
614
612
|
return false;
|
|
615
|
-
|
|
613
|
+
const family = pathFamilyOf(bases);
|
|
614
|
+
if (carriesDotSegment(rule.command, family))
|
|
615
|
+
return false;
|
|
616
|
+
const dir = pathIdentityOf(rule.command, family, bases.root ?? bases.cwd);
|
|
617
|
+
if (!dir.ok || !isIdentityDir(dir.identity, family))
|
|
616
618
|
return false;
|
|
617
|
-
if (!
|
|
619
|
+
if (!isPathIdentity(path, family) || !isIdentityDir(path, family))
|
|
618
620
|
return false;
|
|
619
|
-
return pathWithinRoot(path,
|
|
621
|
+
return pathWithinRoot(path, dir.identity);
|
|
622
|
+
}
|
|
623
|
+
export function directoryIdentityOf(spelling, bases) {
|
|
624
|
+
const r = pathIdentityOf(spelling, pathFamilyOf(bases), bases.root ?? bases.cwd);
|
|
625
|
+
return r.ok ? r.identity : undefined;
|
|
620
626
|
}
|
|
621
627
|
const CD_TARGET_REJECT_CHARS = /[*?[\]{}$`\\'"<>\n\r]/;
|
|
622
628
|
function cdSegmentDirectoryOf(segment, opts) {
|
|
@@ -711,7 +717,8 @@ function resolveCdSegmentDirectories(segments, cwd) {
|
|
|
711
717
|
const dir = cdSegmentDirectoryOf(segment, { cwd, priorCd });
|
|
712
718
|
if (!priorCd && movesShellCursor(segment))
|
|
713
719
|
priorCd = true;
|
|
714
|
-
|
|
720
|
+
const bases = basesOfCwd(cwd);
|
|
721
|
+
return dir === undefined || isShellRootedSpellingUnmapped(pathFamilyOf(bases), dir) ? undefined : directoryIdentityOf(dir, bases);
|
|
715
722
|
});
|
|
716
723
|
}
|
|
717
724
|
export function scopeCoversCwd(scope, cwd, sessionId) {
|
|
@@ -767,11 +774,14 @@ function firstEligibleDirectoryRule(rules, directory, call) {
|
|
|
767
774
|
for (const rule of rules) {
|
|
768
775
|
if (!eligiblePersisted(rule, { tool: "Read", cwd: call.cwd, sessionId: call.sessionId }, "allow"))
|
|
769
776
|
continue;
|
|
770
|
-
if (directoryRuleAdmits(rule, directory))
|
|
777
|
+
if (directoryRuleAdmits(rule, directory, basesOfCwd(call.execCwd ?? call.cwd)))
|
|
771
778
|
return rule;
|
|
772
779
|
}
|
|
773
780
|
return undefined;
|
|
774
781
|
}
|
|
782
|
+
function basesOfCwd(cwd) {
|
|
783
|
+
return cwd === undefined ? {} : { root: cwd };
|
|
784
|
+
}
|
|
775
785
|
export function adjudicatePersistedRules(rules, call) {
|
|
776
786
|
for (const behavior of RULE_BEHAVIORS_BY_PRECEDENCE) {
|
|
777
787
|
if (behavior === "allow") {
|
|
@@ -798,23 +808,29 @@ export function adjudicatePersistedRules(rules, call) {
|
|
|
798
808
|
export function adjudicatePersistedPathRules(rules, call, targets, bases) {
|
|
799
809
|
for (const behavior of RULE_BEHAVIORS_BY_PRECEDENCE) {
|
|
800
810
|
if (behavior === "allow") {
|
|
801
|
-
if (targets.allow
|
|
811
|
+
if (targets.allow?.kind !== "path" || call.tool !== READ_RULE_TOOL)
|
|
802
812
|
continue;
|
|
803
813
|
for (const rule of rules) {
|
|
804
814
|
if (!eligiblePersisted(rule, call, behavior))
|
|
805
815
|
continue;
|
|
806
|
-
if (directoryRuleAdmits(rule, targets.allow))
|
|
816
|
+
if (directoryRuleAdmits(rule, targets.allow.identity, bases))
|
|
807
817
|
return { behavior, rules: [rule] };
|
|
808
818
|
}
|
|
809
819
|
continue;
|
|
810
820
|
}
|
|
811
|
-
|
|
821
|
+
const tighten = targets.tighten;
|
|
822
|
+
if (tighten === undefined || tighten.kind === "refused")
|
|
812
823
|
continue;
|
|
813
824
|
let unreadable;
|
|
814
825
|
for (const rule of rules) {
|
|
815
826
|
if (!eligiblePersisted(rule, call, behavior))
|
|
816
827
|
continue;
|
|
817
|
-
|
|
828
|
+
if (tighten.kind === "unreadable") {
|
|
829
|
+
if (rule.match === "path" || rule.match === "subpath")
|
|
830
|
+
unreadable ??= { behavior: "ask", rules: [rule], unreadable: tighten.reason };
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
const outcome = pathRuleReachOf(rule, tighten.identity, bases);
|
|
818
834
|
if (outcome.reach === "reached")
|
|
819
835
|
return { behavior, rules: [rule] };
|
|
820
836
|
if (outcome.reach === "unreadable")
|
|
@@ -843,7 +859,7 @@ export function segmentCoverageOf(command, rules, call) {
|
|
|
843
859
|
proposed.some((p) => p.rule.tool === "Read"
|
|
844
860
|
? dir !== undefined &&
|
|
845
861
|
eligibleContext({ tool: p.rule.tool, scope: p.scope }, { tool: "Read", cwd: call.cwd, sessionId: call.sessionId }) &&
|
|
846
|
-
directoryRuleAdmits(p.rule, dir)
|
|
862
|
+
directoryRuleAdmits(p.rule, dir, basesOfCwd(call.execCwd ?? call.cwd))
|
|
847
863
|
: eligibleContext({ tool: p.rule.tool, scope: p.scope }, call) && ruleAdmitsCommand(p.rule, segment)),
|
|
848
864
|
};
|
|
849
865
|
});
|
|
@@ -889,7 +905,7 @@ export function suggestRulesForCommand(command, ctx) {
|
|
|
889
905
|
const parsed = parseRuleText(formatRuleText(directory, "subpath", READ_RULE_TOOL), "allow");
|
|
890
906
|
if (!("rule" in parsed) || parsed.rule.match !== "subpath")
|
|
891
907
|
return undefined;
|
|
892
|
-
if (!directoryRuleAdmits(parsed.rule, directory))
|
|
908
|
+
if (!directoryRuleAdmits(parsed.rule, directory, basesOfCwd(ctx?.execCwd ?? ctx?.cwd)))
|
|
893
909
|
return undefined;
|
|
894
910
|
return { kind: "directoryRead", rule: parsed.rule.rule, directory: parsed.rule.command, segment };
|
|
895
911
|
};
|
|
@@ -963,7 +979,7 @@ export function suggestRulesForCommand(command, ctx) {
|
|
|
963
979
|
if (parsedBatch.some((p) => p.tool !== "Read" && ruleAdmitsCommand(p, segment)))
|
|
964
980
|
continue;
|
|
965
981
|
const dir = cdDirs[i];
|
|
966
|
-
if (dir !== undefined && parsedBatch.some((p) => directoryRuleAdmits(p, dir)))
|
|
982
|
+
if (dir !== undefined && parsedBatch.some((p) => directoryRuleAdmits(p, dir, basesOfCwd(ctx?.execCwd ?? ctx?.cwd))))
|
|
967
983
|
continue;
|
|
968
984
|
const reason = carriesShellRedirection(segment) ? "redirection" : mintSegmentRule(segment) === undefined ? "no_rule_form" : "cap_overflow";
|
|
969
985
|
uncoveredDetail.push({ segment, reason });
|
|
@@ -2,6 +2,7 @@ import { MCP_NAMESPACE, protocolOf } from "./protocol-table.js";
|
|
|
2
2
|
import { catalogRuleFaceOf, pathTargetOf } from "./tool-registry.js";
|
|
3
3
|
import { indexOfUnescaped, lastIndexOfUnescaped, parsePermissionRule } from "./permission-rule-syntax.js";
|
|
4
4
|
import { PATH_RULE_BASE_LABEL, isUsablePathBase, parseRuleText, pathRuleReachOf, programRunReachOf, ruleBasesNeeded } from "./permission-rule-model.js";
|
|
5
|
+
import { pathFamilyOf } from "../tools/fs/safety.js";
|
|
5
6
|
import { effectivePathTargetOf } from "./effective-path-target.js";
|
|
6
7
|
import { protectivePathTargetOf } from "./tool-registry.js";
|
|
7
8
|
export { parsePermissionRule };
|
|
@@ -188,7 +189,7 @@ function compile(rules, caps, primaryFieldGeneric, ruleFaces, pathBases = {}) {
|
|
|
188
189
|
}
|
|
189
190
|
for (const base of ruleBasesNeeded(parsedPath.rule)) {
|
|
190
191
|
const value = base === "cwd" ? (pathBases.cwd ?? pathBases.root) : pathBases[base];
|
|
191
|
-
if (!isUsablePathBase(value)) {
|
|
192
|
+
if (!isUsablePathBase(value, pathFamilyOf(pathBases))) {
|
|
192
193
|
bad(text, "unsupported.path_base", `"${text}" is relative to ${PATH_RULE_BASE_LABEL[base]}, and this policy was given no absolute \`pathBases.${base}\`${base === "cwd" ? " (or `root`)" : ""} to resolve it against — the rule could not be judged on any call`);
|
|
193
194
|
}
|
|
194
195
|
}
|
|
@@ -321,9 +322,13 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
321
322
|
let unreadable;
|
|
322
323
|
for (const r of lane) {
|
|
323
324
|
if (r.parsed.match === "subpath" || r.parsed.match === "path") {
|
|
324
|
-
if (target === undefined)
|
|
325
|
+
if (target === undefined || target.kind === "refused")
|
|
325
326
|
continue;
|
|
326
|
-
|
|
327
|
+
if (target.kind === "unreadable") {
|
|
328
|
+
unreadable ??= { rule: r, unreadable: target.reason };
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const outcome = pathRuleReachOf(r.parsed, target.identity, pathBases);
|
|
327
332
|
if (outcome.reach === "reached")
|
|
328
333
|
return { rule: r };
|
|
329
334
|
if (outcome.reach === "unreadable")
|
|
@@ -18,13 +18,13 @@ export declare const MINTED_TOOL_SEGMENT_MIN_CHARS = 16;
|
|
|
18
18
|
* live domain key here (the refresh splice). That case is decided by
|
|
19
19
|
* {@link findNamespacePrefixCollision} and refused at the mount, loudly; the mint stays total.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
21
|
+
* The pattern this enforces is `{1,64}`, and only the charset half was
|
|
22
22
|
* enforced. Both other halves matter for the same reason the charset does — an over-long or empty segment
|
|
23
23
|
* produces a name the provider rejects, which 400s the WHOLE model request, the exact failure the intake
|
|
24
24
|
* gate exists to contain ("a single misconfigured server never bricks the whole task").
|
|
25
25
|
*/
|
|
26
26
|
export declare function normalizeNameSegment(name: string): string;
|
|
27
|
-
/**
|
|
27
|
+
/** Force a normalized segment into `{1,max}`, keeping long names distinct via a stable digest. */
|
|
28
28
|
export declare function clampNameSegment(seg: string, max?: number): string;
|
|
29
29
|
/**
|
|
30
30
|
* The registered-name prefix for `peer` in `ns` — i.e. the set `name.startsWith(prefix)` that a consumer
|
|
@@ -508,7 +508,7 @@ export declare function missingRestoreSurface(env: ExecutionEnv): readonly ("res
|
|
|
508
508
|
* A composite type guard (structural AND capability) so a `suspendVM` call site narrowed by THIS predicate
|
|
509
509
|
* gets the {@link RemoteExecutionEnv} type without a cast — `true` always implies the structural check too.
|
|
510
510
|
*
|
|
511
|
-
*
|
|
511
|
+
* It also requires the RESTORE surface ({@link missingRestoreSurface}). A suspend is only durable
|
|
512
512
|
* if something can undo it; an adapter that offers `suspendVM` but no `resumeVM` used to pass this guard,
|
|
513
513
|
* take a real snapshot, commit a real checkpoint — and then blow up on the resume leg with an untyped
|
|
514
514
|
* TypeError. "Suspendable" now means the whole round trip, so a half-adapter is rejected at the suspend site
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ONE presence rule for a RETIRED key on a caller-supplied bag (a deps object, an options object): the key is
|
|
3
|
+
* PRESENT iff ordinary property lookup answers a defined value — prototype and non-enumerable members included,
|
|
4
|
+
* exactly what the former direct read of the seat honored; an explicit `undefined` reads as absence, as every
|
|
5
|
+
* optional seat reads. Every retired-key door reads through here so no door grows its own idea of "present";
|
|
6
|
+
* each composes its own refusal from the answer. A table row is `{ key: replacement }` — the sentence that says
|
|
7
|
+
* what replaced the seat, quoted in the refusal.
|
|
8
|
+
*/
|
|
9
|
+
/** The first retired key present on `bag`, in table order, or undefined when none is. */
|
|
10
|
+
export declare function retiredKeyOf(bag: object, table: Readonly<Record<string, string>>): {
|
|
11
|
+
key: string;
|
|
12
|
+
replacement: string;
|
|
13
|
+
} | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* The deps-bag door: a retired seat still wired by a deployment is refused BY NAME (`config.deps_retired_key`)
|
|
16
|
+
* instead of vanishing silently — a seat that is silently dropped reads to the deployment as wired. `owner` is
|
|
17
|
+
* the bag's type name for the sentence (`RunnerDeps`, `RunWorkflowToolDeps`).
|
|
18
|
+
*/
|
|
19
|
+
export declare function assertNoRetiredKeys(bag: object, table: Readonly<Record<string, string>>, owner: string): void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function retiredKeyOf(bag, table) {
|
|
2
|
+
for (const [key, replacement] of Object.entries(table)) {
|
|
3
|
+
if (Reflect.get(bag, key) !== undefined)
|
|
4
|
+
return { key, replacement };
|
|
5
|
+
}
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
export function assertNoRetiredKeys(bag, table, owner) {
|
|
9
|
+
const hit = retiredKeyOf(bag, table);
|
|
10
|
+
if (hit === undefined)
|
|
11
|
+
return;
|
|
12
|
+
const e = new Error(`${owner}.${hit.key} is retired and is not read by this engine — ${hit.replacement}. Refused rather than ignored: a seat that is silently dropped reads to the deployment as wired.`);
|
|
13
|
+
e.code = "config.deps_retired_key";
|
|
14
|
+
throw e;
|
|
15
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* same three helpers instead of each carrying a copy. Pure: no engine state, no clock.
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* The per-call signal composition all three synchronous-wait seats share
|
|
10
10
|
* (adjudicate / resolveAsk / resolveContentAsk): `AbortSignal.any([run, call])` when the gate
|
|
11
11
|
* threaded a per-call signal, the bare run signal otherwise (host-driven gates and unthreaded emits
|
|
12
12
|
* keep the historical binding byte for byte). The run signal is ALSO composed into the loop's turn
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* (both sources outlive it), so nothing here retains across calls.
|
|
16
16
|
*/
|
|
17
17
|
export declare function composeCallSignal(runSignal: AbortSignal, callSignal: AbortSignal | undefined): AbortSignal;
|
|
18
|
-
/**
|
|
18
|
+
/** How a raced await ended. The generalized sibling of the ask-side
|
|
19
19
|
* `raceAskWaitAgainstSignal` (tool-policy.ts): same three-state form over any value domain.
|
|
20
20
|
* `aborted` means the SIGNAL settled the race — the promise may still be pending and stays the
|
|
21
21
|
* caller's to detach. `threw` reports the rejection AS a rejection, never folded into the abort
|
|
@@ -31,7 +31,7 @@ export type RacedSettlement<T> = {
|
|
|
31
31
|
} | {
|
|
32
32
|
tag: "aborted";
|
|
33
33
|
};
|
|
34
|
-
/**
|
|
34
|
+
/** Settle a raced await the moment `signal` aborts, WITHOUT waiting for the
|
|
35
35
|
* promise to notice. Pre-aborted entry answers at once; the listener is removed on settle
|
|
36
36
|
* whichever side wins (the `raceAbort` hygiene: `{once}` alone leaks a closure on the signal when
|
|
37
37
|
* the promise settles first). The mapped promise attaches BOTH handlers up front, so an abort-arm
|
|
@@ -12,7 +12,7 @@ export type ActiveSkillFrame = {
|
|
|
12
12
|
reason: string;
|
|
13
13
|
};
|
|
14
14
|
/**
|
|
15
|
-
* The per-task LIFO stack of active skill frames
|
|
15
|
+
* The per-task LIFO stack of active skill frames. Owned by `prepare-task`'s closure;
|
|
16
16
|
* the injected `skill` tool pushes onto it on a successful load, and a deny-narrowing policy reads it
|
|
17
17
|
* on every tool call. A simple array is the stack — `push` on load, and the policy reads ALL frames
|
|
18
18
|
* currently on it (the intersection). v1 keeps lifetime heuristic (see module header).
|
|
@@ -29,7 +29,7 @@ export declare class ActiveSkillScope {
|
|
|
29
29
|
get size(): number;
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
|
-
* Build the Gate-3 deny-narrowing policy over a live {@link ActiveSkillScope}
|
|
32
|
+
* Build the Gate-3 deny-narrowing policy over a live {@link ActiveSkillScope}. For each
|
|
33
33
|
* tool call, WHILE any skill frame is active:
|
|
34
34
|
* - DENY any tool not in the INTERSECTION of every active frame's `allowTools`.
|
|
35
35
|
* - For a WRITE tool with an `allowPaths` constraint on ANY active frame, DENY a write whose
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { canonicalizeTarget } from "../../tools/fs/safety.js";
|
|
1
|
+
import { canonicalizeTarget, pathFamilyOf } from "../../tools/fs/safety.js";
|
|
2
2
|
import { pathTargetValue } from "../tool-registry.js";
|
|
3
3
|
import { pathTargetBaseOf } from "../effective-path-target.js";
|
|
4
4
|
import { PATH_WRITE_TOOLS, isWithin } from "./session-rule-policy.js";
|
|
@@ -100,7 +100,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
100
100
|
decisionReason: "safety",
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
|
-
const canon = await canonicalizeTarget(env, path, signal, pathTargetBaseOf(declaredTarget, { root: rootPath, cwd: req.cwd }));
|
|
103
|
+
const canon = await canonicalizeTarget(env, path, signal, pathFamilyOf({ root: rootPath, cwd: req.cwd ?? env.cwd }), pathTargetBaseOf(declaredTarget, { root: rootPath, cwd: req.cwd }));
|
|
104
104
|
if (!canon.ok) {
|
|
105
105
|
return {
|
|
106
106
|
action: "deny",
|
|
@@ -111,7 +111,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
111
111
|
for (const m of constraining) {
|
|
112
112
|
const roots = [];
|
|
113
113
|
for (const raw of m.allowPaths ?? []) {
|
|
114
|
-
const r = await canonicalizeTarget(env, raw, signal, rootPath);
|
|
114
|
+
const r = await canonicalizeTarget(env, raw, signal, pathFamilyOf({ root: rootPath ?? env.cwd }), rootPath);
|
|
115
115
|
if (r.ok)
|
|
116
116
|
roots.push(r.key);
|
|
117
117
|
}
|