@xioflow/kernel 0.1.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/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/domain.d.ts +56 -0
- package/dist/domain.d.ts.map +1 -0
- package/dist/domain.js +322 -0
- package/dist/domain.js.map +1 -0
- package/dist/driver/node-driver.d.ts +29 -0
- package/dist/driver/node-driver.d.ts.map +1 -0
- package/dist/driver/node-driver.js +404 -0
- package/dist/driver/node-driver.js.map +1 -0
- package/dist/driver/types.d.ts +60 -0
- package/dist/driver/types.d.ts.map +1 -0
- package/dist/driver/types.js +2 -0
- package/dist/driver/types.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/recovery/engine.d.ts +19 -0
- package/dist/recovery/engine.d.ts.map +1 -0
- package/dist/recovery/engine.js +129 -0
- package/dist/recovery/engine.js.map +1 -0
- package/dist/store/schema.d.ts +5 -0
- package/dist/store/schema.d.ts.map +1 -0
- package/dist/store/schema.js +78 -0
- package/dist/store/schema.js.map +1 -0
- package/dist/store/sqlite.d.ts +53 -0
- package/dist/store/sqlite.d.ts.map +1 -0
- package/dist/store/sqlite.js +466 -0
- package/dist/store/sqlite.js.map +1 -0
- package/dist/supervisor/supervisor.d.ts +36 -0
- package/dist/supervisor/supervisor.d.ts.map +1 -0
- package/dist/supervisor/supervisor.js +421 -0
- package/dist/supervisor/supervisor.js.map +1 -0
- package/dist/testing/contract-suite.d.ts +11 -0
- package/dist/testing/contract-suite.d.ts.map +1 -0
- package/dist/testing/contract-suite.js +582 -0
- package/dist/testing/contract-suite.js.map +1 -0
- package/dist/types.d.ts +190 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +90 -0
- package/dist/types.js.map +1 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Xio-Shark
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# @xioflow/kernel
|
|
2
|
+
|
|
3
|
+
A supervised execution kernel for AI agent runtimes. Zero runtime dependencies.
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
## Guarantees
|
|
8
|
+
|
|
9
|
+
| Guarantee | Mechanism |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| No fake running | Admission check, then the operation intent is persisted to SQLite, then the process is spawned. A spawn that fails is recorded as `failed` (exit code 127); it never appears as `running`. |
|
|
12
|
+
| No blind replay | An operation whose outcome cannot be determined is recorded as `indeterminate`, keeps its exclusive leases, and is never auto-retried on restart. |
|
|
13
|
+
| 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`. |
|
|
14
|
+
| 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. |
|
|
15
|
+
| 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. |
|
|
16
|
+
| 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. |
|
|
17
|
+
| 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. |
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- 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.
|
|
22
|
+
- Linux and macOS. Descendant enumeration and group termination use `ps(1)` and POSIX process groups.
|
|
23
|
+
- Windows is not supported in 0.1. There the driver reports `processGroupKill: false` and `descendantEnumeration: 'none'` rather than pretending it can contain processes.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @xioflow/kernel
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quickstart
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
import { ExecutionDomain, NodePlatformDriver, ProcessSupervisor } from '@xioflow/kernel';
|
|
35
|
+
|
|
36
|
+
// The domain directory will hold domain.db, domain.lock and artifacts/.
|
|
37
|
+
const domain = ExecutionDomain.acquire('/path/to/workspace/.xioflow', 'my-domain');
|
|
38
|
+
const supervisor = new ProcessSupervisor(domain, new NodePlatformDriver());
|
|
39
|
+
|
|
40
|
+
// Every operation belongs to a Run, and every Run to a Task. Register them first:
|
|
41
|
+
const store = domain.getStore();
|
|
42
|
+
store.saveTask({
|
|
43
|
+
id: 'task-1',
|
|
44
|
+
domainId: domain.domainId,
|
|
45
|
+
name: 'fix failing tests',
|
|
46
|
+
createdAt: new Date().toISOString(),
|
|
47
|
+
});
|
|
48
|
+
store.saveRun({
|
|
49
|
+
id: 'run-1',
|
|
50
|
+
taskId: 'task-1',
|
|
51
|
+
domainId: domain.domainId,
|
|
52
|
+
owner: 'my-agent-session',
|
|
53
|
+
status: 'running',
|
|
54
|
+
startedAt: new Date().toISOString(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const result = await supervisor.executeProcess({
|
|
58
|
+
runId: 'run-1',
|
|
59
|
+
opId: 'op-1',
|
|
60
|
+
name: 'npm test',
|
|
61
|
+
command: { execPath: 'npm', args: ['test'], cwd: '/path/to/workspace' },
|
|
62
|
+
requiredResources: ['workspace:write:/path/to/workspace'],
|
|
63
|
+
timeoutMs: 120_000,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
console.log(result.status, result.exitCode, result.isTruncated, result.outputRef);
|
|
67
|
+
|
|
68
|
+
domain.close(); // releases the owner lease and the domain lock
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`requiredResources` are arbitrary exclusive lease IDs. Two operations requesting the same ID never run concurrently: the second one waits up to `waitTimeoutMs`, or fails with a `ResourceConflictError` naming the current holder.
|
|
72
|
+
|
|
73
|
+
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.
|
|
74
|
+
|
|
75
|
+
## Crash recovery
|
|
76
|
+
|
|
77
|
+
After a restart, reacquire the domain and run the recovery engine:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
import { ExecutionDomain, NodePlatformDriver, RecoveryEngine } from '@xioflow/kernel';
|
|
81
|
+
|
|
82
|
+
const domain = ExecutionDomain.acquire('/path/to/workspace/.xioflow', 'my-domain');
|
|
83
|
+
const report = await new RecoveryEngine(domain, new NodePlatformDriver()).recover();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
For every unfinished operation, recovery verifies the recorded process identity against the OS:
|
|
87
|
+
|
|
88
|
+
| Observed state | Action | Leases |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| Intent persisted, never spawned | cleaned up as failed | released |
|
|
91
|
+
| Process confirmed dead | marked dead as failed | released |
|
|
92
|
+
| Process alive and identity confirmed | stopped through the stop pipeline | released |
|
|
93
|
+
| Identity cannot be determined | left `indeterminate` | retained, manual decision required |
|
|
94
|
+
|
|
95
|
+
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.
|
|
96
|
+
|
|
97
|
+
## Design notes
|
|
98
|
+
|
|
99
|
+
### Verify your own runtime
|
|
100
|
+
|
|
101
|
+
The package ships the same 15-item contract suite that the kernel itself is tested against, so an embedding runtime can prove its consumer honors these guarantees:
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
import { defineContractTestSuite } from '@xioflow/kernel/testing';
|
|
105
|
+
|
|
106
|
+
defineContractTestSuite('My Agent Runtime', async () => ({
|
|
107
|
+
domain,
|
|
108
|
+
driver,
|
|
109
|
+
supervisor,
|
|
110
|
+
tempDir,
|
|
111
|
+
workflowType: 'memory', // or 'headless' | 'three_piece'
|
|
112
|
+
cleanup: async () => domain.close(),
|
|
113
|
+
}));
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`vitest` is an optional peer dependency, and the subpath is only loaded if you import it.
|
|
117
|
+
|
|
118
|
+
- Persistence is SQLite with WAL and `synchronous = FULL`, so committed facts survive power loss.
|
|
119
|
+
- One execution domain is one workspace-scoped store plus one active owner. Isolation is rebuilt from the store before any new operation is admitted.
|
|
120
|
+
- 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.
|
|
121
|
+
- Protocol specification: [`ARCHITECTURE.md`](./ARCHITECTURE.md) (Chinese).
|
|
122
|
+
|
|
123
|
+
## Status
|
|
124
|
+
|
|
125
|
+
0.1.0, 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.
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
package/dist/domain.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { SqliteStore } from './store/sqlite.js';
|
|
2
|
+
import { Operation, OwnerRecord, ResourceBudget, DomainBudget, DomainLockedError, ResourceConflictError, EpochFencedError } from './types.js';
|
|
3
|
+
export { DomainLockedError, ResourceConflictError, EpochFencedError };
|
|
4
|
+
export declare class ExecutionDomain {
|
|
5
|
+
readonly domainPath: string;
|
|
6
|
+
readonly domainId: string;
|
|
7
|
+
private readonly lockFilePath;
|
|
8
|
+
private store;
|
|
9
|
+
private lockFd;
|
|
10
|
+
private ownerRecord;
|
|
11
|
+
private domainBudget?;
|
|
12
|
+
private inMemoryLockedResources;
|
|
13
|
+
private inMemoryBudgets;
|
|
14
|
+
private heartbeatInterval;
|
|
15
|
+
private isFenced;
|
|
16
|
+
private constructor();
|
|
17
|
+
static acquire(domainPath: string, domainId?: string): ExecutionDomain;
|
|
18
|
+
private static checkProcessAlive;
|
|
19
|
+
getOwnerRecord(): OwnerRecord;
|
|
20
|
+
getEpoch(): number;
|
|
21
|
+
setDomainBudget(budget: DomainBudget): void;
|
|
22
|
+
getDomainBudget(): DomainBudget | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* 启动时优先重建隔离屏障 (Recovery-Before-Execution)
|
|
25
|
+
*/
|
|
26
|
+
rebuildIsolationFromStore(): void;
|
|
27
|
+
getStore(): SqliteStore;
|
|
28
|
+
isResourceLocked(resourceId: string): boolean;
|
|
29
|
+
getResourceOwner(resourceId: string): string | undefined;
|
|
30
|
+
getAllocatedResources(): Map<string, string>;
|
|
31
|
+
isDomainFenced(): boolean;
|
|
32
|
+
private assertNotFenced;
|
|
33
|
+
/**
|
|
34
|
+
* 刷新租约心跳,若失败则触发代际栅栏与写凭证销毁
|
|
35
|
+
*/
|
|
36
|
+
renewHeartbeat(): void;
|
|
37
|
+
/**
|
|
38
|
+
* 申请占用资源(支持隔离检查与配额校验)
|
|
39
|
+
*/
|
|
40
|
+
allocateResources(operationId: string, resources: string[], waitedDurationMs?: number, budget?: ResourceBudget): void;
|
|
41
|
+
/**
|
|
42
|
+
* 具备排队等待与诊断的异步资源分配
|
|
43
|
+
*/
|
|
44
|
+
allocateResourcesWithWait(operationId: string, resources: string[], maxWaitMs?: number, budget?: ResourceBudget): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* 释放资源占用
|
|
47
|
+
*/
|
|
48
|
+
releaseResources(operationId: string, resources?: string[]): void;
|
|
49
|
+
/**
|
|
50
|
+
* 意图登记协议:先持久化意图与占用,再允许驱动调用
|
|
51
|
+
*/
|
|
52
|
+
registerOperationIntent(op: Operation): void;
|
|
53
|
+
isClosed(): boolean;
|
|
54
|
+
close(): void;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=domain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../src/domain.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAEL,SAAS,EAET,WAAW,EACX,cAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,CAAC;AAEtE,qBAAa,eAAe;IAC1B,SAAgB,UAAU,EAAE,MAAM,CAAC;IACnC,SAAgB,QAAQ,EAAE,MAAM,CAAC;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,YAAY,CAAC,CAAe;IAEpC,OAAO,CAAC,uBAAuB,CAAkC;IAEjE,OAAO,CAAC,eAAe,CAA0C;IACjE,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,QAAQ,CAAkB;IAElC,OAAO,eAqCN;IAED,OAAc,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAkB,GAAG,eAAe,CAqDvF;IAED,OAAO,CAAC,MAAM,CAAC,iBAAiB;IASzB,cAAc,IAAI,WAAW,CAEnC;IAEM,QAAQ,IAAI,MAAM,CAExB;IAEM,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAEjD;IAEM,eAAe,IAAI,YAAY,GAAG,SAAS,CAEjD;IAED;;OAEG;IACI,yBAAyB,IAAI,IAAI,CAuBvC;IAEM,QAAQ,IAAI,WAAW,CAE7B;IAEM,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAEnD;IAEM,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE9D;IAEM,qBAAqB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAElD;IAEM,cAAc,IAAI,OAAO,CAE/B;IAED,OAAO,CAAC,eAAe;IAWvB;;OAEG;IACI,cAAc,IAAI,IAAI,CAqB5B;IAED;;OAEG;IACI,iBAAiB,CACtB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EAAE,EACnB,gBAAgB,GAAE,MAAU,EAC5B,MAAM,CAAC,EAAE,cAAc,GACtB,IAAI,CA6BN;IAED;;OAEG;IACU,yBAAyB,CACpC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EAAE,EACnB,SAAS,GAAE,MAAU,EACrB,MAAM,CAAC,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CAkBf;IAED;;OAEG;IACI,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAiBvE;IAED;;OAEG;IACI,uBAAuB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAalD;IAEM,QAAQ,IAAI,OAAO,CAEzB;IAEM,KAAK,IAAI,IAAI,CAmCnB;CACF"}
|
package/dist/domain.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { SqliteStore } from './store/sqlite.js';
|
|
5
|
+
import { DomainLockedError, ResourceConflictError, EpochFencedError, } from './types.js';
|
|
6
|
+
export { DomainLockedError, ResourceConflictError, EpochFencedError };
|
|
7
|
+
export class ExecutionDomain {
|
|
8
|
+
domainPath;
|
|
9
|
+
domainId;
|
|
10
|
+
lockFilePath;
|
|
11
|
+
store;
|
|
12
|
+
lockFd = null;
|
|
13
|
+
ownerRecord;
|
|
14
|
+
domainBudget;
|
|
15
|
+
// 内存隔离表:resourceId -> operationId
|
|
16
|
+
inMemoryLockedResources = new Map();
|
|
17
|
+
// 内存活跃操作预算
|
|
18
|
+
inMemoryBudgets = new Map();
|
|
19
|
+
heartbeatInterval = null;
|
|
20
|
+
isFenced = false;
|
|
21
|
+
constructor(domainPath, domainId, lockFilePath, lockFd) {
|
|
22
|
+
this.domainPath = domainPath;
|
|
23
|
+
this.domainId = domainId;
|
|
24
|
+
this.lockFilePath = lockFilePath;
|
|
25
|
+
this.lockFd = lockFd;
|
|
26
|
+
const dbPath = path.join(domainPath, 'domain.db');
|
|
27
|
+
this.store = new SqliteStore(dbPath);
|
|
28
|
+
const ownerId = `owner-${process.pid}-${Date.now()}`;
|
|
29
|
+
this.ownerRecord = this.store.acquireOwnerLease(domainId, ownerId, os.hostname(), 60000);
|
|
30
|
+
this.rebuildIsolationFromStore();
|
|
31
|
+
// 周期心跳续约租约 (每 10s 续约一次,TTL 60s)
|
|
32
|
+
this.heartbeatInterval = setInterval(() => {
|
|
33
|
+
try {
|
|
34
|
+
if (!this.isClosed() && this.store && this.ownerRecord) {
|
|
35
|
+
this.ownerRecord = this.store.acquireOwnerLease(this.domainId, this.ownerRecord.ownerId, os.hostname(), 60000);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
this.isFenced = true;
|
|
40
|
+
if (this.heartbeatInterval) {
|
|
41
|
+
clearInterval(this.heartbeatInterval);
|
|
42
|
+
this.heartbeatInterval = null;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
this.store.unsafeSetCurrentEpochForTesting(-1);
|
|
46
|
+
}
|
|
47
|
+
catch { }
|
|
48
|
+
}
|
|
49
|
+
}, 10000);
|
|
50
|
+
if (this.heartbeatInterval.unref) {
|
|
51
|
+
this.heartbeatInterval.unref();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
static acquire(domainPath, domainId = 'default') {
|
|
55
|
+
if (!fs.existsSync(domainPath)) {
|
|
56
|
+
fs.mkdirSync(domainPath, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
const lockFilePath = path.join(domainPath, 'domain.lock');
|
|
59
|
+
let fd = null;
|
|
60
|
+
try {
|
|
61
|
+
// 尝试独占创建锁文件
|
|
62
|
+
fd = fs.openSync(lockFilePath, 'wx');
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err.code === 'EEXIST') {
|
|
66
|
+
// 锁文件已存在,读取元数据核验进程是否存活
|
|
67
|
+
let lockMeta = null;
|
|
68
|
+
try {
|
|
69
|
+
const content = fs.readFileSync(lockFilePath, 'utf8');
|
|
70
|
+
lockMeta = JSON.parse(content);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// 损坏的锁文件视为失效
|
|
74
|
+
}
|
|
75
|
+
if (lockMeta) {
|
|
76
|
+
const isAlive = ExecutionDomain.checkProcessAlive(lockMeta.ownerPid);
|
|
77
|
+
if (isAlive) {
|
|
78
|
+
throw new DomainLockedError(lockMeta.domainId, lockMeta.ownerPid, lockMeta.acquiredAt);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// 旧进程已死亡,安全覆盖锁
|
|
82
|
+
try {
|
|
83
|
+
fs.unlinkSync(lockFilePath);
|
|
84
|
+
}
|
|
85
|
+
catch { }
|
|
86
|
+
fd = fs.openSync(lockFilePath, 'wx');
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const metadata = {
|
|
93
|
+
domainId,
|
|
94
|
+
ownerPid: process.pid,
|
|
95
|
+
acquiredAt: new Date().toISOString(),
|
|
96
|
+
hostname: os.hostname(),
|
|
97
|
+
};
|
|
98
|
+
fs.writeSync(fd, JSON.stringify(metadata, null, 2));
|
|
99
|
+
return new ExecutionDomain(domainPath, domainId, lockFilePath, fd);
|
|
100
|
+
}
|
|
101
|
+
static checkProcessAlive(pid) {
|
|
102
|
+
try {
|
|
103
|
+
process.kill(pid, 0);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
return err.code === 'EPERM'; // 存在但无权限发送信号,也算存活
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
getOwnerRecord() {
|
|
111
|
+
return this.ownerRecord;
|
|
112
|
+
}
|
|
113
|
+
getEpoch() {
|
|
114
|
+
return this.ownerRecord.epoch;
|
|
115
|
+
}
|
|
116
|
+
setDomainBudget(budget) {
|
|
117
|
+
this.domainBudget = budget;
|
|
118
|
+
}
|
|
119
|
+
getDomainBudget() {
|
|
120
|
+
return this.domainBudget;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 启动时优先重建隔离屏障 (Recovery-Before-Execution)
|
|
124
|
+
*/
|
|
125
|
+
rebuildIsolationFromStore() {
|
|
126
|
+
this.inMemoryLockedResources.clear();
|
|
127
|
+
this.inMemoryBudgets.clear();
|
|
128
|
+
// 1. 读取持久化未释放的 resource_leases
|
|
129
|
+
const leases = this.store.getPersistedResourceLeases(this.domainId);
|
|
130
|
+
for (const lease of leases) {
|
|
131
|
+
this.inMemoryLockedResources.set(lease.resourceId, lease.operationId);
|
|
132
|
+
if (lease.budget) {
|
|
133
|
+
this.inMemoryBudgets.set(lease.operationId, lease.budget);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// 2. 核对未终结操作声明的所有资源
|
|
137
|
+
const unfinishedOps = this.store.getUnfinishedOperations(this.domainId);
|
|
138
|
+
for (const op of unfinishedOps) {
|
|
139
|
+
for (const res of op.requiredResources) {
|
|
140
|
+
this.inMemoryLockedResources.set(res, op.id);
|
|
141
|
+
}
|
|
142
|
+
if (op.resourceBudget) {
|
|
143
|
+
this.inMemoryBudgets.set(op.id, op.resourceBudget);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
getStore() {
|
|
148
|
+
return this.store;
|
|
149
|
+
}
|
|
150
|
+
isResourceLocked(resourceId) {
|
|
151
|
+
return this.inMemoryLockedResources.has(resourceId);
|
|
152
|
+
}
|
|
153
|
+
getResourceOwner(resourceId) {
|
|
154
|
+
return this.inMemoryLockedResources.get(resourceId);
|
|
155
|
+
}
|
|
156
|
+
getAllocatedResources() {
|
|
157
|
+
return new Map(this.inMemoryLockedResources);
|
|
158
|
+
}
|
|
159
|
+
isDomainFenced() {
|
|
160
|
+
return this.isFenced;
|
|
161
|
+
}
|
|
162
|
+
assertNotFenced() {
|
|
163
|
+
if (this.isFenced) {
|
|
164
|
+
const owner = this.store.getOwner(this.domainId);
|
|
165
|
+
throw new EpochFencedError(this.domainId, this.ownerRecord ? this.ownerRecord.epoch : 0, owner ? owner.epoch : -1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* 刷新租约心跳,若失败则触发代际栅栏与写凭证销毁
|
|
170
|
+
*/
|
|
171
|
+
renewHeartbeat() {
|
|
172
|
+
try {
|
|
173
|
+
if (!this.isClosed() && this.store && this.ownerRecord) {
|
|
174
|
+
this.ownerRecord = this.store.acquireOwnerLease(this.domainId, this.ownerRecord.ownerId, os.hostname(), 60000);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
this.isFenced = true;
|
|
179
|
+
if (this.heartbeatInterval) {
|
|
180
|
+
clearInterval(this.heartbeatInterval);
|
|
181
|
+
this.heartbeatInterval = null;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
this.store.unsafeSetCurrentEpochForTesting(-1);
|
|
185
|
+
}
|
|
186
|
+
catch { }
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* 申请占用资源(支持隔离检查与配额校验)
|
|
192
|
+
*/
|
|
193
|
+
allocateResources(operationId, resources, waitedDurationMs = 0, budget) {
|
|
194
|
+
this.assertNotFenced();
|
|
195
|
+
// 1. 检查资源冲突
|
|
196
|
+
for (const res of resources) {
|
|
197
|
+
const existingOwner = this.inMemoryLockedResources.get(res);
|
|
198
|
+
if (existingOwner && existingOwner !== operationId) {
|
|
199
|
+
throw new ResourceConflictError(res, existingOwner, operationId, waitedDurationMs);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// 2. 检查域级配额(并发数)
|
|
203
|
+
if (this.domainBudget) {
|
|
204
|
+
const activeOps = new Set(this.inMemoryLockedResources.values());
|
|
205
|
+
if (this.domainBudget.maxConcurrentOps &&
|
|
206
|
+
activeOps.size >= this.domainBudget.maxConcurrentOps &&
|
|
207
|
+
!activeOps.has(operationId)) {
|
|
208
|
+
const holder = Array.from(activeOps)[0] || 'domain-concurrency-cap';
|
|
209
|
+
throw new ResourceConflictError('domain:max_concurrent_ops', holder, operationId, waitedDurationMs);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
for (const res of resources) {
|
|
213
|
+
this.inMemoryLockedResources.set(res, operationId);
|
|
214
|
+
}
|
|
215
|
+
if (budget) {
|
|
216
|
+
this.inMemoryBudgets.set(operationId, budget);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* 具备排队等待与诊断的异步资源分配
|
|
221
|
+
*/
|
|
222
|
+
async allocateResourcesWithWait(operationId, resources, maxWaitMs = 0, budget) {
|
|
223
|
+
const startTime = Date.now();
|
|
224
|
+
while (true) {
|
|
225
|
+
try {
|
|
226
|
+
this.allocateResources(operationId, resources, Date.now() - startTime, budget);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
if (err instanceof ResourceConflictError) {
|
|
231
|
+
const elapsed = Date.now() - startTime;
|
|
232
|
+
if (elapsed >= maxWaitMs) {
|
|
233
|
+
throw new ResourceConflictError(err.resourceId, err.existingOwnerOpId, operationId, elapsed);
|
|
234
|
+
}
|
|
235
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
throw err;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* 释放资源占用
|
|
245
|
+
*/
|
|
246
|
+
releaseResources(operationId, resources) {
|
|
247
|
+
this.inMemoryBudgets.delete(operationId);
|
|
248
|
+
if (resources) {
|
|
249
|
+
for (const res of resources) {
|
|
250
|
+
if (this.inMemoryLockedResources.get(res) === operationId) {
|
|
251
|
+
this.inMemoryLockedResources.delete(res);
|
|
252
|
+
this.store.releaseResourceLease(operationId, res);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
for (const [res, owner] of Array.from(this.inMemoryLockedResources.entries())) {
|
|
258
|
+
if (owner === operationId) {
|
|
259
|
+
this.inMemoryLockedResources.delete(res);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
this.store.releaseResourceLease(operationId);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* 意图登记协议:先持久化意图与占用,再允许驱动调用
|
|
267
|
+
*/
|
|
268
|
+
registerOperationIntent(op) {
|
|
269
|
+
this.assertNotFenced();
|
|
270
|
+
// 1. 检查资源隔离
|
|
271
|
+
this.allocateResources(op.id, op.requiredResources, 0, op.resourceBudget);
|
|
272
|
+
// 2. 事务写入 SQLite
|
|
273
|
+
try {
|
|
274
|
+
this.store.registerOperationIntent(op, this.domainId);
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
// 写入失败回滚内存状态
|
|
278
|
+
this.releaseResources(op.id, op.requiredResources);
|
|
279
|
+
throw err;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
isClosed() {
|
|
283
|
+
return this.lockFd === null;
|
|
284
|
+
}
|
|
285
|
+
close() {
|
|
286
|
+
if (this.heartbeatInterval) {
|
|
287
|
+
clearInterval(this.heartbeatInterval);
|
|
288
|
+
this.heartbeatInterval = null;
|
|
289
|
+
}
|
|
290
|
+
if (this.store && this.ownerRecord) {
|
|
291
|
+
try {
|
|
292
|
+
this.store.releaseOwnerLease(this.domainId, this.ownerRecord.ownerId);
|
|
293
|
+
}
|
|
294
|
+
catch { }
|
|
295
|
+
}
|
|
296
|
+
if (this.store) {
|
|
297
|
+
try {
|
|
298
|
+
this.store.close();
|
|
299
|
+
}
|
|
300
|
+
catch { }
|
|
301
|
+
}
|
|
302
|
+
if (this.lockFd !== null) {
|
|
303
|
+
try {
|
|
304
|
+
fs.closeSync(this.lockFd);
|
|
305
|
+
}
|
|
306
|
+
catch { }
|
|
307
|
+
this.lockFd = null;
|
|
308
|
+
}
|
|
309
|
+
if (fs.existsSync(this.lockFilePath)) {
|
|
310
|
+
try {
|
|
311
|
+
// 仅在自己持有时移除
|
|
312
|
+
const content = fs.readFileSync(this.lockFilePath, 'utf8');
|
|
313
|
+
const meta = JSON.parse(content);
|
|
314
|
+
if (meta.ownerPid === process.pid) {
|
|
315
|
+
fs.unlinkSync(this.lockFilePath);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
catch { }
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=domain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain.js","sourceRoot":"","sources":["../src/domain.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { PlatformDriver, PlatformCapabilities, StructuredCommand, ManagedProcessHandle, ProcessIdentity, StopProcessResult } from './types.js';
|
|
2
|
+
import { IdentityVerificationResult } from '../types.js';
|
|
3
|
+
export declare class NodePlatformDriver implements PlatformDriver {
|
|
4
|
+
readonly name = "node-default";
|
|
5
|
+
readonly capabilities: PlatformCapabilities;
|
|
6
|
+
private activeHandles;
|
|
7
|
+
private cumulativeDescendantsMap;
|
|
8
|
+
private descendantPollers;
|
|
9
|
+
spawn(command: StructuredCommand): Promise<ManagedProcessHandle>;
|
|
10
|
+
verifyIdentity(identity: ProcessIdentity): Promise<IdentityVerificationResult>;
|
|
11
|
+
terminate(identity: ProcessIdentity, graceMs?: number): Promise<StopProcessResult>;
|
|
12
|
+
sampleMetrics(identity: ProcessIdentity): Promise<{
|
|
13
|
+
rssBytes: number;
|
|
14
|
+
pidsCount: number;
|
|
15
|
+
cpuTimeMs: number;
|
|
16
|
+
}>;
|
|
17
|
+
getProcessTree(leaderPid: number): {
|
|
18
|
+
allDescendants: number[];
|
|
19
|
+
inGroupDescendants: number[];
|
|
20
|
+
escapedDescendants: number[];
|
|
21
|
+
};
|
|
22
|
+
private getAllProcesses;
|
|
23
|
+
private isPidAlive;
|
|
24
|
+
private isGroupAlive;
|
|
25
|
+
private sendSignalToGroup;
|
|
26
|
+
private waitForGroupAndPids;
|
|
27
|
+
private parseCpuTimeToMs;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=node-driver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-driver.d.ts","sourceRoot":"","sources":["../../src/driver/node-driver.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAEzD,qBAAa,kBAAmB,YAAW,cAAc;IACvD,SAAgB,IAAI,kBAAkB;IACtC,SAAgB,YAAY,EAAE,oBAAoB,CAUhD;IAGF,OAAO,CAAC,aAAa,CACT;IAEZ,OAAO,CAAC,wBAAwB,CAAuC;IAEvE,OAAO,CAAC,iBAAiB,CAA0C;IAEtD,KAAK,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA2F5E;IAEY,cAAc,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAmC1F;IAEY,SAAS,CACpB,QAAQ,EAAE,eAAe,EACzB,OAAO,GAAE,MAAa,GACrB,OAAO,CAAC,iBAAiB,CAAC,CA8D5B;IAEY,aAAa,CACxB,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAwCrE;IAEM,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG;QACxC,cAAc,EAAE,MAAM,EAAE,CAAC;QACzB,kBAAkB,EAAE,MAAM,EAAE,CAAC;QAC7B,kBAAkB,EAAE,MAAM,EAAE,CAAC;KAC9B,CA2EA;IAED,OAAO,CAAC,eAAe;IA2BvB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,iBAAiB;YAUX,mBAAmB;IAuBjC,OAAO,CAAC,gBAAgB;CAgCzB"}
|