paseo-agy-acp 2.2.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 +296 -0
- package/LICENSE +201 -0
- package/README.md +571 -0
- package/README.zh-CN.md +540 -0
- package/assets/official-kernel-compat/rc01/paseo_model_compat.py +172 -0
- package/dist/ACP Connector/acp/session/paseo-context.d.ts +6 -0
- package/dist/ACP Connector/acp/session/paseo-context.js +83 -0
- package/dist/ACP Connector/acp/session/paseo-context.js.map +1 -0
- package/dist/ACP Connector/acp/session/turn-scheduler.d.ts +90 -0
- package/dist/ACP Connector/acp/session/turn-scheduler.js +247 -0
- package/dist/ACP Connector/acp/session/turn-scheduler.js.map +1 -0
- package/dist/ACP Connector/admission/dispatch-boundary.d.ts +19 -0
- package/dist/ACP Connector/admission/dispatch-boundary.js +16 -0
- package/dist/ACP Connector/admission/dispatch-boundary.js.map +1 -0
- package/dist/ACP Connector/admission/errors.d.ts +18 -0
- package/dist/ACP Connector/admission/errors.js +46 -0
- package/dist/ACP Connector/admission/errors.js.map +1 -0
- package/dist/ACP Connector/admission/owner-instance.d.ts +31 -0
- package/dist/ACP Connector/admission/owner-instance.js +105 -0
- package/dist/ACP Connector/admission/owner-instance.js.map +1 -0
- package/dist/ACP Connector/admission/runtime.d.ts +20 -0
- package/dist/ACP Connector/admission/runtime.js +157 -0
- package/dist/ACP Connector/admission/runtime.js.map +1 -0
- package/dist/ACP Connector/admission/startup-recovery.d.ts +22 -0
- package/dist/ACP Connector/admission/startup-recovery.js +129 -0
- package/dist/ACP Connector/admission/startup-recovery.js.map +1 -0
- package/dist/ACP Connector/admission/turn-coordinator.d.ts +57 -0
- package/dist/ACP Connector/admission/turn-coordinator.js +369 -0
- package/dist/ACP Connector/admission/turn-coordinator.js.map +1 -0
- package/dist/ACP Connector/agent.d.ts +4 -0
- package/dist/ACP Connector/agent.js +6 -0
- package/dist/ACP Connector/agent.js.map +1 -0
- package/dist/ACP Connector/main.d.ts +2 -0
- package/dist/ACP Connector/main.js +31 -0
- package/dist/ACP Connector/main.js.map +1 -0
- package/dist/ACP Connector/official-kernel/admission-fence.d.ts +10 -0
- package/dist/ACP Connector/official-kernel/admission-fence.js +52 -0
- package/dist/ACP Connector/official-kernel/admission-fence.js.map +1 -0
- package/dist/ACP Connector/official-kernel/context.d.ts +3 -0
- package/dist/ACP Connector/official-kernel/context.js +52 -0
- package/dist/ACP Connector/official-kernel/context.js.map +1 -0
- package/dist/ACP Connector/official-kernel/errors.d.ts +5 -0
- package/dist/ACP Connector/official-kernel/errors.js +39 -0
- package/dist/ACP Connector/official-kernel/errors.js.map +1 -0
- package/dist/ACP Connector/official-kernel/identity.d.ts +3 -0
- package/dist/ACP Connector/official-kernel/identity.js +17 -0
- package/dist/ACP Connector/official-kernel/identity.js.map +1 -0
- package/dist/ACP Connector/official-kernel/json-rpc.d.ts +34 -0
- package/dist/ACP Connector/official-kernel/json-rpc.js +26 -0
- package/dist/ACP Connector/official-kernel/json-rpc.js.map +1 -0
- package/dist/ACP Connector/official-kernel/kernel-compat-lifecycle.d.ts +115 -0
- package/dist/ACP Connector/official-kernel/kernel-compat-lifecycle.js +1904 -0
- package/dist/ACP Connector/official-kernel/kernel-compat-lifecycle.js.map +1 -0
- package/dist/ACP Connector/official-kernel/kernel-compat-pins.d.ts +14 -0
- package/dist/ACP Connector/official-kernel/kernel-compat-pins.js +29 -0
- package/dist/ACP Connector/official-kernel/kernel-compat-pins.js.map +1 -0
- package/dist/ACP Connector/official-kernel/kernel-compat-rc01-recipe.d.ts +2 -0
- package/dist/ACP Connector/official-kernel/kernel-compat-rc01-recipe.js +32 -0
- package/dist/ACP Connector/official-kernel/kernel-compat-rc01-recipe.js.map +1 -0
- package/dist/ACP Connector/official-kernel/kernel.d.ts +3 -0
- package/dist/ACP Connector/official-kernel/kernel.js +12 -0
- package/dist/ACP Connector/official-kernel/kernel.js.map +1 -0
- package/dist/ACP Connector/official-kernel/login.d.ts +1 -0
- package/dist/ACP Connector/official-kernel/login.js +73 -0
- package/dist/ACP Connector/official-kernel/login.js.map +1 -0
- package/dist/ACP Connector/official-kernel/mcp-rewrite.d.ts +1 -0
- package/dist/ACP Connector/official-kernel/mcp-rewrite.js +39 -0
- package/dist/ACP Connector/official-kernel/mcp-rewrite.js.map +1 -0
- package/dist/ACP Connector/official-kernel/mode-map.d.ts +5 -0
- package/dist/ACP Connector/official-kernel/mode-map.js +35 -0
- package/dist/ACP Connector/official-kernel/mode-map.js.map +1 -0
- package/dist/ACP Connector/official-kernel/ndjson.d.ts +3 -0
- package/dist/ACP Connector/official-kernel/ndjson.js +29 -0
- package/dist/ACP Connector/official-kernel/ndjson.js.map +1 -0
- package/dist/ACP Connector/official-kernel/proxy.d.ts +14 -0
- package/dist/ACP Connector/official-kernel/proxy.js +197 -0
- package/dist/ACP Connector/official-kernel/proxy.js.map +1 -0
- package/dist/ACP Connector/official-kernel/run.d.ts +8 -0
- package/dist/ACP Connector/official-kernel/run.js +22 -0
- package/dist/ACP Connector/official-kernel/run.js.map +1 -0
- package/dist/ACP Connector/official-kernel/spawn.d.ts +5 -0
- package/dist/ACP Connector/official-kernel/spawn.js +33 -0
- package/dist/ACP Connector/official-kernel/spawn.js.map +1 -0
- package/dist/Admission Controller/controller.d.ts +338 -0
- package/dist/Admission Controller/controller.js +2089 -0
- package/dist/Admission Controller/controller.js.map +1 -0
- package/dist/Admission Controller/key-derivation.d.ts +24 -0
- package/dist/Admission Controller/key-derivation.js +141 -0
- package/dist/Admission Controller/key-derivation.js.map +1 -0
- package/dist/Admission Controller/key-store.d.ts +8 -0
- package/dist/Admission Controller/key-store.js +280 -0
- package/dist/Admission Controller/key-store.js.map +1 -0
- package/dist/Admission Controller/policy-limits.d.ts +6 -0
- package/dist/Admission Controller/policy-limits.js +11 -0
- package/dist/Admission Controller/policy-limits.js.map +1 -0
- package/dist/Admission Controller/process-evidence.d.ts +111 -0
- package/dist/Admission Controller/process-evidence.js +549 -0
- package/dist/Admission Controller/process-evidence.js.map +1 -0
- package/dist/Admission Controller/runtime-config.d.ts +23 -0
- package/dist/Admission Controller/runtime-config.js +86 -0
- package/dist/Admission Controller/runtime-config.js.map +1 -0
- package/dist/Admission Controller/schema.d.ts +12 -0
- package/dist/Admission Controller/schema.js +422 -0
- package/dist/Admission Controller/schema.js.map +1 -0
- package/package.json +72 -0
- package/scripts/prepare-admission-state-dir.mjs +62 -0
- package/scripts/prepare-official-kernel-compat.mjs +387 -0
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "paseo-agy-acp",
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "Paseo product ACP adapter that proxies the official Google Antigravity ACP kernel (NDJSON) with Admission and daemon context",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "tiezbro"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/tiezbro/paseo-agy-acp#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/tiezbro/paseo-agy-acp/issues"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/tiezbro/paseo-agy-acp.git"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"acp",
|
|
19
|
+
"agent",
|
|
20
|
+
"antigravity",
|
|
21
|
+
"google",
|
|
22
|
+
"paseo"
|
|
23
|
+
],
|
|
24
|
+
"main": "dist/ACP Connector/agent.js",
|
|
25
|
+
"types": "dist/ACP Connector/agent.d.ts",
|
|
26
|
+
"license": "Apache-2.0",
|
|
27
|
+
"bin": {
|
|
28
|
+
"paseo-agy-acp": "dist/ACP Connector/main.js",
|
|
29
|
+
"agy-acp": "dist/ACP Connector/main.js",
|
|
30
|
+
"agy-acp-prepare-state": "scripts/prepare-admission-state-dir.mjs",
|
|
31
|
+
"agy-acp-prepare-official-kernel-compat": "scripts/prepare-official-kernel-compat.mjs"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"scripts/prepare-admission-state-dir.mjs",
|
|
39
|
+
"scripts/prepare-official-kernel-compat.mjs",
|
|
40
|
+
"assets/official-kernel-compat/rc01/paseo_model_compat.py",
|
|
41
|
+
"README.md",
|
|
42
|
+
"README.zh-CN.md",
|
|
43
|
+
"CHANGELOG.md",
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"package.json"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "node scripts/clean-dist.mjs && tsc",
|
|
49
|
+
"typecheck": "tsc -p tsconfig.test.json",
|
|
50
|
+
"test": "npm run typecheck && npm run build && vitest run",
|
|
51
|
+
"validate:architecture": "node scripts/verify-import-boundaries.mjs",
|
|
52
|
+
"validate:secrets": "node scripts/verify-no-secrets.mjs",
|
|
53
|
+
"validate": "npm test && npm run validate:architecture && npm run validate:secrets",
|
|
54
|
+
"test:watch": "vitest",
|
|
55
|
+
"prepublishOnly": "npm run build"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"better-sqlite3": "^13.0.1"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
62
|
+
"@types/node": "^26.1.1",
|
|
63
|
+
"typescript": "^7.0.2",
|
|
64
|
+
"vitest": "^4.1.10"
|
|
65
|
+
},
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=22"
|
|
68
|
+
},
|
|
69
|
+
"allowScripts": {
|
|
70
|
+
"better-sqlite3@13.0.1": true
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { closeSync, constants, fstatSync, mkdirSync, openSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
function fail(message) {
|
|
7
|
+
process.stderr.write(`agy-acp admission state preflight failed: ${message}\n`);
|
|
8
|
+
process.exitCode = 1;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function modeText(mode) {
|
|
12
|
+
return (mode & 0o777).toString(8).padStart(4, "0");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function prepareAdmissionStateDirectory(input) {
|
|
16
|
+
if (typeof input !== "string" || input.length === 0) {
|
|
17
|
+
throw new Error("provide an absolute directory path or set AGY_ACP_STATE_DIR");
|
|
18
|
+
}
|
|
19
|
+
if (!path.isAbsolute(input)) {
|
|
20
|
+
throw new Error("AGY_ACP_STATE_DIR must be an absolute path");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
mkdirSync(input, { recursive: true, mode: 0o700 });
|
|
24
|
+
const flags = constants.O_RDONLY |
|
|
25
|
+
(constants.O_CLOEXEC ?? 0) |
|
|
26
|
+
(constants.O_DIRECTORY ?? 0) |
|
|
27
|
+
(constants.O_NOFOLLOW ?? 0);
|
|
28
|
+
|
|
29
|
+
let directory;
|
|
30
|
+
try {
|
|
31
|
+
directory = openSync(input, flags);
|
|
32
|
+
} catch {
|
|
33
|
+
throw new Error("AGY_ACP_STATE_DIR must be a real directory and not a symbolic link");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const stat = fstatSync(directory);
|
|
38
|
+
if (!stat.isDirectory()) {
|
|
39
|
+
throw new Error("AGY_ACP_STATE_DIR must be a real directory");
|
|
40
|
+
}
|
|
41
|
+
const currentUid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
42
|
+
if (currentUid !== undefined && stat.uid !== currentUid) {
|
|
43
|
+
throw new Error("AGY_ACP_STATE_DIR must be owned by the current user");
|
|
44
|
+
}
|
|
45
|
+
if ((stat.mode & 0o777) !== 0o700) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`AGY_ACP_STATE_DIR mode is ${modeText(stat.mode)}; expected 0700. ` +
|
|
48
|
+
`Run chmod 700 -- ${JSON.stringify(input)} and rerun the preflight`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
} finally {
|
|
52
|
+
closeSync(directory);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const stateDir = process.argv[2] ?? process.env.AGY_ACP_STATE_DIR;
|
|
57
|
+
try {
|
|
58
|
+
prepareAdmissionStateDirectory(stateDir);
|
|
59
|
+
process.stdout.write(`admission state directory ready: ${JSON.stringify(stateDir)} mode=0700\n`);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
62
|
+
}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
chmodSync,
|
|
5
|
+
closeSync,
|
|
6
|
+
constants,
|
|
7
|
+
fchmodSync,
|
|
8
|
+
fstatSync,
|
|
9
|
+
fsyncSync,
|
|
10
|
+
lstatSync,
|
|
11
|
+
mkdtempSync,
|
|
12
|
+
openSync,
|
|
13
|
+
readSync,
|
|
14
|
+
rmSync,
|
|
15
|
+
writeSync
|
|
16
|
+
} from "node:fs";
|
|
17
|
+
import os from "node:os";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { pathToFileURL } from "node:url";
|
|
20
|
+
|
|
21
|
+
const repositoryRoot = path.resolve(import.meta.dirname, "..");
|
|
22
|
+
const defaultStateRoot = path.join(os.homedir(), ".local", "opt", "paseo-agy-acp-kernel-compat");
|
|
23
|
+
const defaultOfficialKernelRoot = path.join(
|
|
24
|
+
os.homedir(),
|
|
25
|
+
".local",
|
|
26
|
+
"opt",
|
|
27
|
+
"agy-acp-server-agy_acp_server_20260818_01_RC01"
|
|
28
|
+
);
|
|
29
|
+
const defaultParPath = path.join(defaultOfficialKernelRoot, "agy_acp_server.par");
|
|
30
|
+
const defaultExternalHarnessPath = path.join(defaultOfficialKernelRoot, "localharness_external");
|
|
31
|
+
const defaultCompatModulePath = path.join(
|
|
32
|
+
repositoryRoot,
|
|
33
|
+
"assets",
|
|
34
|
+
"official-kernel-compat",
|
|
35
|
+
"rc01",
|
|
36
|
+
"paseo_model_compat.py"
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const MAX_COMPAT_MODULE_BYTES = 1_000_000;
|
|
40
|
+
const MAX_PATCH_PLAN_BYTES = 64 * 1024;
|
|
41
|
+
const CLOEXEC = constants.O_CLOEXEC ?? 0;
|
|
42
|
+
|
|
43
|
+
function fail(message) {
|
|
44
|
+
process.stderr.write(`agy-acp kernel compatibility lifecycle failed: ${message}\n`);
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function printUsage() {
|
|
49
|
+
process.stdout.write(
|
|
50
|
+
"usage: agy-acp-prepare-official-kernel-compat <prepare|verify|activate|rollback|status|cleanup> [options]\n"
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parseArguments(argv) {
|
|
55
|
+
const [command, ...rest] = argv;
|
|
56
|
+
if (command === undefined || command === "--help" || command === "-h") return { help: true };
|
|
57
|
+
const values = new Map();
|
|
58
|
+
const booleans = new Set();
|
|
59
|
+
const seenOptions = new Set();
|
|
60
|
+
const valueOptions = new Set([
|
|
61
|
+
"state-root",
|
|
62
|
+
"par",
|
|
63
|
+
"external-harness",
|
|
64
|
+
"compat-module",
|
|
65
|
+
"patch-plan",
|
|
66
|
+
"compatibility-version",
|
|
67
|
+
"compat-destination",
|
|
68
|
+
"importer-env",
|
|
69
|
+
"artifact-id"
|
|
70
|
+
]);
|
|
71
|
+
const booleanOptions = new Set(["activate", "remove-unreferenced"]);
|
|
72
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
73
|
+
const token = rest[index];
|
|
74
|
+
if (!token.startsWith("--")) throw new Error(`unexpected argument ${JSON.stringify(token)}`);
|
|
75
|
+
const name = token.slice(2);
|
|
76
|
+
if (!valueOptions.has(name) && !booleanOptions.has(name)) throw new Error(`unknown option --${name}`);
|
|
77
|
+
if (seenOptions.has(name)) throw new Error(`--${name} may be provided once`);
|
|
78
|
+
seenOptions.add(name);
|
|
79
|
+
if (booleanOptions.has(name)) {
|
|
80
|
+
booleans.add(name);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const value = rest[index + 1];
|
|
84
|
+
if (value === undefined || value.startsWith("--")) throw new Error(`--${name} requires a value`);
|
|
85
|
+
values.set(name, value);
|
|
86
|
+
index += 1;
|
|
87
|
+
}
|
|
88
|
+
return { command, values, booleans };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function required(values, name) {
|
|
92
|
+
const value = values.get(name);
|
|
93
|
+
if (value === undefined) throw new Error(`--${name} is required`);
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function absolute(value, label) {
|
|
98
|
+
if (typeof value !== "string" || value.length === 0 || !path.isAbsolute(value)) {
|
|
99
|
+
throw new Error(`${label} must be an absolute path`);
|
|
100
|
+
}
|
|
101
|
+
return path.resolve(value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseImporterEnvironment(value) {
|
|
105
|
+
if (value === undefined) return undefined;
|
|
106
|
+
const result = {};
|
|
107
|
+
for (const entry of value.split(",")) {
|
|
108
|
+
const separator = entry.indexOf("=");
|
|
109
|
+
if (separator <= 0 || separator === entry.length - 1) {
|
|
110
|
+
throw new Error("--importer-env entries must use NAME=relative/path");
|
|
111
|
+
}
|
|
112
|
+
result[entry.slice(0, separator)] = entry.slice(separator + 1);
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function prepareOptions(values, builtInPatchPlan) {
|
|
118
|
+
const layout = {
|
|
119
|
+
...(values.has("compat-destination") ? { compatModuleRelativePath: values.get("compat-destination") } : {}),
|
|
120
|
+
...(values.has("importer-env") ? { importerEnvironment: parseImporterEnvironment(values.get("importer-env")) } : {})
|
|
121
|
+
};
|
|
122
|
+
const selectedCompatModule = values.has("compat-module")
|
|
123
|
+
? { path: absolute(values.get("compat-module"), "compatibility module path"), builtIn: false }
|
|
124
|
+
: { path: defaultCompatModulePath, builtIn: true };
|
|
125
|
+
const stagedCompatModule = materializeCompatModule(selectedCompatModule.path, selectedCompatModule.builtIn);
|
|
126
|
+
try {
|
|
127
|
+
return {
|
|
128
|
+
options: {
|
|
129
|
+
parPath: absolute(values.get("par") ?? defaultParPath, "PAR path"),
|
|
130
|
+
externalHarnessPath: absolute(values.get("external-harness") ?? defaultExternalHarnessPath, "external harness path"),
|
|
131
|
+
compatModulePath: stagedCompatModule.path,
|
|
132
|
+
patchPlan: values.has("patch-plan") ? readPatchPlan(values.get("patch-plan")) : builtInPatchPlan,
|
|
133
|
+
...(values.has("compatibility-version") ? { compatibilityVersion: values.get("compatibility-version") } : {}),
|
|
134
|
+
...(Object.keys(layout).length > 0 ? { layout } : {})
|
|
135
|
+
},
|
|
136
|
+
cleanup: stagedCompatModule.cleanup
|
|
137
|
+
};
|
|
138
|
+
} catch (error) {
|
|
139
|
+
stagedCompatModule.cleanup();
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function readPatchPlan(filename) {
|
|
145
|
+
const patchPlanPath = absolute(filename, "patch plan path");
|
|
146
|
+
const text = readStableTextFile(patchPlanPath, "patch plan", MAX_PATCH_PLAN_BYTES, false);
|
|
147
|
+
try {
|
|
148
|
+
return JSON.parse(text);
|
|
149
|
+
} catch {
|
|
150
|
+
throw new Error("patch plan must be a readable local JSON file");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function materializeCompatModule(sourcePath, builtIn) {
|
|
155
|
+
const label = builtIn ? "built-in compatibility module" : "compatibility module override";
|
|
156
|
+
const source = openStableRegularFile(sourcePath, label, MAX_COMPAT_MODULE_BYTES, builtIn);
|
|
157
|
+
let directory;
|
|
158
|
+
let destination;
|
|
159
|
+
try {
|
|
160
|
+
directory = mkdtempSync(path.join(os.tmpdir(), "paseo-agy-acp-compat-"));
|
|
161
|
+
chmodSync(directory, 0o700);
|
|
162
|
+
const directoryStat = lstatSync(directory);
|
|
163
|
+
if (directoryStat.isSymbolicLink() || !directoryStat.isDirectory() || (directoryStat.mode & 0o777) !== 0o700) {
|
|
164
|
+
throw new Error("compatibility module staging directory must have mode 0700");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const destinationPath = path.join(directory, "paseo_model_compat.py");
|
|
168
|
+
destination = openPrivateOutput(destinationPath, 0o400);
|
|
169
|
+
copyStableFile(source, destination, label);
|
|
170
|
+
fchmodSync(destination, 0o400);
|
|
171
|
+
fsyncSync(destination);
|
|
172
|
+
const destinationStat = fstatSync(destination);
|
|
173
|
+
if (!destinationStat.isFile() || (destinationStat.mode & 0o777) !== 0o400) {
|
|
174
|
+
throw new Error("compatibility module staging file must have mode 0400");
|
|
175
|
+
}
|
|
176
|
+
closeSync(destination);
|
|
177
|
+
destination = undefined;
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
path: destinationPath,
|
|
181
|
+
cleanup: () => rmSync(directory, { force: true, recursive: true })
|
|
182
|
+
};
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (destination !== undefined) closeSync(destination);
|
|
185
|
+
if (directory !== undefined) rmSync(directory, { force: true, recursive: true });
|
|
186
|
+
throw error;
|
|
187
|
+
} finally {
|
|
188
|
+
closeSync(source.fd);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function readStableTextFile(filename, label, maximumBytes, builtIn) {
|
|
193
|
+
const source = openStableRegularFile(filename, label, maximumBytes, builtIn);
|
|
194
|
+
try {
|
|
195
|
+
const content = readStableFile(source, label);
|
|
196
|
+
return content.toString("utf8");
|
|
197
|
+
} finally {
|
|
198
|
+
closeSync(source.fd);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function openStableRegularFile(filename, label, maximumBytes, builtIn) {
|
|
203
|
+
assertExistingPathHasNoSymlinkComponents(filename, label);
|
|
204
|
+
const before = lstatSync(filename);
|
|
205
|
+
if (before.isSymbolicLink() || !before.isFile()) {
|
|
206
|
+
throw new Error(`${label} must be a regular file and not a symbolic link`);
|
|
207
|
+
}
|
|
208
|
+
if (!Number.isSafeInteger(before.size) || before.size < 0 || before.size > maximumBytes) {
|
|
209
|
+
throw new Error(`${label} exceeds the ${maximumBytes}-byte limit`);
|
|
210
|
+
}
|
|
211
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
212
|
+
if (!builtIn && (uid === undefined || before.uid !== uid)) {
|
|
213
|
+
throw new Error(`${label} must be owned by the current user`);
|
|
214
|
+
}
|
|
215
|
+
if ((before.mode & 0o002) !== 0 || (!builtIn && (before.mode & 0o020) !== 0)) {
|
|
216
|
+
throw new Error(`${label} must not be group- or world-writable`);
|
|
217
|
+
}
|
|
218
|
+
if (builtIn && uid !== undefined && before.uid !== uid && (before.mode & 0o022) !== 0) {
|
|
219
|
+
throw new Error(`${label} from another owner must not be group- or world-writable`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const fd = openReadOnlyNoFollow(filename);
|
|
223
|
+
try {
|
|
224
|
+
const opened = fstatSync(fd);
|
|
225
|
+
if (!opened.isFile() || !sameFileSnapshot(opened, before)) {
|
|
226
|
+
throw new Error(`${label} changed while it was being opened`);
|
|
227
|
+
}
|
|
228
|
+
return { fd, before: opened };
|
|
229
|
+
} catch (error) {
|
|
230
|
+
closeSync(fd);
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function assertExistingPathHasNoSymlinkComponents(candidate, label) {
|
|
236
|
+
const resolved = path.resolve(candidate);
|
|
237
|
+
const parsed = path.parse(resolved);
|
|
238
|
+
let cursor = parsed.root;
|
|
239
|
+
for (const part of resolved.slice(parsed.root.length).split(path.sep).filter(Boolean)) {
|
|
240
|
+
cursor = path.join(cursor, part);
|
|
241
|
+
let stat;
|
|
242
|
+
try {
|
|
243
|
+
stat = lstatSync(cursor);
|
|
244
|
+
} catch {
|
|
245
|
+
throw new Error(`${label} must be a readable local file`);
|
|
246
|
+
}
|
|
247
|
+
if (stat.isSymbolicLink()) throw new Error(`${label} must not traverse a symbolic link`);
|
|
248
|
+
if (cursor !== resolved && !stat.isDirectory()) throw new Error(`${label} has a non-directory parent`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function openReadOnlyNoFollow(filename) {
|
|
253
|
+
const noFollow = constants.O_NOFOLLOW;
|
|
254
|
+
if (noFollow === undefined) throw new Error("kernel compatibility CLI requires O_NOFOLLOW support");
|
|
255
|
+
return openSync(filename, constants.O_RDONLY | noFollow | CLOEXEC);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function openPrivateOutput(filename, mode) {
|
|
259
|
+
const noFollow = constants.O_NOFOLLOW;
|
|
260
|
+
if (noFollow === undefined) throw new Error("kernel compatibility CLI requires O_NOFOLLOW support");
|
|
261
|
+
return openSync(filename, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollow | CLOEXEC, mode);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function readStableFile(source, label) {
|
|
265
|
+
const output = Buffer.alloc(source.before.size);
|
|
266
|
+
let offset = 0;
|
|
267
|
+
while (offset < output.length) {
|
|
268
|
+
const count = readSync(source.fd, output, offset, output.length - offset, offset);
|
|
269
|
+
if (count === 0) throw new Error(`${label} changed while it was being read`);
|
|
270
|
+
offset += count;
|
|
271
|
+
}
|
|
272
|
+
assertStableFile(source, label);
|
|
273
|
+
return output;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function copyStableFile(source, destination, label) {
|
|
277
|
+
const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, Math.max(source.before.size, 1)));
|
|
278
|
+
let position = 0;
|
|
279
|
+
while (position < source.before.size) {
|
|
280
|
+
const count = readSync(source.fd, buffer, 0, Math.min(buffer.length, source.before.size - position), position);
|
|
281
|
+
if (count === 0) throw new Error(`${label} changed while it was being copied`);
|
|
282
|
+
writeAll(destination, buffer.subarray(0, count));
|
|
283
|
+
position += count;
|
|
284
|
+
}
|
|
285
|
+
assertStableFile(source, label);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function assertStableFile(source, label) {
|
|
289
|
+
if (!sameFileSnapshot(fstatSync(source.fd), source.before)) {
|
|
290
|
+
throw new Error(`${label} changed while it was being read`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function sameFileSnapshot(left, right) {
|
|
295
|
+
return left.dev === right.dev && left.ino === right.ino && left.uid === right.uid &&
|
|
296
|
+
left.size === right.size && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function writeAll(fd, data) {
|
|
300
|
+
let offset = 0;
|
|
301
|
+
while (offset < data.length) {
|
|
302
|
+
const written = writeSync(fd, data, offset, data.length - offset);
|
|
303
|
+
if (written <= 0) throw new Error("could not materialize compatibility module");
|
|
304
|
+
offset += written;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function loadLifecycleModule() {
|
|
309
|
+
const modulePath = path.join(
|
|
310
|
+
repositoryRoot,
|
|
311
|
+
"dist",
|
|
312
|
+
"ACP Connector",
|
|
313
|
+
"official-kernel",
|
|
314
|
+
"kernel-compat-lifecycle.js"
|
|
315
|
+
);
|
|
316
|
+
try {
|
|
317
|
+
lstatSync(modulePath);
|
|
318
|
+
} catch {
|
|
319
|
+
throw new Error("built lifecycle module is missing; run npm run build before invoking this command");
|
|
320
|
+
}
|
|
321
|
+
return import(pathToFileURL(modulePath).href);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function loadBuiltInPatchPlan() {
|
|
325
|
+
const modulePath = path.join(
|
|
326
|
+
repositoryRoot,
|
|
327
|
+
"dist",
|
|
328
|
+
"ACP Connector",
|
|
329
|
+
"official-kernel",
|
|
330
|
+
"kernel-compat-rc01-recipe.js"
|
|
331
|
+
);
|
|
332
|
+
try {
|
|
333
|
+
lstatSync(modulePath);
|
|
334
|
+
} catch {
|
|
335
|
+
throw new Error("built RC01 patch recipe is missing; run npm run build before invoking prepare");
|
|
336
|
+
}
|
|
337
|
+
const module = await import(pathToFileURL(modulePath).href);
|
|
338
|
+
if (module.RC01_KERNEL_COMPAT_PATCH_PLAN === undefined) {
|
|
339
|
+
throw new Error("built RC01 patch recipe is invalid");
|
|
340
|
+
}
|
|
341
|
+
return module.RC01_KERNEL_COMPAT_PATCH_PLAN;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function main() {
|
|
345
|
+
const parsed = parseArguments(process.argv.slice(2));
|
|
346
|
+
if (parsed.help) {
|
|
347
|
+
printUsage();
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const { command, values, booleans } = parsed;
|
|
351
|
+
if (!new Set(["prepare", "verify", "activate", "rollback", "status", "cleanup"]).has(command)) {
|
|
352
|
+
throw new Error(`unknown lifecycle command ${JSON.stringify(command)}`);
|
|
353
|
+
}
|
|
354
|
+
if (booleans.has("activate") && command !== "prepare") throw new Error("--activate is valid only with prepare");
|
|
355
|
+
if (booleans.has("remove-unreferenced") && command !== "cleanup") {
|
|
356
|
+
throw new Error("--remove-unreferenced is valid only with cleanup");
|
|
357
|
+
}
|
|
358
|
+
const { OfficialKernelCompatLifecycle } = await loadLifecycleModule();
|
|
359
|
+
const stateRoot = absolute(values.get("state-root") ?? defaultStateRoot, "state root");
|
|
360
|
+
const lifecycle = new OfficialKernelCompatLifecycle({ stateRoot });
|
|
361
|
+
|
|
362
|
+
let result;
|
|
363
|
+
if (command === "prepare") {
|
|
364
|
+
const preparedOptions = prepareOptions(values, await loadBuiltInPatchPlan());
|
|
365
|
+
try {
|
|
366
|
+
const prepared = await lifecycle.prepare(preparedOptions.options);
|
|
367
|
+
result = booleans.has("activate")
|
|
368
|
+
? { prepared, activation: await lifecycle.activate(prepared.artifactId) }
|
|
369
|
+
: { prepared };
|
|
370
|
+
} finally {
|
|
371
|
+
preparedOptions.cleanup();
|
|
372
|
+
}
|
|
373
|
+
} else if (command === "verify") {
|
|
374
|
+
result = await lifecycle.verify(values.get("artifact-id"));
|
|
375
|
+
} else if (command === "activate") {
|
|
376
|
+
result = await lifecycle.activate(required(values, "artifact-id"));
|
|
377
|
+
} else if (command === "rollback") {
|
|
378
|
+
result = await lifecycle.rollback();
|
|
379
|
+
} else if (command === "status") {
|
|
380
|
+
result = await lifecycle.status();
|
|
381
|
+
} else {
|
|
382
|
+
result = await lifecycle.cleanup({ removeUnreferenced: booleans.has("remove-unreferenced") });
|
|
383
|
+
}
|
|
384
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
main().catch((error) => fail(error instanceof Error ? error.message : "unknown local lifecycle error"));
|