@xioflow/kernel 0.2.0 → 0.4.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.
Files changed (86) hide show
  1. package/README.md +116 -15
  2. package/dist/capability/check.d.ts +32 -0
  3. package/dist/capability/check.d.ts.map +1 -0
  4. package/dist/capability/check.js +136 -0
  5. package/dist/capability/check.js.map +1 -0
  6. package/dist/capability/index.d.ts +3 -0
  7. package/dist/capability/index.d.ts.map +1 -0
  8. package/dist/capability/index.js +3 -0
  9. package/dist/capability/index.js.map +1 -0
  10. package/dist/capability/path-utils.d.ts +11 -0
  11. package/dist/capability/path-utils.d.ts.map +1 -0
  12. package/dist/capability/path-utils.js +46 -0
  13. package/dist/capability/path-utils.js.map +1 -0
  14. package/dist/confinement/bubblewrap.d.ts +8 -0
  15. package/dist/confinement/bubblewrap.d.ts.map +1 -0
  16. package/dist/confinement/bubblewrap.js +45 -0
  17. package/dist/confinement/bubblewrap.js.map +1 -0
  18. package/dist/confinement/detector.d.ts +4 -0
  19. package/dist/confinement/detector.d.ts.map +1 -0
  20. package/dist/confinement/detector.js +35 -0
  21. package/dist/confinement/detector.js.map +1 -0
  22. package/dist/confinement/index.d.ts +5 -0
  23. package/dist/confinement/index.d.ts.map +1 -0
  24. package/dist/confinement/index.js +5 -0
  25. package/dist/confinement/index.js.map +1 -0
  26. package/dist/confinement/sandbox-exec.d.ts +8 -0
  27. package/dist/confinement/sandbox-exec.d.ts.map +1 -0
  28. package/dist/confinement/sandbox-exec.js +30 -0
  29. package/dist/confinement/sandbox-exec.js.map +1 -0
  30. package/dist/confinement/srt.d.ts +8 -0
  31. package/dist/confinement/srt.d.ts.map +1 -0
  32. package/dist/confinement/srt.js +41 -0
  33. package/dist/confinement/srt.js.map +1 -0
  34. package/dist/domain.d.ts +46 -2
  35. package/dist/domain.d.ts.map +1 -1
  36. package/dist/domain.js +209 -18
  37. package/dist/domain.js.map +1 -1
  38. package/dist/driver/node-driver.d.ts.map +1 -1
  39. package/dist/driver/node-driver.js +46 -36
  40. package/dist/driver/node-driver.js.map +1 -1
  41. package/dist/driver/types.d.ts +13 -0
  42. package/dist/driver/types.d.ts.map +1 -1
  43. package/dist/index.d.ts +4 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +4 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/quick-run.d.ts +27 -0
  48. package/dist/quick-run.d.ts.map +1 -0
  49. package/dist/quick-run.js +75 -0
  50. package/dist/quick-run.js.map +1 -0
  51. package/dist/recovery/engine.d.ts +7 -0
  52. package/dist/recovery/engine.d.ts.map +1 -1
  53. package/dist/recovery/engine.js +174 -4
  54. package/dist/recovery/engine.js.map +1 -1
  55. package/dist/snapshot/git-shadow.d.ts +49 -0
  56. package/dist/snapshot/git-shadow.d.ts.map +1 -0
  57. package/dist/snapshot/git-shadow.js +359 -0
  58. package/dist/snapshot/git-shadow.js.map +1 -0
  59. package/dist/store/schema.d.ts +1 -1
  60. package/dist/store/schema.d.ts.map +1 -1
  61. package/dist/store/schema.js +31 -0
  62. package/dist/store/schema.js.map +1 -1
  63. package/dist/store/sqlite.d.ts +17 -1
  64. package/dist/store/sqlite.d.ts.map +1 -1
  65. package/dist/store/sqlite.js +193 -58
  66. package/dist/store/sqlite.js.map +1 -1
  67. package/dist/supervisor/drainer.d.ts +17 -0
  68. package/dist/supervisor/drainer.d.ts.map +1 -0
  69. package/dist/supervisor/drainer.js +200 -0
  70. package/dist/supervisor/drainer.js.map +1 -0
  71. package/dist/supervisor/service.d.ts +24 -0
  72. package/dist/supervisor/service.d.ts.map +1 -0
  73. package/dist/supervisor/service.js +588 -0
  74. package/dist/supervisor/service.js.map +1 -0
  75. package/dist/supervisor/supervisor.d.ts +62 -4
  76. package/dist/supervisor/supervisor.d.ts.map +1 -1
  77. package/dist/supervisor/supervisor.js +1037 -562
  78. package/dist/supervisor/supervisor.js.map +1 -1
  79. package/dist/testing/contract-suite.d.ts.map +1 -1
  80. package/dist/testing/contract-suite.js +748 -2
  81. package/dist/testing/contract-suite.js.map +1 -1
  82. package/dist/types.d.ts +175 -2
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/types.js +39 -1
  85. package/dist/types.js.map +1 -1
  86. package/package.json +1 -1
package/README.md CHANGED
@@ -2,15 +2,18 @@
2
2
 
3
3
  [![CI](https://github.com/Xio-Shark/xioflow/actions/workflows/ci.yml/badge.svg)](https://github.com/Xio-Shark/xioflow/actions/workflows/ci.yml)
4
4
  [![npm](https://img.shields.io/npm/v/@xioflow/kernel.svg)](https://www.npmjs.com/package/@xioflow/kernel)
5
- [![Node](https://img.shields.io/badge/Node.js-22.5%2B-green.svg)](https://nodejs.org/)
5
+ [![Node](https://img.shields.io/badge/Node.js-22.13%2B-green.svg)](https://nodejs.org/)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
7
- [![Docs](https://img.shields.io/badge/spec-ARCHITECTURE.md-informational.svg)](./ARCHITECTURE.md)
7
+ [![Docs (EN)](https://img.shields.io/badge/spec-ARCHITECTURE.en.md-informational.svg)](./ARCHITECTURE.en.md)
8
+ [![Docs (ZH)](https://img.shields.io/badge/spec-ARCHITECTURE.md-informational.svg)](./ARCHITECTURE.md)
8
9
 
9
- A supervised execution kernel for AI agent runtimes. Zero runtime dependencies.
10
+ A supervised execution kernel for AI agent runtimes. Zero runtime dependencies. (See [Architecture & Protocol Specification (EN)](./ARCHITECTURE.en.md) / [中文规范](./ARCHITECTURE.md)).
10
11
 
11
12
  Agent runtimes usually call `spawn()` (or `exec()`) and hope for the best. When the host crashes mid-tool-call, or a cancel cannot be confirmed, they are left with orphan processes, double-applied side effects, and no honest record of what actually happened. This kernel makes those states first-class instead of silent.
12
13
 
13
- [xiocode](https://github.com/Xio-Shark/xiocode), the reference distribution, runs its supervised commands on this kernel by default on Node 22.5+ — the equivalence suite below is what made that switch safe to make.
14
+ [xiocode](https://github.com/Xio-Shark/xiocode), the reference distribution, runs its supervised commands on this kernel by default — the equivalence suite below is what made that switch safe to make.
15
+
16
+ Besides one-shot commands, the kernel supervises long-running services (MCP stdio servers, dev servers), makes operations idempotent by `opId` for durable engines (Temporal, LangGraph), and snapshots, rolls back and forks the workspace so an agent's file changes can be undone per step.
14
17
 
15
18
  ## Guarantees
16
19
 
@@ -21,14 +24,17 @@ Agent runtimes usually call `spawn()` (or `exec()`) and hope for the best. When
21
24
  | No premature release | Exclusive resource leases are released only after the platform driver confirms the process is gone. An unconfirmed stop keeps the lease and escalates to `indeterminate`. |
22
25
  | No silent output loss | In-memory output is capped by `maxOutputBytes` (default 10 MiB); each stream is spilled to `<domain>/artifacts/<opId>-stdout.log` / `-stderr.log`, `fsync`ed, and hashed. Truncation is reported per stream (`stdoutTruncated` / `stderrTruncated`) with `stdoutRef` / `stderrRef`, `stdoutBytes` / `stderrBytes` and `stdoutHash` / `stderrHash`; `isTruncated` / `outputRef` / `outputHash` remain as aggregate compatibility fields. |
23
26
  | No implicit environment | `envWhiteList` is exact: whatever you pass is what the child gets, with no injected `PATH`. Without a whitelist the child inherits `process.env`, unless you pass `inheritEnv: false` to get an empty environment. |
24
- | No orphan leak | Termination escalates SIGINT to SIGTERM to SIGKILL across the process group, then re-enumerates descendants. Escaped (`setsid`) survivors are reported honestly as `{ stopped: false, residualPids: [...] }` instead of a fake success. |
27
+ | No orphan leak | Termination escalates SIGINT to SIGTERM to SIGKILL across the process group, then re-enumerates descendants. Escaped (`setsid`) survivors are reported honestly as `{ stopped: 'cannot_determine', residualPids: [...] }` instead of a fake success. If the group is confirmed empty but the output pipes are still held by a process the driver never saw, the operation is `indeterminate`, not `succeeded`. |
28
+ | No blind retry | Resubmitting an `opId` with the same input fingerprint joins the in-flight execution or replays the recorded result (`replayed: true`); an `indeterminate` result is returned as-is and never re-executed. A different fingerprint throws `OperationIdConflictError`. |
29
+ | No out-of-scope rollback | A rollback only rewrites and deletes files inside the snapshot's declared roots, never touches ignored files unless they were captured, and is verified by fingerprint. `coverage: 'complete'` is claimed only when every operation since the snapshot ran under a confinement driver. |
25
30
  | No split brain | A domain has one active owner, held by an exclusive lock file plus a heartbeat lease. Stale owners are fenced by an epoch counter; their writes are rejected. |
26
31
 
27
32
  ## Requirements
28
33
 
29
- - Node.js >= 22.5 (`node:sqlite`). Verified on Node v24.14.0. `node:sqlite` is still marked experimental upstream, so Node prints an `ExperimentalWarning`; that is expected.
34
+ - Node.js >= 22.13 (`node:sqlite` without a flag). CI covers Node 22.13 and 24 on Ubuntu and macOS. `node:sqlite` is still marked experimental upstream, so Node prints an `ExperimentalWarning`; that is expected.
30
35
  - Linux and macOS. Descendant enumeration and group termination use `ps(1)` and POSIX process groups.
31
- - Windows is not supported in 0.1. There the driver reports `processGroupKill: false` and `descendantEnumeration: 'none'` rather than pretending it can contain processes.
36
+ - Snapshots need `git` on `PATH` and a git working tree. Confinement is optional and uses `sandbox-exec` (macOS), `bubblewrap` (Linux) or `srt` when available.
37
+ - Windows is not supported. There the driver reports `processGroupKill: false` and `descendantEnumeration: 'none'` rather than pretending it can contain processes.
32
38
 
33
39
  ## Install
34
40
 
@@ -38,6 +44,36 @@ npm install @xioflow/kernel
38
44
 
39
45
  ## Quickstart
40
46
 
47
+ ### 1. Simplest Usage (`quickRun`)
48
+
49
+ For standard tool calls and durable activity execution, `quickRun` automatically manages domain acquisition, Task and Run records, and lock lifecycles:
50
+
51
+ ```js
52
+ import { quickRun } from '@xioflow/kernel';
53
+
54
+ // 1. One-line supervised execution
55
+ const result = await quickRun({
56
+ execPath: 'npm',
57
+ args: ['test'],
58
+ cwd: '/path/to/workspace',
59
+ });
60
+
61
+ console.log(result.status, result.exitCode, result.stdout);
62
+
63
+ // 2. Idempotent execution (safe retry for Temporal / LangGraph / durable workflows)
64
+ const idemResult = await quickRun(
65
+ { execPath: 'git', args: ['commit', '-m', 'chore: update'], cwd: '/path/to/workspace' },
66
+ { opId: 'workflow-step-42' } // Passing opId guarantees at-most-once execution across retries
67
+ );
68
+ if (idemResult.replayed) {
69
+ console.log('Result retrieved from journal without re-executing process');
70
+ }
71
+ ```
72
+
73
+ ### 2. Full Architecture & Fine-Grained Supervision
74
+
75
+ When you need granular control over Tasks, Runs, resource locks, streaming output, or multi-phase workflows:
76
+
41
77
  ```js
42
78
  import { ExecutionDomain, NodePlatformDriver, ProcessSupervisor } from '@xioflow/kernel';
43
79
 
@@ -89,6 +125,9 @@ domain.close(); // releases the owner lease and the domain lock
89
125
 
90
126
  To cancel, call `await supervisor.cancelOperation('op-1', graceMs)`. It returns `{ stopped, scope, residualPids? }`. The pending `executeProcess` promise then resolves with `status: 'cancelled'`, or with `status: 'indeterminate'` when the driver cannot confirm that the process actually stopped, in which case its leases stay locked.
91
127
 
128
+ > **Note on In-Flight Join and Cancellation**:
129
+ > When an in-flight operation with the same `opId` and fingerprint is joined concurrently, passing an `abortSignal` to the secondary caller only cancels the secondary caller's own wait promise—it never aborts the underlying process or the primary caller's execution. To deliberately terminate the underlying process, explicitly call `supervisor.cancelOperation(opId)`.
130
+
92
131
  ## Crash recovery
93
132
 
94
133
  After a restart, reacquire the domain and run the recovery engine:
@@ -111,11 +150,73 @@ For every unfinished operation, recovery verifies the recorded process identity
111
150
 
112
151
  The kernel records execution facts (state, output evidence, artifact references) and refuses to guess. Whether a failed test that was later fixed counts as business success is the distribution's decision, not the kernel's.
113
152
 
153
+ An `indeterminate` operation leaves the kernel only through `domain.adjudicate(opId, verdict, actor, note)`, which re-scans for residual processes, refuses `confirmed_stopped` while any are alive (or while the scan itself fails), and journals the decision before releasing leases.
154
+
155
+ ## Long-running services
156
+
157
+ MCP stdio servers, dev servers and watchers are supervised as services. Each instance is a kernel operation (`<serviceId>#<n>`), so a host crash leaves a record that recovery can act on:
158
+
159
+ ```js
160
+ const service = await supervisor.startService({
161
+ serviceId: 'mcp-fs',
162
+ runId: 'run-1',
163
+ command: { execPath: 'node', args: ['server.mjs'], cwd: '/path/to/workspace' },
164
+ readiness: { stdoutLine: /listening/, timeoutMs: 5000 }, // or 'spawned'
165
+ restart: { policy: 'on-failure', maxRestarts: 3, backoffMs: 500 },
166
+ requiredResources: ['port:3000'],
167
+ });
168
+ await service.ready; // rejects if the readiness line never appears; the instance is then stopped
169
+ service.stdin.write('...'); // stdin stays open (stdinMode: 'stream'); stdout is passed through
170
+ await service.stop(2000);
171
+ ```
172
+
173
+ Leases are held by the service across restart backoff. A stop the driver cannot confirm records the instance as `indeterminate`, journals `SERVICE_FAILED { reason: 'stop_unconfirmed' }` and keeps the leases. After a host crash, `recover()` stops surviving instances and never restarts them automatically. See [`examples/mcp-stdio-transport`](./examples/mcp-stdio-transport) for an MCP SDK `Transport` on top of this.
174
+
175
+ ## Snapshots, rollback and forks
176
+
177
+ The git-shadow driver snapshots declared roots without touching the user's index, HEAD or branch, and pins each snapshot under `refs/xioflow/snapshots/<id>`:
178
+
179
+ ```js
180
+ const snap = await supervisor.captureSnapshot({ runId: 'run-1', opId: 'snap-1', roots: ['/path/to/workspace/src'] });
181
+
182
+ // ... the agent edits files ...
183
+
184
+ const rb = await supervisor.rollback({ runId: 'run-1', opId: 'rb-1', snapshotId: snap.snapshot.id });
185
+ console.log(rb.status, rb.coverage, rb.outOfScopeEffects); // e.g. 'restored', 'declared_roots', 'possible'
186
+
187
+ // Fork the snapshot into an independent worktree for a parallel candidate:
188
+ await supervisor.materialize(snap.snapshot.id, '/tmp/candidate-a');
189
+ await supervisor.dematerialize('/tmp/candidate-a');
190
+ ```
191
+
192
+ Rollback only rewrites and deletes files inside the snapshot's roots and leaves ignored files alone unless the snapshot used `includeIgnored: true`. It is verified by fingerprint against the snapshot tree. `coverage` is `complete` only when every operation since the snapshot ran confined; otherwise it is `declared_roots` with `outOfScopeEffects: 'possible'`.
193
+
194
+ To make `complete` reachable, bind operations to a capability and run them confined:
195
+
196
+ ```js
197
+ const cap = domain.issueCapability(
198
+ { write: ['/path/to/workspace/src'], exclusive: ['workspace:write:/path/to/workspace/src'] },
199
+ 'my-agent-policy',
200
+ 10 * 60_000,
201
+ );
202
+ await supervisor.executeProcess({
203
+ runId: 'run-1',
204
+ opId: 'op-edit',
205
+ name: 'codegen',
206
+ command: { execPath: 'node', args: ['../codegen.mjs'], cwd: '/path/to/workspace/src' },
207
+ // mutation roots default to cwd; pass mutationRoots to declare others
208
+ capabilityId: cap.id, // out-of-scope resources or paths are rejected at admission
209
+ confinement: true, // sandbox-exec / bubblewrap / srt, whichever is available
210
+ });
211
+ ```
212
+
213
+ A capability can be narrowed with `domain.attenuate`, and revoked with `domain.revokeCapability`. Confinement exists so the rollback coverage claim is true. It is not a security boundary.
214
+
114
215
  ## Design notes
115
216
 
116
217
  ### Verify your own runtime
117
218
 
118
- The package ships the same 18-item contract suite that the kernel itself is tested against, so an embedding runtime can prove its consumer honors these guarantees:
219
+ The package ships the same 44-item contract suite that the kernel itself is tested against, so an embedding runtime can prove its consumer honors these guarantees:
119
220
 
120
221
  ```js
121
222
  import { defineContractTestSuite } from '@xioflow/kernel/testing';
@@ -137,31 +238,31 @@ defineContractTestSuite('My Agent Runtime', async () => ({
137
238
  - When the root process exits while a descendant still holds its pipes, the supervisor reaps the process group and reports the root's real exit facts with `residualProcessesReaped: true`, instead of blocking the operation until its timeout.
138
239
  - A spawn failure is reported both as `status: 'failed'`/`exitCode: 127` and as `spawnFailure` with the underlying error message, so embedders can tell "the binary could not start" apart from "the child exited 127".
139
240
  - `onStreamChunk(stream, chunk)` forwards raw stdout/stderr chunks while the process runs, so a caller can render live output. A throwing callback never aborts the drain; the first error is recorded as `streamCallbackError`.
140
- - Hard resource requests (`maxMemoryBytes`, `maxPids`, `maxCpuTimeMs` with `enforcement: 'hard'`) are rejected at admission with `UnsupportedCapabilityError` on platforms that cannot enforce them, instead of degrading silently. The remaining capability flags are reported truthfully for callers to inspect, but are not enforced by admission in 0.1.
141
- - Protocol specification: [`ARCHITECTURE.md`](./ARCHITECTURE.md) (Chinese). It describes the target design; the phased plan and the spec-vs-implementation gap table live in [`ROADMAP.md`](./ROADMAP.md).
241
+ - Hard resource requests (`maxMemoryBytes`, `maxPids`, `maxCpuTimeMs` with `enforcement: 'hard'`) are rejected at admission with `UnsupportedCapabilityError` on platforms that cannot enforce them, instead of degrading silently. The remaining capability flags are reported truthfully for callers to inspect, but are not enforced by admission yet.
242
+ - Protocol specification: [`ARCHITECTURE.md`](./ARCHITECTURE.md) (Chinese, complete) and [`ARCHITECTURE.en.md`](./ARCHITECTURE.en.md) (English, §0, §3 and §7). They describe the target design; the phased plan and the spec-vs-implementation gap table live in [`ROADMAP.md`](./ROADMAP.md).
142
243
 
143
244
  ## Status
144
245
 
145
- 0.1.5, pre-1.0: the API may change. Not implemented yet: Linux cgroup v2 backend (hard memory/CPU/PID enforcement), enforced domain-wide memory budgets, artifact retrieval helpers. Known correctness gaps in 0.1.x and the plan toward a Rust core with snapshot/rollback and a daemon mode are tracked in [`ROADMAP.md`](./ROADMAP.md).
246
+ 0.4.0 on npm (services, snapshots/rollback/forks, capabilities and confinement; see [`CHANGELOG.md`](./CHANGELOG.md)), pre-1.0: the API may change. Not implemented yet: Linux cgroup v2 backend (hard memory/CPU/PID enforcement), enforced domain-wide memory budgets, artifact retrieval helpers, daemon mode and non-TypeScript bindings. Remaining gaps are tracked in [`ROADMAP.md`](./ROADMAP.md).
146
247
 
147
248
  ## Releasing
148
249
 
149
250
  Releases are tag-driven. A tag always produces a GitHub Release; the npm upload is switched on separately, so a tag can never ship an artifact that is not traceable to this repository:
150
251
 
151
252
  1. Bump `version` in `package.json`, commit, and push to `main`.
152
- 2. Push the matching tag, e.g. `git tag v0.1.5 && git push origin v0.1.5`.
253
+ 2. Push the matching tag, e.g. `git tag v0.3.0 && git push origin v0.3.0`.
153
254
  3. `.github/workflows/release.yml` re-runs typecheck, tests and the pack smoke test, refuses a tag that does not match `package.json`, creates the GitHub Release with the packed tarball, `SHA256SUMS` and a build provenance attestation attached, and — only when the repository variable `NPM_TRUSTED_PUBLISHING_ENABLED` is `true` — publishes with `npm publish --provenance` and verifies the attestation.
154
255
 
155
256
  One-time setup, in this order: (1) npmjs.com → package settings → Trusted Publisher → GitHub Actions: organization `Xio-Shark`, repository `xioflow`, workflow filename `release.yml`, environment left empty; (2) set the repository variable `NPM_TRUSTED_PUBLISHING_ENABLED=true`. Until both exist the publish job is skipped and the reason is printed in the `verify` job's log. npm's registry index can lag several minutes behind an upload, so the verification step polls and may need a job re-run.
156
257
 
157
- To attach assets to an existing tag without publishing to npm, run the workflow manually: `gh workflow run release.yml -f tag=v0.1.5`. An asset that is already attached is kept if identical and fails the run if it differs; published assets are never overwritten.
258
+ To attach assets to an existing tag without publishing to npm, run the workflow manually: `gh workflow run release.yml -f tag=v0.3.0`. An asset that is already attached is kept if identical and fails the run if it differs; published assets are never overwritten.
158
259
 
159
260
  ### Verifying a release
160
261
 
161
262
  ```bash
162
- gh release download v0.1.5 --repo Xio-Shark/xioflow
263
+ gh release download v0.3.0 --repo Xio-Shark/xioflow
163
264
  shasum -a 256 -c SHA256SUMS
164
- gh attestation verify xioflow-kernel-0.1.5.tgz --repo Xio-Shark/xioflow
265
+ gh attestation verify xioflow-kernel-0.3.0.tgz --repo Xio-Shark/xioflow
165
266
  npm audit signatures # inside a project that installed @xioflow/kernel from npm
166
267
  ```
167
268
 
@@ -0,0 +1,32 @@
1
+ import { Capability, CapabilityScope } from '../types.js';
2
+ import { resolveRealPath, isPathContained } from './path-utils.js';
3
+ import { SqliteStore } from '../store/sqlite.js';
4
+ export { resolveRealPath, isPathContained };
5
+ /**
6
+ * 规范化 Capability 作用域
7
+ */
8
+ export declare function normalizeScope(scope: CapabilityScope): CapabilityScope;
9
+ /**
10
+ * 校验并生成收窄的作用域(禁止放宽)
11
+ */
12
+ export declare function validateScopeNarrowing(parentScope: CapabilityScope, narrowerScope: Partial<CapabilityScope>): CapabilityScope;
13
+ export interface CheckCapabilityAdmissionOptions {
14
+ domainId: string;
15
+ currentEpoch: number;
16
+ store: SqliteStore;
17
+ capabilityId: string;
18
+ requiredResources?: string[];
19
+ mutationRoots?: string[];
20
+ runId?: string;
21
+ opId?: string;
22
+ }
23
+ export interface CapabilityAdmissionResult {
24
+ capability: Capability;
25
+ effectiveResources: string[];
26
+ effectiveMutationRoots: string[];
27
+ }
28
+ /**
29
+ * 准入校验:在意图登记前校验结构完整性与作用域合法性(契约 #55)
30
+ */
31
+ export declare function checkCapabilityAdmission(options: CheckCapabilityAdmissionOptions): CapabilityAdmissionResult;
32
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/capability/check.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,eAAe,EAEhB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,CAAC;AAE5C;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,eAAe,CAKtE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,eAAe,EAC5B,aAAa,EAAE,OAAO,CAAC,eAAe,CAAC,GACtC,eAAe,CAmCjB;AAED,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,WAAW,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,UAAU,CAAC;IACvB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,sBAAsB,EAAE,MAAM,EAAE,CAAC;CAClC;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,+BAA+B,GACvC,yBAAyB,CAwG3B"}
@@ -0,0 +1,136 @@
1
+ import { CapabilityViolationError, } from '../types.js';
2
+ import { resolveRealPath, isPathContained } from './path-utils.js';
3
+ export { resolveRealPath, isPathContained };
4
+ /**
5
+ * 规范化 Capability 作用域
6
+ */
7
+ export function normalizeScope(scope) {
8
+ return {
9
+ write: scope.write.map((w) => resolveRealPath(w)),
10
+ exclusive: [...new Set(scope.exclusive)],
11
+ };
12
+ }
13
+ /**
14
+ * 校验并生成收窄的作用域(禁止放宽)
15
+ */
16
+ export function validateScopeNarrowing(parentScope, narrowerScope) {
17
+ const normParent = normalizeScope(parentScope);
18
+ let newWrite = normParent.write;
19
+ if (narrowerScope.write !== undefined) {
20
+ const resolvedWrites = narrowerScope.write.map((w) => resolveRealPath(w));
21
+ for (const rw of resolvedWrites) {
22
+ const allowed = normParent.write.some((pw) => isPathContained(pw, rw));
23
+ if (!allowed) {
24
+ throw new CapabilityViolationError('attenuation_widened', `Cannot widen writable scope: '${rw}' is not contained within parent scope [${normParent.write.join(', ')}]`);
25
+ }
26
+ }
27
+ newWrite = resolvedWrites;
28
+ }
29
+ let newExclusive = normParent.exclusive;
30
+ if (narrowerScope.exclusive !== undefined) {
31
+ for (const res of narrowerScope.exclusive) {
32
+ if (!normParent.exclusive.includes(res)) {
33
+ throw new CapabilityViolationError('attenuation_widened', `Cannot widen exclusive resource scope: '${res}' is not in parent exclusive scope [${normParent.exclusive.join(', ')}]`);
34
+ }
35
+ }
36
+ newExclusive = [...new Set(narrowerScope.exclusive)];
37
+ }
38
+ return {
39
+ write: newWrite,
40
+ exclusive: newExclusive,
41
+ };
42
+ }
43
+ /**
44
+ * 准入校验:在意图登记前校验结构完整性与作用域合法性(契约 #55)
45
+ */
46
+ export function checkCapabilityAdmission(options) {
47
+ const { domainId, currentEpoch, store, capabilityId, runId, opId } = options;
48
+ const reject = (reason, message) => {
49
+ const err = new CapabilityViolationError(reason, message);
50
+ try {
51
+ store.recordJournalEvent({
52
+ domainId,
53
+ runId,
54
+ operationId: opId,
55
+ type: 'CAPABILITY_REJECTED',
56
+ payload: {
57
+ capabilityId,
58
+ reason,
59
+ message,
60
+ requiredResources: options.requiredResources,
61
+ mutationRoots: options.mutationRoots,
62
+ },
63
+ timestamp: new Date().toISOString(),
64
+ });
65
+ }
66
+ catch {
67
+ // 保证即便 journal 写入异常也必须抛出 CapabilityViolationError
68
+ }
69
+ throw err;
70
+ };
71
+ const cap = store.getCapability(capabilityId);
72
+ if (!cap) {
73
+ return reject('revoked', `Capability '${capabilityId}' not found`);
74
+ }
75
+ if (cap.epoch !== currentEpoch) {
76
+ return reject('epoch_mismatch', `Capability '${capabilityId}' epoch ${cap.epoch} does not match current domain epoch ${currentEpoch}`);
77
+ }
78
+ if (new Date(cap.expiresAt).getTime() <= Date.now()) {
79
+ return reject('expired', `Capability '${capabilityId}' expired at ${cap.expiresAt}`);
80
+ }
81
+ if (cap.revokedAt) {
82
+ return reject('revoked', `Capability '${capabilityId}' was revoked at ${cap.revokedAt}`);
83
+ }
84
+ // 递归校验祖先链,父撤销/过期/换代导致子级联失效
85
+ let ancestorId = cap.parentId;
86
+ while (ancestorId) {
87
+ const ancestor = store.getCapability(ancestorId);
88
+ if (!ancestor) {
89
+ return reject('revoked', `Capability ancestor '${ancestorId}' not found`);
90
+ }
91
+ if (ancestor.revokedAt) {
92
+ return reject('revoked', `Capability ancestor '${ancestorId}' was revoked`);
93
+ }
94
+ if (ancestor.epoch !== currentEpoch) {
95
+ return reject('epoch_mismatch', `Capability ancestor '${ancestorId}' epoch mismatch`);
96
+ }
97
+ if (new Date(ancestor.expiresAt).getTime() <= Date.now()) {
98
+ return reject('expired', `Capability ancestor '${ancestorId}' has expired`);
99
+ }
100
+ ancestorId = ancestor.parentId;
101
+ }
102
+ // 1. 资源推导与越界检查
103
+ let effectiveResources;
104
+ if (options.requiredResources !== undefined && options.requiredResources.length > 0) {
105
+ for (const res of options.requiredResources) {
106
+ if (!cap.scope.exclusive.includes(res)) {
107
+ return reject('out_of_scope_resource', `Resource '${res}' is not permitted by capability '${capabilityId}' exclusive scope [${cap.scope.exclusive.join(', ')}]`);
108
+ }
109
+ }
110
+ effectiveResources = [...options.requiredResources];
111
+ }
112
+ else {
113
+ effectiveResources = [...cap.scope.exclusive];
114
+ }
115
+ // 2. 写入根推导与越界检查 (按路径段真实路径包含判定)
116
+ let effectiveMutationRoots;
117
+ if (options.mutationRoots !== undefined && options.mutationRoots.length > 0) {
118
+ const resolvedRoots = options.mutationRoots.map((r) => resolveRealPath(r));
119
+ for (const rr of resolvedRoots) {
120
+ const allowed = cap.scope.write.some((cw) => isPathContained(cw, rr));
121
+ if (!allowed) {
122
+ return reject('out_of_scope_path', `Mutation root '${rr}' is outside capability '${capabilityId}' writable scope [${cap.scope.write.join(', ')}]`);
123
+ }
124
+ }
125
+ effectiveMutationRoots = resolvedRoots;
126
+ }
127
+ else {
128
+ effectiveMutationRoots = [...cap.scope.write];
129
+ }
130
+ return {
131
+ capability: cap,
132
+ effectiveResources,
133
+ effectiveMutationRoots,
134
+ };
135
+ }
136
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/capability/check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export * from './path-utils.js';
2
+ export * from './check.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/capability/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './path-utils.js';
2
+ export * from './check.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/capability/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 规范化真实路径(处理软链接、.. 与大小写)
3
+ * 若路径不存在,回溯寻找最近存在的祖先目录 realpath 后拼接剩余段
4
+ */
5
+ export declare function resolveRealPath(p: string): string;
6
+ /**
7
+ * 路径包含判定:按路径段严格比对(/a/b 不包含 /a/bc)
8
+ * 双方均做 realpath,杜绝符号链接逃逸与 .. 越界
9
+ */
10
+ export declare function isPathContained(parentPath: string, childPath: string): boolean;
11
+ //# sourceMappingURL=path-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../src/capability/path-utils.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAqBjD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAS9E"}
@@ -0,0 +1,46 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ /**
4
+ * 规范化真实路径(处理软链接、.. 与大小写)
5
+ * 若路径不存在,回溯寻找最近存在的祖先目录 realpath 后拼接剩余段
6
+ */
7
+ export function resolveRealPath(p) {
8
+ const resolved = path.resolve(p);
9
+ try {
10
+ return fs.realpathSync(resolved);
11
+ }
12
+ catch {
13
+ const parts = [];
14
+ let cur = resolved;
15
+ while (!fs.existsSync(cur)) {
16
+ const parent = path.dirname(cur);
17
+ if (parent === cur)
18
+ break;
19
+ parts.unshift(path.basename(cur));
20
+ cur = parent;
21
+ }
22
+ let realBase = cur;
23
+ try {
24
+ realBase = fs.realpathSync(cur);
25
+ }
26
+ catch {
27
+ realBase = cur;
28
+ }
29
+ return parts.length > 0 ? path.join(realBase, ...parts) : realBase;
30
+ }
31
+ }
32
+ /**
33
+ * 路径包含判定:按路径段严格比对(/a/b 不包含 /a/bc)
34
+ * 双方均做 realpath,杜绝符号链接逃逸与 .. 越界
35
+ */
36
+ export function isPathContained(parentPath, childPath) {
37
+ const realParent = resolveRealPath(parentPath);
38
+ const realChild = resolveRealPath(childPath);
39
+ if (realParent === realChild)
40
+ return true;
41
+ if (realParent === path.sep)
42
+ return true;
43
+ const parentWithSep = realParent.endsWith(path.sep) ? realParent : realParent + path.sep;
44
+ return realChild.startsWith(parentWithSep);
45
+ }
46
+ //# sourceMappingURL=path-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-utils.js","sourceRoot":"","sources":["../../src/capability/path-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,CAAS;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACjC,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,IAAI,GAAG,GAAG,QAAQ,CAAC;QACnB,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACjC,IAAI,MAAM,KAAK,GAAG;gBAAE,MAAM;YAC1B,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;YAClC,GAAG,GAAG,MAAM,CAAC;QACf,CAAC;QACD,IAAI,QAAQ,GAAG,GAAG,CAAC;QACnB,IAAI,CAAC;YACH,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,GAAG,GAAG,CAAC;QACjB,CAAC;QACD,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACrE,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,UAAkB,EAAE,SAAiB;IACnE,MAAM,UAAU,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IAC/C,MAAM,SAAS,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;IAE7C,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC1C,IAAI,UAAU,KAAK,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEzC,MAAM,aAAa,GAAG,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC;IACzF,OAAO,SAAS,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { ConfinementDriver } from '../types.js';
2
+ import { StructuredCommand } from '../driver/types.js';
3
+ export declare class BubblewrapConfinementDriver implements ConfinementDriver {
4
+ readonly name = "bubblewrap";
5
+ static isAvailable(): boolean;
6
+ wrap(command: StructuredCommand, writableRoots: string[]): StructuredCommand;
7
+ }
8
+ //# sourceMappingURL=bubblewrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bubblewrap.d.ts","sourceRoot":"","sources":["../../src/confinement/bubblewrap.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAiBvD,qBAAa,2BAA4B,YAAW,iBAAiB;IACnE,SAAgB,IAAI,gBAAgB;IAEpC,OAAc,WAAW,IAAI,OAAO,CAEnC;IAEM,IAAI,CAAC,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAwBlF;CACF"}
@@ -0,0 +1,45 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { resolveRealPath } from '../capability/path-utils.js';
4
+ function findExecutable(name) {
5
+ const envPath = process.env.PATH || '';
6
+ const dirs = envPath.split(path.delimiter);
7
+ for (const dir of dirs) {
8
+ const candidate = path.join(dir, name);
9
+ try {
10
+ if (fs.existsSync(candidate) && (fs.statSync(candidate).mode & 0o111)) {
11
+ return candidate;
12
+ }
13
+ }
14
+ catch { }
15
+ }
16
+ return null;
17
+ }
18
+ export class BubblewrapConfinementDriver {
19
+ name = 'bubblewrap';
20
+ static isAvailable() {
21
+ return process.platform === 'linux' && findExecutable('bwrap') !== null;
22
+ }
23
+ wrap(command, writableRoots) {
24
+ const bwrapPath = findExecutable('bwrap');
25
+ if (!bwrapPath || process.platform !== 'linux') {
26
+ throw new Error("Confinement driver 'bubblewrap' is not available on this platform");
27
+ }
28
+ const realRoots = writableRoots.map((r) => resolveRealPath(r));
29
+ const bwrapArgs = [
30
+ '--ro-bind', '/', '/',
31
+ '--dev', '/dev',
32
+ '--proc', '/proc',
33
+ ];
34
+ for (const root of realRoots) {
35
+ bwrapArgs.push('--bind', root, root);
36
+ }
37
+ bwrapArgs.push('--', command.execPath, ...command.args);
38
+ return {
39
+ ...command,
40
+ execPath: bwrapPath,
41
+ args: bwrapArgs,
42
+ };
43
+ }
44
+ }
45
+ //# sourceMappingURL=bubblewrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bubblewrap.js","sourceRoot":"","sources":["../../src/confinement/bubblewrap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,SAAS,cAAc,CAAC,IAAY;IAClC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IACvC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC3C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC;YACH,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;gBACtE,OAAO,SAAS,CAAC;YACnB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,OAAO,2BAA2B;IACtB,IAAI,GAAG,YAAY,CAAC;IAE7B,MAAM,CAAC,WAAW;QACvB,OAAO,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,cAAc,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IAC1E,CAAC;IAEM,IAAI,CAAC,OAA0B,EAAE,aAAuB;QAC7D,MAAM,SAAS,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC,SAAS,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;QACvF,CAAC;QAED,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/D,MAAM,SAAS,GAAa;YAC1B,WAAW,EAAE,GAAG,EAAE,GAAG;YACrB,OAAO,EAAE,MAAM;YACf,QAAQ,EAAE,OAAO;SAClB,CAAC;QAEF,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QACvC,CAAC;QAED,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAExD,OAAO;YACL,GAAG,OAAO;YACV,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,SAAS;SAChB,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,4 @@
1
+ import { ConfinementDriver } from '../types.js';
2
+ export declare function detectAvailableConfinementDrivers(): string[];
3
+ export declare function createConfinementDriver(name?: string): ConfinementDriver | null;
4
+ //# sourceMappingURL=detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/confinement/detector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAKhD,wBAAgB,iCAAiC,IAAI,MAAM,EAAE,CAY5D;AAED,wBAAgB,uBAAuB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAa/E"}
@@ -0,0 +1,35 @@
1
+ import { SandboxExecConfinementDriver } from './sandbox-exec.js';
2
+ import { BubblewrapConfinementDriver } from './bubblewrap.js';
3
+ import { SrtConfinementDriver } from './srt.js';
4
+ export function detectAvailableConfinementDrivers() {
5
+ const drivers = [];
6
+ if (SrtConfinementDriver.isAvailable()) {
7
+ drivers.push('srt');
8
+ }
9
+ if (SandboxExecConfinementDriver.isAvailable()) {
10
+ drivers.push('sandbox-exec');
11
+ }
12
+ if (BubblewrapConfinementDriver.isAvailable()) {
13
+ drivers.push('bubblewrap');
14
+ }
15
+ return drivers;
16
+ }
17
+ export function createConfinementDriver(name) {
18
+ if (name === 'sandbox-exec')
19
+ return new SandboxExecConfinementDriver();
20
+ if (name === 'bubblewrap')
21
+ return new BubblewrapConfinementDriver();
22
+ if (name === 'srt')
23
+ return new SrtConfinementDriver();
24
+ if (!name) {
25
+ if (SandboxExecConfinementDriver.isAvailable())
26
+ return new SandboxExecConfinementDriver();
27
+ if (BubblewrapConfinementDriver.isAvailable())
28
+ return new BubblewrapConfinementDriver();
29
+ if (SrtConfinementDriver.isAvailable())
30
+ return new SrtConfinementDriver();
31
+ return null;
32
+ }
33
+ throw new Error(`Unknown confinement driver: '${name}'`);
34
+ }
35
+ //# sourceMappingURL=detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.js","sourceRoot":"","sources":["../../src/confinement/detector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAEhD,MAAM,UAAU,iCAAiC;IAC/C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,oBAAoB,CAAC,WAAW,EAAE,EAAE,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IACD,IAAI,4BAA4B,CAAC,WAAW,EAAE,EAAE,CAAC;QAC/C,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,2BAA2B,CAAC,WAAW,EAAE,EAAE,CAAC;QAC9C,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,IAAa;IACnD,IAAI,IAAI,KAAK,cAAc;QAAE,OAAO,IAAI,4BAA4B,EAAE,CAAC;IACvE,IAAI,IAAI,KAAK,YAAY;QAAE,OAAO,IAAI,2BAA2B,EAAE,CAAC;IACpE,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,IAAI,oBAAoB,EAAE,CAAC;IAEtD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,4BAA4B,CAAC,WAAW,EAAE;YAAE,OAAO,IAAI,4BAA4B,EAAE,CAAC;QAC1F,IAAI,2BAA2B,CAAC,WAAW,EAAE;YAAE,OAAO,IAAI,2BAA2B,EAAE,CAAC;QACxF,IAAI,oBAAoB,CAAC,WAAW,EAAE;YAAE,OAAO,IAAI,oBAAoB,EAAE,CAAC;QAC1E,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,GAAG,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from './sandbox-exec.js';
2
+ export * from './bubblewrap.js';
3
+ export * from './srt.js';
4
+ export * from './detector.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/confinement/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from './sandbox-exec.js';
2
+ export * from './bubblewrap.js';
3
+ export * from './srt.js';
4
+ export * from './detector.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/confinement/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { ConfinementDriver } from '../types.js';
2
+ import { StructuredCommand } from '../driver/types.js';
3
+ export declare class SandboxExecConfinementDriver implements ConfinementDriver {
4
+ readonly name = "sandbox-exec";
5
+ static isAvailable(): boolean;
6
+ wrap(command: StructuredCommand, writableRoots: string[]): StructuredCommand;
7
+ }
8
+ //# sourceMappingURL=sandbox-exec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-exec.d.ts","sourceRoot":"","sources":["../../src/confinement/sandbox-exec.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGvD,qBAAa,4BAA6B,YAAW,iBAAiB;IACpE,SAAgB,IAAI,kBAAkB;IAEtC,OAAc,WAAW,IAAI,OAAO,CAEnC;IAEM,IAAI,CAAC,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAwBlF;CACF"}