apphx 1.1.0 → 2.0.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/README.md +50 -182
- package/benchmarks/peer-context-worker.mjs +186 -0
- package/benchmarks/peer-context.mjs +150 -0
- package/dist/apple-types.js.map +1 -1
- package/dist/cli.js +370 -118
- package/dist/cli.js.map +1 -1
- package/dist/config.js +42 -4
- package/dist/config.js.map +1 -1
- package/dist/dependencies.js +455 -16
- package/dist/dependencies.js.map +1 -1
- package/dist/export-options.js +44 -2
- package/dist/export-options.js.map +1 -1
- package/dist/flutter.js +13 -25
- package/dist/flutter.js.map +1 -1
- package/dist/go-dependencies.js +389 -0
- package/dist/go-dependencies.js.map +1 -0
- package/dist/inbox.js +7 -8
- package/dist/inbox.js.map +1 -1
- package/dist/integrations.js +3 -3
- package/dist/isolation.js +190 -0
- package/dist/isolation.js.map +1 -0
- package/dist/macos.js +57 -0
- package/dist/macos.js.map +1 -0
- package/dist/mygo-build.js +55 -0
- package/dist/mygo-build.js.map +1 -0
- package/dist/policy.js +35 -0
- package/dist/policy.js.map +1 -0
- package/dist/preflight.js +1 -1
- package/dist/process.js +3 -3
- package/dist/project-profiles.js +163 -0
- package/dist/project-profiles.js.map +1 -0
- package/dist/proof.js +33 -0
- package/dist/proof.js.map +1 -0
- package/dist/remote.js +1 -1
- package/dist/runner.js +269 -287
- package/dist/runner.js.map +1 -1
- package/dist/scheduler.js +0 -3
- package/dist/scheduler.js.map +1 -1
- package/dist/schemas.js +156 -44
- package/dist/schemas.js.map +1 -1
- package/dist/store.js +166 -44
- package/dist/store.js.map +1 -1
- package/dist/vendor/obfuscator/dist/cli.js +72 -0
- package/dist/vendor/obfuscator/dist/cli.js.map +1 -0
- package/dist/vendor/obfuscator/dist/code.js +585 -0
- package/dist/vendor/obfuscator/dist/code.js.map +1 -0
- package/dist/vendor/obfuscator/dist/common.js +262 -0
- package/dist/vendor/obfuscator/dist/common.js.map +1 -0
- package/dist/vendor/obfuscator/dist/config.js +123 -0
- package/dist/vendor/obfuscator/dist/config.js.map +1 -0
- package/dist/vendor/obfuscator/dist/content.js +89 -0
- package/dist/vendor/obfuscator/dist/content.js.map +1 -0
- package/dist/vendor/obfuscator/dist/obfuscate.js +93 -0
- package/dist/vendor/obfuscator/dist/obfuscate.js.map +1 -0
- package/dist/vendor/obfuscator/dist/png.js +283 -0
- package/dist/vendor/obfuscator/dist/png.js.map +1 -0
- package/dist/vendor/obfuscator/dist/project.js +159 -0
- package/dist/vendor/obfuscator/dist/project.js.map +1 -0
- package/dist/vendor/obfuscator/dist/resources.js +335 -0
- package/dist/vendor/obfuscator/dist/resources.js.map +1 -0
- package/dist/vendor/obfuscator/package.json +22 -0
- package/dist/workspace.js +113 -0
- package/dist/workspace.js.map +1 -0
- package/evals/control.mjs +73 -0
- package/evals/expectations.mjs +37 -0
- package/evals/host.mjs +51 -0
- package/evals/oracle.mjs +80 -0
- package/evals/policy.mjs +268 -0
- package/examples/android-java.factory.json +18 -0
- package/examples/android-java.json +16 -0
- package/examples/existing-project.factory.json +18 -0
- package/examples/existing-project.json +17 -0
- package/examples/expo.eas.json +17 -0
- package/examples/expo.factory.json +18 -0
- package/examples/expo.json +16 -0
- package/examples/factory.json +0 -2
- package/examples/flutter.factory.json +13 -0
- package/examples/flutter.json +16 -0
- package/examples/luggage.json +5 -3
- package/examples/mygo-web.factory.json +18 -0
- package/examples/mygo-web.json +16 -0
- package/examples/mygo.factory.json +18 -0
- package/examples/mygo.json +16 -0
- package/examples/react-native.factory.json +18 -0
- package/examples/react-native.json +16 -0
- package/examples/runtime/android-java/app/build.gradle +7 -0
- package/examples/runtime/android-java/app/src/main/AndroidManifest.xml +8 -0
- package/examples/runtime/android-java/app/src/main/java/dev/apphx/runtime/MainActivity.java +44 -0
- package/examples/runtime/android-java/sdk-build.mjs +29 -0
- package/examples/runtime/android-java/settings.gradle +2 -0
- package/examples/runtime/android-ui.mjs +70 -0
- package/examples/runtime/mygo-native/go.mod +13 -0
- package/examples/runtime/mygo-native/go.sum +10 -0
- package/examples/runtime/mygo-native/main.go +173 -0
- package/examples/runtime/mygo-native/main_test.go +14 -0
- package/examples/runtime/mygo-native/mygo.json +1 -0
- package/examples/runtime/mygo-web/bun.lock +15 -0
- package/examples/runtime/mygo-web/go.mod +9 -0
- package/examples/runtime/mygo-web/go.sum +8 -0
- package/examples/runtime/mygo-web/index.html +1 -0
- package/examples/runtime/mygo-web/main.go +60 -0
- package/examples/runtime/mygo-web/mygo.config.ts +10 -0
- package/examples/runtime/mygo-web/package.json +1 -0
- package/examples/runtime/mygo-web/src/main.ts +23 -0
- package/examples/runtime/mygo-web/src/mygo.ts +14 -0
- package/examples/swift.factory.json +18 -0
- package/examples/swift.json +16 -0
- package/examples/uni-app.factory.json +18 -0
- package/examples/uni-app.json +16 -0
- package/package.json +12 -5
- package/scripts/bundle-obfuscator.mjs +19 -0
- package/scripts/runtime-smoke.mjs +209 -0
- package/skills/apphx/SKILL.md +94 -17
- package/skills/apphx/resources/google-play-research-result.json +6 -0
- package/skills/apphx/resources/local-research-result.json +6 -0
- package/skills/apphx/resources/requirement.json +5 -8
- package/skills/apphx/resources/research-result.json +5 -14
- package/skills/apphx/resources/review-result.json +1 -1
- package/dist/apple.js +0 -900
- package/dist/apple.js.map +0 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { canonicalPath, normalizeIsolationPaths } from "./config.js";
|
|
2
|
+
import { accessSync, constants, existsSync, lstatSync, mkdirSync, mkdtempSync, realpathSync } from "node:fs";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
// src/isolation.ts
|
|
6
|
+
function failure(message) {
|
|
7
|
+
return Object.assign(new Error(message), { code: "SETUP_REQUIRED" });
|
|
8
|
+
}
|
|
9
|
+
function contains(parent, child) {
|
|
10
|
+
const path = relative(parent, child);
|
|
11
|
+
return path === "" || path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path);
|
|
12
|
+
}
|
|
13
|
+
function executablePath(command, cwd, env) {
|
|
14
|
+
const candidates = command.includes("/") ? [resolve(cwd, command)] : (env.PATH ?? "").split(delimiter).filter(Boolean).map((path) => resolve(cwd, path, command));
|
|
15
|
+
for (const candidate of candidates) try {
|
|
16
|
+
accessSync(candidate, constants.X_OK);
|
|
17
|
+
const path = realpathSync(candidate);
|
|
18
|
+
if (lstatSync(path).isFile()) return path;
|
|
19
|
+
} catch {}
|
|
20
|
+
throw failure(`Cannot resolve isolated executable ${command}; select an existing executable in the approved worker PATH`);
|
|
21
|
+
}
|
|
22
|
+
function pathRule(path) {
|
|
23
|
+
const info = lstatSync(path);
|
|
24
|
+
if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) throw failure(`Isolation grant requires a regular canonical file or directory: ${path}`);
|
|
25
|
+
return `(${info.isDirectory() ? "subpath" : "literal"} ${JSON.stringify(path)})`;
|
|
26
|
+
}
|
|
27
|
+
/** Keep deletion authority on the original trusted root, never a re-resolved target or its parent. */
|
|
28
|
+
function removeConfinedPath(path, root) {
|
|
29
|
+
if (process.platform !== "darwin") throw failure("Confined cleanup requires the selected macOS backend");
|
|
30
|
+
const boundary = resolve(root), target = resolve(path);
|
|
31
|
+
if (boundary === "/" || !contains(boundary, target)) throw failure("Cleanup target must stay within its original isolation root");
|
|
32
|
+
const ancestors = /* @__PURE__ */ new Set([
|
|
33
|
+
"/",
|
|
34
|
+
"/bin",
|
|
35
|
+
"/usr",
|
|
36
|
+
"/System"
|
|
37
|
+
]);
|
|
38
|
+
for (let parent = dirname(boundary); !ancestors.has(parent); parent = dirname(parent)) ancestors.add(parent);
|
|
39
|
+
const profile = [
|
|
40
|
+
"(version 1)",
|
|
41
|
+
"(deny default)",
|
|
42
|
+
"(allow process-exec)",
|
|
43
|
+
"(allow sysctl-read)",
|
|
44
|
+
"(allow file-read* (literal \"/\") (literal \"/bin/rm\") (subpath \"/System/Library\") (subpath \"/usr/lib\") (literal \"/dev/null\"))",
|
|
45
|
+
`(allow file-read-metadata ${[...ancestors].map((parent) => `(literal ${JSON.stringify(parent)})`).join(" ")})`,
|
|
46
|
+
`(allow file-read* file-write* (subpath ${JSON.stringify(boundary)}))`,
|
|
47
|
+
"(allow file-write* (literal \"/dev/null\"))"
|
|
48
|
+
].join("\n");
|
|
49
|
+
const result = spawnSync("/usr/bin/sandbox-exec", [
|
|
50
|
+
"-p",
|
|
51
|
+
profile,
|
|
52
|
+
"/bin/rm",
|
|
53
|
+
"-rf",
|
|
54
|
+
"--",
|
|
55
|
+
target
|
|
56
|
+
], {
|
|
57
|
+
cwd: "/",
|
|
58
|
+
env: {
|
|
59
|
+
PATH: "/usr/bin:/bin",
|
|
60
|
+
LANG: "C"
|
|
61
|
+
},
|
|
62
|
+
encoding: "utf8",
|
|
63
|
+
timeout: 1e4,
|
|
64
|
+
maxBuffer: 65536
|
|
65
|
+
});
|
|
66
|
+
if (result.error || result.signal || result.status !== 0) throw Object.assign(/* @__PURE__ */ new Error(`Confined cleanup did not complete; outside paths were not granted: ${result.error?.message ?? result.stderr ?? result.signal}`), { code: "PROCESS_CLEANUP_BLOCKED" });
|
|
67
|
+
}
|
|
68
|
+
/** This confines spawned commands, not the host agent, resource consumption or GUI/device services. */
|
|
69
|
+
function prepareIsolatedCommand(argv, context) {
|
|
70
|
+
if (!context.config.isolation) throw failure("No subprocess isolation mode was selected");
|
|
71
|
+
if (process.platform !== "darwin") throw failure("macos-sandbox isolation requires macOS; execution was not downgraded to trusted mode");
|
|
72
|
+
try {
|
|
73
|
+
accessSync("/usr/bin/sandbox-exec", constants.X_OK);
|
|
74
|
+
} catch {
|
|
75
|
+
throw failure("The selected macos-sandbox backend is unavailable; no command was started");
|
|
76
|
+
}
|
|
77
|
+
const config = {
|
|
78
|
+
...context.config,
|
|
79
|
+
isolation: {
|
|
80
|
+
...context.config.isolation,
|
|
81
|
+
readOnlyPaths: [...context.config.isolation.readOnlyPaths]
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
normalizeIsolationPaths(config);
|
|
85
|
+
for (let index = 0; index < config.isolation.readOnlyPaths.length; index++) if (config.isolation.readOnlyPaths[index] !== context.config.isolation.readOnlyPaths[index]) throw failure("An isolation read-only path changed identity; review the owner configuration before retrying");
|
|
86
|
+
const workspaceInfo = lstatSync(context.workspace);
|
|
87
|
+
if (!workspaceInfo.isDirectory() || workspaceInfo.isSymbolicLink()) throw failure("Isolation requires a real owned workspace directory");
|
|
88
|
+
const workspace = realpathSync(context.workspace);
|
|
89
|
+
const state = canonicalPath(config.stateDir), workspaceRoot = canonicalPath(config.workspaceRoot);
|
|
90
|
+
if (workspace === workspaceRoot || contains(workspace, state) || contains(state, workspace)) throw failure("Isolation workspace must not expose control state or the whole workspace root");
|
|
91
|
+
const cwd = realpathSync(context.cwd);
|
|
92
|
+
if (![workspace, ...config.isolation.readOnlyPaths].some((path) => contains(path, cwd))) throw failure("Isolated command cwd must stay in its workspace or an approved read-only root");
|
|
93
|
+
const executable = executablePath(argv[0] ?? "", cwd, context.env);
|
|
94
|
+
if (contains(state, executable) || contains(workspaceRoot, executable) && !contains(workspace, executable)) throw failure("An isolated command cannot execute control state or another task workspace");
|
|
95
|
+
const readable = [
|
|
96
|
+
...config.isolation.readOnlyPaths,
|
|
97
|
+
executable,
|
|
98
|
+
realpathSync(process.execPath)
|
|
99
|
+
];
|
|
100
|
+
for (const file of context.readableFiles ?? []) {
|
|
101
|
+
if (!lstatSync(file).isFile() || lstatSync(file).isSymbolicLink()) throw failure(`Harness isolation capability requires a regular file: ${file}`);
|
|
102
|
+
readable.push(realpathSync(file));
|
|
103
|
+
}
|
|
104
|
+
const extraWritable = (context.writableDirectories ?? []).map((path) => {
|
|
105
|
+
if (!lstatSync(path).isDirectory() || lstatSync(path).isSymbolicLink()) throw failure(`Harness isolation capability requires a real directory: ${path}`);
|
|
106
|
+
return realpathSync(path);
|
|
107
|
+
});
|
|
108
|
+
const scratchParent = context.scratchParent ?? state;
|
|
109
|
+
mkdirSync(scratchParent, {
|
|
110
|
+
recursive: true,
|
|
111
|
+
mode: 448
|
|
112
|
+
});
|
|
113
|
+
const scratch = realpathSync(mkdtempSync(join(scratchParent, "isolated-command-")));
|
|
114
|
+
const cleanup = () => removeConfinedPath(scratch, scratch);
|
|
115
|
+
try {
|
|
116
|
+
const home = join(scratch, "home"), temporary = join(scratch, "tmp"), cache = join(scratch, "cache");
|
|
117
|
+
for (const path of [
|
|
118
|
+
home,
|
|
119
|
+
temporary,
|
|
120
|
+
cache
|
|
121
|
+
]) mkdirSync(path, { mode: 448 });
|
|
122
|
+
const env = {
|
|
123
|
+
...context.env,
|
|
124
|
+
HOME: home,
|
|
125
|
+
TMPDIR: `${temporary}${sep}`,
|
|
126
|
+
TMP: temporary,
|
|
127
|
+
TEMP: temporary,
|
|
128
|
+
XDG_CACHE_HOME: cache,
|
|
129
|
+
GRADLE_USER_HOME: join(cache, "gradle"),
|
|
130
|
+
npm_config_cache: join(cache, "npm")
|
|
131
|
+
};
|
|
132
|
+
for (const [name, fallback] of [["GOCACHE", join(cache, "go-build")], ["GOMODCACHE", join(cache, "go-mod")]]) if (!env[name] || contains(state, canonicalPath(resolve(cwd, env[name])))) env[name] = fallback;
|
|
133
|
+
const systemPaths = ["/System/Library", "/usr/lib"].filter(existsSync);
|
|
134
|
+
const devices = [
|
|
135
|
+
"/dev/urandom",
|
|
136
|
+
"/dev/random",
|
|
137
|
+
"/dev/null"
|
|
138
|
+
].filter(existsSync);
|
|
139
|
+
const readablePaths = [.../* @__PURE__ */ new Set([...systemPaths, ...readable])];
|
|
140
|
+
const writablePaths = [.../* @__PURE__ */ new Set([
|
|
141
|
+
workspace,
|
|
142
|
+
scratch,
|
|
143
|
+
...extraWritable
|
|
144
|
+
])];
|
|
145
|
+
const ancestors = /* @__PURE__ */ new Set(["/"]);
|
|
146
|
+
for (const path of [
|
|
147
|
+
...readablePaths,
|
|
148
|
+
...writablePaths,
|
|
149
|
+
...devices,
|
|
150
|
+
cwd
|
|
151
|
+
]) {
|
|
152
|
+
let parent = dirname(path);
|
|
153
|
+
while (true) {
|
|
154
|
+
ancestors.add(parent);
|
|
155
|
+
const next = dirname(parent);
|
|
156
|
+
if (next === parent) break;
|
|
157
|
+
parent = next;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
argv: [
|
|
162
|
+
"/usr/bin/sandbox-exec",
|
|
163
|
+
"-p",
|
|
164
|
+
[
|
|
165
|
+
"(version 1)",
|
|
166
|
+
"(deny default)",
|
|
167
|
+
"(allow process-exec)",
|
|
168
|
+
"(allow process-fork)",
|
|
169
|
+
"(allow sysctl-read)",
|
|
170
|
+
"(allow file-read* (literal \"/\"))",
|
|
171
|
+
`(allow file-read-metadata ${[...ancestors].map((path) => `(literal ${JSON.stringify(path)})`).join(" ")})`,
|
|
172
|
+
`(allow file-read* ${readablePaths.map(pathRule).join(" ")} ${devices.map((path) => `(literal ${JSON.stringify(path)})`).join(" ")})`,
|
|
173
|
+
`(allow file-read* file-write* ${writablePaths.map(pathRule).join(" ")})`,
|
|
174
|
+
"(allow file-write* (literal \"/dev/null\"))",
|
|
175
|
+
...config.isolation.network === "allow" ? ["(allow network*)"] : []
|
|
176
|
+
].join("\n"),
|
|
177
|
+
executable,
|
|
178
|
+
...argv.slice(1)
|
|
179
|
+
],
|
|
180
|
+
env,
|
|
181
|
+
cleanup
|
|
182
|
+
};
|
|
183
|
+
} catch (cause) {
|
|
184
|
+
cleanup();
|
|
185
|
+
throw cause;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
export { prepareIsolatedCommand, removeConfinedPath };
|
|
189
|
+
|
|
190
|
+
//# sourceMappingURL=isolation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isolation.js","names":[],"sources":["../src/isolation.ts"],"sourcesContent":["import { accessSync, constants, existsSync, lstatSync, mkdirSync, mkdtempSync, realpathSync } from 'node:fs';\nimport { spawnSync } from 'node:child_process';\nimport { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';\nimport { canonicalPath, normalizeIsolationPaths } from './config.js';\nimport type { Config } from './schemas.js';\n\nexport interface IsolationCapabilities {\n /** Narrow capabilities for trusted harness utilities; never populated from project command data. */\n readableFiles?: readonly string[];\n writableDirectories?: readonly string[];\n}\n\nexport interface IsolationContext extends IsolationCapabilities {\n config: Config;\n workspace: string;\n cwd: string;\n env: NodeJS.ProcessEnv;\n scratchParent?: string;\n}\n\nexport interface IsolatedCommand {\n argv: string[];\n env: NodeJS.ProcessEnv;\n cleanup(): void;\n}\n\nfunction failure(message: string): Error & { code: string } {\n return Object.assign(new Error(message), { code: 'SETUP_REQUIRED' });\n}\n\nfunction contains(parent: string, child: string): boolean {\n const path = relative(parent, child);\n return path === '' || path !== '..' && !path.startsWith(`..${sep}`) && !isAbsolute(path);\n}\n\nfunction executablePath(command: string, cwd: string, env: NodeJS.ProcessEnv): string {\n const candidates = command.includes('/') ? [resolve(cwd, command)] : (env.PATH ?? '').split(delimiter).filter(Boolean).map(path => resolve(cwd, path, command));\n for (const candidate of candidates) {\n try {\n accessSync(candidate, constants.X_OK);\n const path = realpathSync(candidate);\n if (lstatSync(path).isFile()) return path;\n } catch {}\n }\n throw failure(`Cannot resolve isolated executable ${command}; select an existing executable in the approved worker PATH`);\n}\n\nfunction pathRule(path: string): string {\n const info = lstatSync(path);\n if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) throw failure(`Isolation grant requires a regular canonical file or directory: ${path}`);\n return `(${info.isDirectory() ? 'subpath' : 'literal'} ${JSON.stringify(path)})`;\n}\n\n/** Keep deletion authority on the original trusted root, never a re-resolved target or its parent. */\nexport function removeConfinedPath(path: string, root: string): void {\n if (process.platform !== 'darwin') throw failure('Confined cleanup requires the selected macOS backend');\n const boundary = resolve(root), target = resolve(path);\n if (boundary === '/' || !contains(boundary, target)) throw failure('Cleanup target must stay within its original isolation root');\n const ancestors = new Set<string>(['/', '/bin', '/usr', '/System']);\n for (let parent = dirname(boundary); !ancestors.has(parent); parent = dirname(parent)) ancestors.add(parent);\n const profile = [\n '(version 1)', '(deny default)', '(allow process-exec)', '(allow sysctl-read)',\n '(allow file-read* (literal \"/\") (literal \"/bin/rm\") (subpath \"/System/Library\") (subpath \"/usr/lib\") (literal \"/dev/null\"))',\n `(allow file-read-metadata ${[...ancestors].map(parent => `(literal ${JSON.stringify(parent)})`).join(' ')})`,\n `(allow file-read* file-write* (subpath ${JSON.stringify(boundary)}))`,\n '(allow file-write* (literal \"/dev/null\"))',\n ].join('\\n');\n const result = spawnSync('/usr/bin/sandbox-exec', ['-p', profile, '/bin/rm', '-rf', '--', target], {\n cwd: '/', env: { PATH: '/usr/bin:/bin', LANG: 'C' }, encoding: 'utf8', timeout: 10000, maxBuffer: 65536,\n });\n if (result.error || result.signal || result.status !== 0) throw Object.assign(new Error(`Confined cleanup did not complete; outside paths were not granted: ${result.error?.message ?? result.stderr ?? result.signal}`), { code: 'PROCESS_CLEANUP_BLOCKED' });\n}\n\n/** This confines spawned commands, not the host agent, resource consumption or GUI/device services. */\nexport function prepareIsolatedCommand(argv: string[], context: IsolationContext): IsolatedCommand {\n if (!context.config.isolation) throw failure('No subprocess isolation mode was selected');\n if (process.platform !== 'darwin') throw failure('macos-sandbox isolation requires macOS; execution was not downgraded to trusted mode');\n try { accessSync('/usr/bin/sandbox-exec', constants.X_OK); }\n catch { throw failure('The selected macos-sandbox backend is unavailable; no command was started'); }\n const config = { ...context.config, isolation: { ...context.config.isolation, readOnlyPaths: [...context.config.isolation.readOnlyPaths] } };\n normalizeIsolationPaths(config);\n for (let index = 0; index < config.isolation.readOnlyPaths.length; index++) {\n if (config.isolation.readOnlyPaths[index] !== context.config.isolation.readOnlyPaths[index]) throw failure('An isolation read-only path changed identity; review the owner configuration before retrying');\n }\n const workspaceInfo = lstatSync(context.workspace);\n if (!workspaceInfo.isDirectory() || workspaceInfo.isSymbolicLink()) throw failure('Isolation requires a real owned workspace directory');\n const workspace = realpathSync(context.workspace);\n const state = canonicalPath(config.stateDir), workspaceRoot = canonicalPath(config.workspaceRoot);\n if (workspace === workspaceRoot || contains(workspace, state) || contains(state, workspace)) throw failure('Isolation workspace must not expose control state or the whole workspace root');\n const cwd = realpathSync(context.cwd);\n const approved = [workspace, ...config.isolation.readOnlyPaths];\n if (!approved.some(path => contains(path, cwd))) throw failure('Isolated command cwd must stay in its workspace or an approved read-only root');\n const executable = executablePath(argv[0] ?? '', cwd, context.env);\n if (contains(state, executable) || contains(workspaceRoot, executable) && !contains(workspace, executable)) throw failure('An isolated command cannot execute control state or another task workspace');\n const readable = [...config.isolation.readOnlyPaths, executable, realpathSync(process.execPath)];\n for (const file of context.readableFiles ?? []) {\n if (!lstatSync(file).isFile() || lstatSync(file).isSymbolicLink()) throw failure(`Harness isolation capability requires a regular file: ${file}`);\n readable.push(realpathSync(file));\n }\n const extraWritable = (context.writableDirectories ?? []).map(path => {\n if (!lstatSync(path).isDirectory() || lstatSync(path).isSymbolicLink()) throw failure(`Harness isolation capability requires a real directory: ${path}`);\n return realpathSync(path);\n });\n const scratchParent = context.scratchParent ?? state;\n mkdirSync(scratchParent, { recursive: true, mode: 0o700 });\n const scratch = realpathSync(mkdtempSync(join(scratchParent, 'isolated-command-')));\n const cleanup = () => removeConfinedPath(scratch, scratch);\n try {\n const home = join(scratch, 'home'), temporary = join(scratch, 'tmp'), cache = join(scratch, 'cache');\n for (const path of [home, temporary, cache]) mkdirSync(path, { mode: 0o700 });\n const env: NodeJS.ProcessEnv = { ...context.env, HOME: home, TMPDIR: `${temporary}${sep}`, TMP: temporary, TEMP: temporary, XDG_CACHE_HOME: cache, GRADLE_USER_HOME: join(cache, 'gradle'), npm_config_cache: join(cache, 'npm') };\n for (const [name, fallback] of [['GOCACHE', join(cache, 'go-build')], ['GOMODCACHE', join(cache, 'go-mod')]] as const) {\n if (!env[name] || contains(state, canonicalPath(resolve(cwd, env[name]!)))) env[name] = fallback;\n }\n const systemPaths = ['/System/Library', '/usr/lib'].filter(existsSync);\n const devices = ['/dev/urandom', '/dev/random', '/dev/null'].filter(existsSync);\n const readablePaths = [...new Set([...systemPaths, ...readable])];\n const writablePaths = [...new Set([workspace, scratch, ...extraWritable])];\n const ancestors = new Set<string>(['/']);\n for (const path of [...readablePaths, ...writablePaths, ...devices, cwd]) {\n let parent = dirname(path);\n while (true) {\n ancestors.add(parent);\n const next = dirname(parent);\n if (next === parent) break;\n parent = next;\n }\n }\n const profile = [\n '(version 1)', '(deny default)', '(allow process-exec)', '(allow process-fork)', '(allow sysctl-read)',\n '(allow file-read* (literal \"/\"))',\n `(allow file-read-metadata ${[...ancestors].map(path => `(literal ${JSON.stringify(path)})`).join(' ')})`,\n `(allow file-read* ${readablePaths.map(pathRule).join(' ')} ${devices.map(path => `(literal ${JSON.stringify(path)})`).join(' ')})`,\n `(allow file-read* file-write* ${writablePaths.map(pathRule).join(' ')})`,\n '(allow file-write* (literal \"/dev/null\"))',\n ...(config.isolation.network === 'allow' ? ['(allow network*)'] : []),\n ].join('\\n');\n return { argv: ['/usr/bin/sandbox-exec', '-p', profile, executable, ...argv.slice(1)], env, cleanup };\n } catch (cause) {\n cleanup();\n throw cause;\n }\n}\n"],"mappings":"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"}
|
package/dist/macos.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { artifactOutputPath } from "./workspace.js";
|
|
2
|
+
import { closeSync, lstatSync, openSync, readSync } from "node:fs";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
// src/macos.ts
|
|
5
|
+
/** Validate native output with system tooling, not just the file extension. */
|
|
6
|
+
async function validateMacOSArtifact(path, target, exec) {
|
|
7
|
+
if (target === "dmg") {
|
|
8
|
+
const result = await exec([
|
|
9
|
+
"/usr/bin/hdiutil",
|
|
10
|
+
"verify",
|
|
11
|
+
path
|
|
12
|
+
]);
|
|
13
|
+
if (result.code !== 0) throw new Error(`Invalid macOS disk image: ${result.stderr || result.stdout}`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const info = artifactOutputPath(path, "Contents/Info.plist");
|
|
17
|
+
if (!lstatSync(info).isFile()) throw new Error("macOS application requires a regular Contents/Info.plist");
|
|
18
|
+
const result = await exec([
|
|
19
|
+
"/usr/bin/plutil",
|
|
20
|
+
"-extract",
|
|
21
|
+
"CFBundleExecutable",
|
|
22
|
+
"raw",
|
|
23
|
+
"-o",
|
|
24
|
+
"-",
|
|
25
|
+
info
|
|
26
|
+
]);
|
|
27
|
+
const executable = result.stdout.trim();
|
|
28
|
+
if (result.code !== 0 || !executable || executable === "." || executable === ".." || basename(executable) !== executable || executable.includes("\\")) throw new Error("macOS Info.plist must identify an in-bundle executable by file name");
|
|
29
|
+
const binary = artifactOutputPath(path, join("Contents", "MacOS", executable));
|
|
30
|
+
const stat = lstatSync(binary);
|
|
31
|
+
if (!stat.isFile() || stat.size < 32 || !(stat.mode & 73)) throw new Error("macOS application executable is missing, empty or not executable");
|
|
32
|
+
const header = Buffer.allocUnsafe(4);
|
|
33
|
+
const descriptor = openSync(binary, "r");
|
|
34
|
+
try {
|
|
35
|
+
if (readSync(descriptor, header, 0, 4, 0) !== 4 || ![
|
|
36
|
+
"feedfacf",
|
|
37
|
+
"cffaedfe",
|
|
38
|
+
"cafebabe",
|
|
39
|
+
"bebafeca",
|
|
40
|
+
"cafebabf",
|
|
41
|
+
"bfbafeca"
|
|
42
|
+
].includes(header.toString("hex"))) throw new Error("macOS application executable must be a Mach-O binary, not a script or placeholder");
|
|
43
|
+
} finally {
|
|
44
|
+
closeSync(descriptor);
|
|
45
|
+
}
|
|
46
|
+
const signed = await exec([
|
|
47
|
+
"/usr/bin/codesign",
|
|
48
|
+
"--verify",
|
|
49
|
+
"--deep",
|
|
50
|
+
"--strict",
|
|
51
|
+
path
|
|
52
|
+
]);
|
|
53
|
+
if (signed.code !== 0) throw new Error(`macOS local application signature is invalid: ${signed.stderr || signed.stdout}`);
|
|
54
|
+
}
|
|
55
|
+
export { validateMacOSArtifact };
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=macos.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"macos.js","names":[],"sources":["../src/macos.ts"],"sourcesContent":["import { basename, join } from 'node:path';\nimport { closeSync, lstatSync, openSync, readSync } from 'node:fs';\nimport { artifactOutputPath } from './workspace.js';\nimport type { Run } from './process.js';\n\n\n/** Validate native output with system tooling, not just the file extension. */\nexport async function validateMacOSArtifact(path: string, target: 'app' | 'dmg', exec: Run): Promise<void> {\n if (target === 'dmg') {\n const result = await exec(['/usr/bin/hdiutil', 'verify', path]);\n if (result.code !== 0) throw new Error(`Invalid macOS disk image: ${result.stderr || result.stdout}`);\n return;\n }\n const info = artifactOutputPath(path, 'Contents/Info.plist');\n if (!lstatSync(info).isFile()) throw new Error('macOS application requires a regular Contents/Info.plist');\n const result = await exec(['/usr/bin/plutil', '-extract', 'CFBundleExecutable', 'raw', '-o', '-', info]);\n const executable = result.stdout.trim();\n if (result.code !== 0 || !executable || executable === '.' || executable === '..' || basename(executable) !== executable || executable.includes('\\\\')) throw new Error('macOS Info.plist must identify an in-bundle executable by file name');\n const binary = artifactOutputPath(path, join('Contents', 'MacOS', executable));\n const stat = lstatSync(binary);\n if (!stat.isFile() || stat.size < 32 || !(stat.mode & 0o111)) throw new Error('macOS application executable is missing, empty or not executable');\n const header = Buffer.allocUnsafe(4);\n const descriptor = openSync(binary, 'r');\n try {\n if (readSync(descriptor, header, 0, 4, 0) !== 4 || !['feedfacf', 'cffaedfe', 'cafebabe', 'bebafeca', 'cafebabf', 'bfbafeca'].includes(header.toString('hex'))) throw new Error('macOS application executable must be a Mach-O binary, not a script or placeholder');\n } finally { closeSync(descriptor); }\n const signed = await exec(['/usr/bin/codesign', '--verify', '--deep', '--strict', path]);\n if (signed.code !== 0) throw new Error(`macOS local application signature is invalid: ${signed.stderr || signed.stdout}`);\n}\n"],"mappings":";;;;;AAOA,eAAsB,sBAAsB,MAAc,QAAuB,MAA0B;CACzG,IAAI,WAAW,OAAO;EACpB,MAAM,SAAS,MAAM,KAAK;GAAC;GAAoB;GAAU;EAAI,CAAC;EAC9D,IAAI,OAAO,SAAS,GAAG,MAAM,IAAI,MAAM,6BAA6B,OAAO,UAAU,OAAO,QAAQ;EACpG;CACF;CACA,MAAM,OAAO,mBAAmB,MAAM,qBAAqB;CAC3D,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC,OAAO,GAAG,MAAM,IAAI,MAAM,0DAA0D;CACzG,MAAM,SAAS,MAAM,KAAK;EAAC;EAAmB;EAAY;EAAsB;EAAO;EAAM;EAAK;CAAI,CAAC;CACvG,MAAM,aAAa,OAAO,OAAO,KAAK;CACtC,IAAI,OAAO,SAAS,KAAK,CAAC,cAAc,eAAe,OAAO,eAAe,QAAQ,SAAS,UAAU,MAAM,cAAc,WAAW,SAAS,IAAI,GAAG,MAAM,IAAI,MAAM,qEAAqE;CAC5O,MAAM,SAAS,mBAAmB,MAAM,KAAK,YAAY,SAAS,UAAU,CAAC;CAC7E,MAAM,OAAO,UAAU,MAAM;CAC7B,IAAI,CAAC,KAAK,OAAO,KAAK,KAAK,OAAO,MAAM,EAAE,KAAK,OAAO,KAAQ,MAAM,IAAI,MAAM,kEAAkE;CAChJ,MAAM,SAAS,OAAO,YAAY,CAAC;CACnC,MAAM,aAAa,SAAS,QAAQ,GAAG;CACvC,IAAI;EACF,IAAI,SAAS,YAAY,QAAQ,GAAG,GAAG,CAAC,MAAM,KAAK,CAAC;GAAC;GAAY;GAAY;GAAY;GAAY;GAAY;EAAU,CAAC,CAAC,SAAS,OAAO,SAAS,KAAK,CAAC,GAAG,MAAM,IAAI,MAAM,mFAAmF;CACpQ,UAAU;EAAE,UAAU,UAAU;CAAG;CACnC,MAAM,SAAS,MAAM,KAAK;EAAC;EAAqB;EAAY;EAAU;EAAY;CAAI,CAAC;CACvF,IAAI,OAAO,SAAS,GAAG,MAAM,IAAI,MAAM,iDAAiD,OAAO,UAAU,OAAO,QAAQ;AAC1H"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { basename, posix } from "node:path";
|
|
2
|
+
// src/mygo-build.ts
|
|
3
|
+
/** Accept a direct, local MyGo build, never a wrapper that can add publishing flags. */
|
|
4
|
+
function validateMyGoPackaging(config, screenshotsDir) {
|
|
5
|
+
const profile = config.packaging;
|
|
6
|
+
if (!profile || profile.provider !== "local" || profile.commands.length !== 1) throw Object.assign(/* @__PURE__ */ new Error("MyGo requires one direct local build command"), { code: "SETUP_REQUIRED" });
|
|
7
|
+
const argv = profile.commands[0];
|
|
8
|
+
let start;
|
|
9
|
+
if (basename(argv[0]) === "mygo" && argv[1] === "build") start = 2;
|
|
10
|
+
else if (basename(argv[0]) === "go" && argv[1] === "tool" && argv[2] === "mygo" && argv[3] === "build") start = 4;
|
|
11
|
+
else if (basename(argv[0]) === "go" && argv[1] === "run" && /^github\.com\/egoist\/mygo\/cmd\/mygo@v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(argv[2] ?? "") && argv[3] === "build") start = 4;
|
|
12
|
+
else throw Object.assign(/* @__PURE__ */ new Error("MyGo packaging must use a pinned go tool mygo build, installed mygo build, or version-pinned go run command"), { code: "SETUP_REQUIRED" });
|
|
13
|
+
const flags = /* @__PURE__ */ new Map();
|
|
14
|
+
for (let index = start; index < argv.length; index++) {
|
|
15
|
+
const flag = argv[index];
|
|
16
|
+
if (flags.has(flag)) throw Object.assign(/* @__PURE__ */ new Error(`Duplicate MyGo build flag: ${flag}`), { code: "SETUP_REQUIRED" });
|
|
17
|
+
if (flag === "-sign" || flag === "-platform" || flag === "-o") {
|
|
18
|
+
const value = argv[++index];
|
|
19
|
+
if (!value) throw Object.assign(/* @__PURE__ */ new Error(`Missing MyGo flag value: ${flag}`), { code: "SETUP_REQUIRED" });
|
|
20
|
+
flags.set(flag, value);
|
|
21
|
+
} else if (flag === "-skip-notarize" || flag === "-skip-dmg" || flag === "-skip-build-command") flags.set(flag, "true");
|
|
22
|
+
else throw Object.assign(/* @__PURE__ */ new Error(`MyGo local packaging does not permit ${flag}; upload, notarization and custom wrapper commands are out of scope`), { code: "SETUP_REQUIRED" });
|
|
23
|
+
}
|
|
24
|
+
if (flags.get("-sign") !== "-" || !flags.has("-skip-notarize")) throw Object.assign(/* @__PURE__ */ new Error("MyGo local builds require -sign - and -skip-notarize, even when project configuration enables signing/notarization"), { code: "SETUP_REQUIRED" });
|
|
25
|
+
const platform = flags.get("-platform");
|
|
26
|
+
if (!platform || !/^darwin\/(?:arm64|amd64|universal)$/.test(platform)) throw Object.assign(/* @__PURE__ */ new Error("MyGo packaging requires one explicit darwin/arm64, darwin/amd64 or darwin/universal target"), { code: "SETUP_REQUIRED" });
|
|
27
|
+
if (flags.get("-o") !== "build") throw Object.assign(/* @__PURE__ */ new Error("MyGo packaging requires -o build so output cannot replace project source"), { code: "SETUP_REQUIRED" });
|
|
28
|
+
const prefix = `build/${platform.replace("/", "-")}/`;
|
|
29
|
+
if (!profile.artifactPath.startsWith(prefix)) throw Object.assign(/* @__PURE__ */ new Error(`MyGo artifactPath must be inside ${prefix}`), { code: "SETUP_REQUIRED" });
|
|
30
|
+
if (screenshotsDir !== void 0) {
|
|
31
|
+
const screenshot = posix.normalize(screenshotsDir);
|
|
32
|
+
if (posix.isAbsolute(screenshot) || screenshot === ".." || screenshot.startsWith("../") || screenshot.includes("\\")) throw Object.assign(/* @__PURE__ */ new Error("MyGo screenshotsDir must stay within the selected project"), { code: "SETUP_REQUIRED" });
|
|
33
|
+
const outputDirectory = prefix.slice(0, -1);
|
|
34
|
+
if ([posix.relative(outputDirectory, screenshot), posix.relative(screenshot, outputDirectory)].some((path) => path === "" || path !== ".." && !path.startsWith("../") && !posix.isAbsolute(path))) throw Object.assign(/* @__PURE__ */ new Error("MyGo screenshotsDir must not overlap the platform output directory replaced during packaging"), { code: "SETUP_REQUIRED" });
|
|
35
|
+
}
|
|
36
|
+
if (config.packageTarget === "app" ? !flags.has("-skip-dmg") : flags.has("-skip-dmg")) throw Object.assign(/* @__PURE__ */ new Error("MyGo app builds require -skip-dmg; dmg builds must create the disk image"), { code: "SETUP_REQUIRED" });
|
|
37
|
+
}
|
|
38
|
+
/** Doctor and tracked execution share the same gate for every recognizable direct build. */
|
|
39
|
+
function validateMyGoCommand(config, argv) {
|
|
40
|
+
const tool = basename(argv[0] ?? "");
|
|
41
|
+
const buildIndex = argv.indexOf("build");
|
|
42
|
+
if (buildIndex <= 0) return;
|
|
43
|
+
if (!(tool === "mygo" || tool === "go" && argv.slice(1, buildIndex).some((value) => value === "mygo" || value === "github.com/egoist/mygo/cmd/mygo" || value.startsWith("github.com/egoist/mygo/cmd/mygo@")))) return;
|
|
44
|
+
validateMyGoPackaging({
|
|
45
|
+
...config,
|
|
46
|
+
packageTarget: argv.includes("-skip-dmg") ? "app" : "dmg",
|
|
47
|
+
packaging: config.packaging ? {
|
|
48
|
+
...config.packaging,
|
|
49
|
+
commands: [argv]
|
|
50
|
+
} : void 0
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export { validateMyGoCommand, validateMyGoPackaging };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=mygo-build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mygo-build.js","names":[],"sources":["../src/mygo-build.ts"],"sourcesContent":["import { basename, posix } from 'node:path';\nimport type { Config } from './schemas.js';\n\n/** Accept a direct, local MyGo build, never a wrapper that can add publishing flags. */\nexport function validateMyGoPackaging(config: Config, screenshotsDir?: string): void {\n const profile = config.packaging;\n if (!profile || profile.provider !== 'local' || profile.commands.length !== 1) throw Object.assign(new Error('MyGo requires one direct local build command'), { code: 'SETUP_REQUIRED' });\n const argv = profile.commands[0]!;\n let start: number;\n if (basename(argv[0]!) === 'mygo' && argv[1] === 'build') start = 2;\n else if (basename(argv[0]!) === 'go' && argv[1] === 'tool' && argv[2] === 'mygo' && argv[3] === 'build') start = 4;\n else if (basename(argv[0]!) === 'go' && argv[1] === 'run' && /^github\\.com\\/egoist\\/mygo\\/cmd\\/mygo@v\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$/.test(argv[2] ?? '') && argv[3] === 'build') start = 4;\n else throw Object.assign(new Error('MyGo packaging must use a pinned go tool mygo build, installed mygo build, or version-pinned go run command'), { code: 'SETUP_REQUIRED' });\n const flags = new Map<string, string>();\n for (let index = start; index < argv.length; index++) {\n const flag = argv[index]!;\n if (flags.has(flag)) throw Object.assign(new Error(`Duplicate MyGo build flag: ${flag}`), { code: 'SETUP_REQUIRED' });\n if (flag === '-sign' || flag === '-platform' || flag === '-o') {\n const value = argv[++index];\n if (!value) throw Object.assign(new Error(`Missing MyGo flag value: ${flag}`), { code: 'SETUP_REQUIRED' });\n flags.set(flag, value);\n } else if (flag === '-skip-notarize' || flag === '-skip-dmg' || flag === '-skip-build-command') flags.set(flag, 'true');\n else throw Object.assign(new Error(`MyGo local packaging does not permit ${flag}; upload, notarization and custom wrapper commands are out of scope`), { code: 'SETUP_REQUIRED' });\n }\n if (flags.get('-sign') !== '-' || !flags.has('-skip-notarize')) throw Object.assign(new Error('MyGo local builds require -sign - and -skip-notarize, even when project configuration enables signing/notarization'), { code: 'SETUP_REQUIRED' });\n const platform = flags.get('-platform');\n if (!platform || !/^darwin\\/(?:arm64|amd64|universal)$/.test(platform)) throw Object.assign(new Error('MyGo packaging requires one explicit darwin/arm64, darwin/amd64 or darwin/universal target'), { code: 'SETUP_REQUIRED' });\n if (flags.get('-o') !== 'build') throw Object.assign(new Error('MyGo packaging requires -o build so output cannot replace project source'), { code: 'SETUP_REQUIRED' });\n const prefix = `build/${platform.replace('/', '-')}/`;\n if (!profile.artifactPath.startsWith(prefix)) throw Object.assign(new Error(`MyGo artifactPath must be inside ${prefix}`), { code: 'SETUP_REQUIRED' });\n if (screenshotsDir !== undefined) {\n const screenshot = posix.normalize(screenshotsDir);\n if (posix.isAbsolute(screenshot) || screenshot === '..' || screenshot.startsWith('../') || screenshot.includes('\\\\')) throw Object.assign(new Error('MyGo screenshotsDir must stay within the selected project'), { code: 'SETUP_REQUIRED' });\n const outputDirectory = prefix.slice(0, -1);\n if ([posix.relative(outputDirectory, screenshot), posix.relative(screenshot, outputDirectory)].some(path => path === '' || path !== '..' && !path.startsWith('../') && !posix.isAbsolute(path))) throw Object.assign(new Error('MyGo screenshotsDir must not overlap the platform output directory replaced during packaging'), { code: 'SETUP_REQUIRED' });\n }\n if (config.packageTarget === 'app' ? !flags.has('-skip-dmg') : flags.has('-skip-dmg')) throw Object.assign(new Error('MyGo app builds require -skip-dmg; dmg builds must create the disk image'), { code: 'SETUP_REQUIRED' });\n}\n\n/** Doctor and tracked execution share the same gate for every recognizable direct build. */\nexport function validateMyGoCommand(config: Config, argv: string[]): void {\n const tool = basename(argv[0] ?? '');\n const buildIndex = argv.indexOf('build');\n if (buildIndex <= 0) return;\n const direct = tool === 'mygo' || tool === 'go' && argv.slice(1, buildIndex).some(value => value === 'mygo' || value === 'github.com/egoist/mygo/cmd/mygo' || value.startsWith('github.com/egoist/mygo/cmd/mygo@'));\n if (!direct) return;\n validateMyGoPackaging({ ...config, packageTarget: argv.includes('-skip-dmg') ? 'app' : 'dmg', packaging: config.packaging ? { ...config.packaging, commands: [argv] } : undefined });\n}\n"],"mappings":"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"}
|
package/dist/policy.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ResearchSchema } from "./schemas.js";
|
|
2
|
+
// src/policy.ts
|
|
3
|
+
function parseResearch(text, channel) {
|
|
4
|
+
const content = text.trim().replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```$/, "");
|
|
5
|
+
let value;
|
|
6
|
+
try {
|
|
7
|
+
value = JSON.parse(content);
|
|
8
|
+
} catch {
|
|
9
|
+
throw new Error("Host did not return a JSON verdict; no approval was inferred from text or an exit code");
|
|
10
|
+
}
|
|
11
|
+
return ResearchSchema.parse(value);
|
|
12
|
+
}
|
|
13
|
+
/** Honest pre-delivery differentiation and quality checklist. Store submission and review stay manual. */
|
|
14
|
+
function internalQaChecklist() {
|
|
15
|
+
return [
|
|
16
|
+
"Internal QA checklist for a normal, honest local package handoff. Store submission, account setup and platform review are manual human work outside this tool; nothing here bypasses, influences or pre-approves a store reviewer.",
|
|
17
|
+
"Real value: each package has its own genuine purpose, audience and functional behavior. Identical functionality with only renamed labels is not meaningful differentiation.",
|
|
18
|
+
"Visual identity: themes, layouts, colors and interaction flows are real product choices for this app, not templated reskins produced to look different.",
|
|
19
|
+
"Developer and identity metadata: app name, title, icon, bundle/package id, keywords and description truthfully describe this app and its actual developer. Never forge identities, accounts, locations or submission sources.",
|
|
20
|
+
"Functionality: every advertised feature works and is exercised by the frozen runtime acceptance; no hidden functionality for any audience and no fabricated screenshots or evidence.",
|
|
21
|
+
"Metadata: price, region, category and description reflect the actual product. Design rationale notes, if any, are the owner's honest answers, not scripted reply templates.",
|
|
22
|
+
"Code identity: project, folder, class, function and resource names follow this project's real conventions. Do not mutate hashes, names or resource bytes solely to defeat similarity checks; that would be deceptive.",
|
|
23
|
+
"Quality gate: source, dependency and runtime proofs must be current; independent review must cover every frozen acceptance requirement with observed evidence. Only then may the artifact be handed off to a human for store submission."
|
|
24
|
+
].join("\n");
|
|
25
|
+
}
|
|
26
|
+
function researchPrompt(job, portfolio) {
|
|
27
|
+
const siblings = `Other same-channel products ${JSON.stringify(portfolio)}; earlier versions of this same app are its source baseline, not competing products.`;
|
|
28
|
+
return `Research only with your own host tools. Preserve the requested ${job.spec.framework} ${job.spec.platform} project and its existing architecture. ${internalQaChecklist()} ${siblings} Assess technical feasibility, dependencies, licensing, privacy and real acceptance for this local package. Return typed JSON {"viable":boolean,"distinctness":"specific honest differentiation","risks":["..."],"plan":["concrete implementation and device acceptance step"]}. Do not promise store approval and do not add unrequested features.`;
|
|
29
|
+
}
|
|
30
|
+
function reviewInstructions(job) {
|
|
31
|
+
return `Use an independent reviewer with fresh context when supported; otherwise disclose the limitation. Review actual source and acceptance read-only. Verified source: ${job.verifiedDigest}; dependency resolution: ${job.verifiedDependencyDigest}; actual checks: ${JSON.stringify(job.config.verification)}. ${internalQaChecklist()} Reject incomplete requirements, data loss, fake screenshots, accessibility failures, placeholders, weak tests and hidden features. Return typed JSON {"approved":boolean,"findings":["blocking issue"],"acceptanceEvidence":[{"requirement":"exact acceptance string","evidence":"file/symbol + observed command or device scenario"}]}. Cover every acceptance requirement. Reviewer identity is advisory; the Harness gates typed results and unchanged execution evidence.`;
|
|
32
|
+
}
|
|
33
|
+
export { internalQaChecklist, parseResearch, researchPrompt, reviewInstructions };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","names":[],"sources":["../src/policy.ts"],"sourcesContent":["import { ResearchSchema } from './schemas.js';\nimport type { Job, Spec } from './schemas.js';\n\nexport function parseResearch(text: string, channel: Spec['channel']) {\n const content = text.trim().replace(/^```(?:json)?\\s*\\n?/, '').replace(/\\n?```$/, '');\n let value: unknown;\n try { value = JSON.parse(content); }\n catch { throw new Error('Host did not return a JSON verdict; no approval was inferred from text or an exit code'); }\n return ResearchSchema.parse(value);\n}\n\n/** Honest pre-delivery differentiation and quality checklist. Store submission and review stay manual. */\nexport function internalQaChecklist(): string {\n return [\n 'Internal QA checklist for a normal, honest local package handoff. Store submission, account setup and platform review are manual human work outside this tool; nothing here bypasses, influences or pre-approves a store reviewer.',\n 'Real value: each package has its own genuine purpose, audience and functional behavior. Identical functionality with only renamed labels is not meaningful differentiation.',\n 'Visual identity: themes, layouts, colors and interaction flows are real product choices for this app, not templated reskins produced to look different.',\n 'Developer and identity metadata: app name, title, icon, bundle/package id, keywords and description truthfully describe this app and its actual developer. Never forge identities, accounts, locations or submission sources.',\n 'Functionality: every advertised feature works and is exercised by the frozen runtime acceptance; no hidden functionality for any audience and no fabricated screenshots or evidence.',\n 'Metadata: price, region, category and description reflect the actual product. Design rationale notes, if any, are the owner\\'s honest answers, not scripted reply templates.',\n 'Code identity: project, folder, class, function and resource names follow this project\\'s real conventions. Do not mutate hashes, names or resource bytes solely to defeat similarity checks; that would be deceptive.',\n 'Quality gate: source, dependency and runtime proofs must be current; independent review must cover every frozen acceptance requirement with observed evidence. Only then may the artifact be handed off to a human for store submission.',\n ].join('\\n');\n}\n\nexport function researchPrompt(job: Job, portfolio: Array<Pick<Spec, 'id' | 'purpose' | 'audience' | 'differentiators'>>): string {\n const siblings = `Other same-channel products ${JSON.stringify(portfolio)}; earlier versions of this same app are its source baseline, not competing products.`;\n return `Research only with your own host tools. Preserve the requested ${job.spec.framework} ${job.spec.platform} project and its existing architecture. ${internalQaChecklist()} ${siblings} Assess technical feasibility, dependencies, licensing, privacy and real acceptance for this local package. Return typed JSON {\"viable\":boolean,\"distinctness\":\"specific honest differentiation\",\"risks\":[\"...\"],\"plan\":[\"concrete implementation and device acceptance step\"]}. Do not promise store approval and do not add unrequested features.`;\n}\n\nexport function reviewInstructions(job: Job): string {\n return `Use an independent reviewer with fresh context when supported; otherwise disclose the limitation. Review actual source and acceptance read-only. Verified source: ${job.verifiedDigest}; dependency resolution: ${job.verifiedDependencyDigest}; actual checks: ${JSON.stringify(job.config.verification)}. ${internalQaChecklist()} Reject incomplete requirements, data loss, fake screenshots, accessibility failures, placeholders, weak tests and hidden features. Return typed JSON {\"approved\":boolean,\"findings\":[\"blocking issue\"],\"acceptanceEvidence\":[{\"requirement\":\"exact acceptance string\",\"evidence\":\"file/symbol + observed command or device scenario\"}]}. Cover every acceptance requirement. Reviewer identity is advisory; the Harness gates typed results and unchanged execution evidence.`;\n}\n"],"mappings":";;AAGA,SAAgB,cAAc,MAAc,SAA0B;CACpE,MAAM,UAAU,KAAK,KAAK,CAAC,CAAC,QAAQ,uBAAuB,EAAE,CAAC,CAAC,QAAQ,WAAW,EAAE;CACpF,IAAI;CACJ,IAAI;EAAE,QAAQ,KAAK,MAAM,OAAO;CAAG,QAC7B;EAAE,MAAM,IAAI,MAAM,wFAAwF;CAAG;CACnH,OAAO,eAAe,MAAM,KAAK;AACnC;;AAGA,SAAgB,sBAA8B;CAC5C,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI;AACb;AAEA,SAAgB,eAAe,KAAU,WAAyF;CAChI,MAAM,WAAW,+BAA+B,KAAK,UAAU,SAAS,EAAE;CAC1E,OAAO,kEAAkE,IAAI,KAAK,UAAU,GAAG,IAAI,KAAK,SAAS,0CAA0C,oBAAoB,EAAE,GAAG,SAAS;AAC/L;AAEA,SAAgB,mBAAmB,KAAkB;CACnD,OAAO,qKAAqK,IAAI,eAAe,2BAA2B,IAAI,yBAAyB,mBAAmB,KAAK,UAAU,IAAI,OAAO,YAAY,EAAE,IAAI,oBAAoB,EAAE;AAC9U"}
|
package/dist/preflight.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { toError } from "./schemas.js";
|
|
2
|
-
import { run } from "./process.js";
|
|
3
2
|
import { expandArgv } from "./config.js";
|
|
3
|
+
import { run } from "./process.js";
|
|
4
4
|
// src/preflight.ts
|
|
5
5
|
function prerequisiteFailure(argv, reason, detail, cause) {
|
|
6
6
|
return Object.assign(new Error(`Prerequisite ${JSON.stringify(argv)} ${detail}. Restore the configured worker environment before retrying.`, { cause }), {
|
package/dist/process.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { toError } from "./schemas.js";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { dirname } from "node:path";
|
|
4
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
5
2
|
import { closeSync, mkdirSync, openSync, readFileSync, writeSync } from "node:fs";
|
|
3
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
6
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
7
7
|
// src/process.ts
|
|
8
8
|
const COMMAND_TOKEN = "APPLE_APP_FACTORY_COMMAND_TOKEN";
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { inside } from "./config.js";
|
|
2
|
+
import { parseGoManifest } from "./go-dependencies.js";
|
|
3
|
+
import { generatedModulePaths, isGeneratedPath } from "./dependencies.js";
|
|
4
|
+
import { lstatSync, readFileSync, readdirSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { parseDocument } from "yaml";
|
|
7
|
+
// src/project-profiles.ts
|
|
8
|
+
function file(root, path) {
|
|
9
|
+
const absolute = inside(root, path);
|
|
10
|
+
const stat = lstatSync(absolute, { throwIfNoEntry: false });
|
|
11
|
+
if (!stat) return void 0;
|
|
12
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`Project manifest must be a regular source file: ${path}`);
|
|
13
|
+
return readFileSync(absolute, "utf8");
|
|
14
|
+
}
|
|
15
|
+
function json(root, path, comments = false) {
|
|
16
|
+
let text = file(root, path);
|
|
17
|
+
if (text === void 0) return void 0;
|
|
18
|
+
if (comments) text = text.replace(/"(?:\\.|[^"\\])*"|\/\*[\s\S]*?\*\/|\/\/[^\r\n]*/g, (token) => token.startsWith("/") ? "" : token);
|
|
19
|
+
const value = JSON.parse(text);
|
|
20
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`Expected a project manifest object: ${path}`);
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function directories(root, framework, current = "", moduleOutputs = [...generatedModulePaths(root, framework)]) {
|
|
24
|
+
const result = [current];
|
|
25
|
+
for (const entry of readdirSync(join(root, current), { withFileTypes: true })) {
|
|
26
|
+
const path = current ? `${current}/${entry.name}` : entry.name;
|
|
27
|
+
if (isGeneratedPath(path, framework) || moduleOutputs.some((output) => path === output || path.startsWith(`${output}/`))) continue;
|
|
28
|
+
const stat = lstatSync(join(root, path));
|
|
29
|
+
if (stat.isSymbolicLink()) throw new Error(`Project source symlinks are not trusted: ${path}`);
|
|
30
|
+
if (stat.isDirectory()) result.push(...directories(root, framework, path, moduleOutputs));
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
/** The caller resolves project.directory; this function never generates or ejects a project. */
|
|
35
|
+
function assertProject(root, spec) {
|
|
36
|
+
const fail = (manifest) => {
|
|
37
|
+
throw new Error(`${spec.framework} build completion requires ${manifest} in the project directory`);
|
|
38
|
+
};
|
|
39
|
+
if (spec.platform === "macos" && spec.framework !== "mygo") fail("an existing supported iOS or Android platform; macOS is supported only for MyGo");
|
|
40
|
+
if (spec.framework === "mygo") {
|
|
41
|
+
if (spec.platform !== "macos") fail("the macOS platform");
|
|
42
|
+
const mod = file(root, "go.mod");
|
|
43
|
+
if (mod === void 0 || !parseGoManifest(mod).requires.has("github.com/egoist/mygo")) fail("a go.mod requiring github.com/egoist/mygo");
|
|
44
|
+
const native = json(root, "mygo.json");
|
|
45
|
+
const web = file(root, "mygo.config.ts");
|
|
46
|
+
if (native === void 0 === (web === void 0)) fail("exactly one mygo.json or mygo.config.ts");
|
|
47
|
+
if (web !== void 0 && !json(root, "package.json") && !json(root, "frontend/package.json")) fail("the original package.json or frontend/package.json for mygo.config.ts");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (spec.framework === "flutter") {
|
|
51
|
+
const text = file(root, "pubspec.yaml");
|
|
52
|
+
if (!text) fail("a Flutter pubspec.yaml");
|
|
53
|
+
const document = parseDocument(text);
|
|
54
|
+
if (document.errors.length) throw new Error(`Invalid pubspec.yaml: ${document.errors.map((error) => error.message).join("; ")}`);
|
|
55
|
+
const value = document.toJS();
|
|
56
|
+
if (!value || typeof value.name !== "string" || value.dependencies?.flutter?.sdk !== "flutter") fail("a named pubspec.yaml with dependencies.flutter.sdk: flutter");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (spec.framework === "android-java") {
|
|
60
|
+
if (spec.platform !== "android") fail("the Android platform");
|
|
61
|
+
const settings = file(root, "settings.gradle") ?? file(root, "settings.gradle.kts");
|
|
62
|
+
const application = directories(root, spec.framework).some((directory) => {
|
|
63
|
+
const prefix = directory ? `${directory}/` : "";
|
|
64
|
+
if ((file(root, `${prefix}build.gradle`) ?? file(root, `${prefix}build.gradle.kts`)) === void 0) return false;
|
|
65
|
+
const manifest = file(root, `${prefix}src/main/AndroidManifest.xml`)?.replace(/<!--[\s\S]*?-->/g, "");
|
|
66
|
+
return !!manifest && /<manifest\b/.test(manifest) && /<application\b/.test(manifest) && /<\/manifest\s*>/.test(manifest);
|
|
67
|
+
});
|
|
68
|
+
if (settings === void 0 || !application) fail("Gradle settings/build files and an application module src/main/AndroidManifest.xml");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (spec.framework === "swift") {
|
|
72
|
+
if (spec.platform !== "ios") fail("the iOS platform");
|
|
73
|
+
const swift = file(root, "Package.swift");
|
|
74
|
+
if (swift && /swift-tools-version\s*:/.test(swift) && /\bPackage\s*\(/.test(swift)) return;
|
|
75
|
+
for (const directory of directories(root, spec.framework)) if (directory.endsWith(".xcodeproj")) {
|
|
76
|
+
const project = file(root, `${directory}/project.pbxproj`);
|
|
77
|
+
if (project && /\bPBXProject\b/.test(project)) return;
|
|
78
|
+
}
|
|
79
|
+
fail("a Swift Package.swift or an Xcode .xcodeproj/project.pbxproj");
|
|
80
|
+
}
|
|
81
|
+
const pkg = json(root, "package.json");
|
|
82
|
+
const dependencies = {
|
|
83
|
+
...pkg?.dependencies,
|
|
84
|
+
...pkg?.devDependencies
|
|
85
|
+
};
|
|
86
|
+
if (spec.framework === "uni-app") {
|
|
87
|
+
const source = json(root, "src/manifest.json", true) ? "src/" : "";
|
|
88
|
+
const manifest = json(root, `${source}manifest.json`, true);
|
|
89
|
+
const pages = json(root, `${source}pages.json`, true);
|
|
90
|
+
if (!manifest || !Array.isArray(pages?.pages) || !pages.pages.length) fail("manifest.json and nonempty pages.json at the root or under src/");
|
|
91
|
+
const cli = typeof dependencies["@dcloudio/uni-app"] === "string" && !!dependencies["@dcloudio/uni-app"];
|
|
92
|
+
const classic = !!file(root, `${source}App.vue`) && !!(file(root, `${source}main.js`) ?? file(root, `${source}main.ts`));
|
|
93
|
+
if (!cli && !classic) fail("a uni-app CLI package.json or HBuilderX App.vue and main.js/main.ts");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const required = spec.framework === "react-native" ? "react-native" : "expo";
|
|
97
|
+
if (!pkg || typeof dependencies[required] !== "string" || !dependencies[required]) fail(`package.json declaring ${required}`);
|
|
98
|
+
}
|
|
99
|
+
function buildInstructions(spec) {
|
|
100
|
+
return `${`Build the ${spec.platform} application using the ${spec.framework} stack. ${spec.project?.sourceDir ? "Preserve the imported project and its existing native structure." : "Preserve any existing project, including a prepared predecessor; generate a real runnable project only when none exists in the designated project directory."}`}\n${{
|
|
101
|
+
flutter: "Use a Flutter pubspec.yaml, Dart sources and the selected platform runner. Resolve dependencies with flutter pub get; keep pubspec.lock. Do not modify the Flutter SDK.",
|
|
102
|
+
"android-java": "Use a Java Android application with Gradle settings/build files and AndroidManifest.xml. Keep the Gradle wrapper and dependency locks; use the existing application module rather than rewriting it.",
|
|
103
|
+
swift: "Use the existing Xcode project or Swift package, Swift sources and iOS application configuration. Keep Package.resolved and Podfile.lock when used. Do not replace an existing Xcode project with a package.",
|
|
104
|
+
"react-native": "Use package.json with react-native and the existing iOS/Android native projects. Keep the package-manager lockfile and native dependency locks. Do not migrate to Expo or regenerate native projects.",
|
|
105
|
+
expo: "Use package.json with expo and the existing Expo application configuration. Preserve managed or prebuilt structure; do not force expo prebuild or eject. Cloud builds require the owner-configured permission and packaging commands.",
|
|
106
|
+
"uni-app": "Preserve either the existing CLI package.json with @dcloudio/uni-app or the classic HBuilderX App.vue/main.js/main.ts project without requiring npm. Keep manifest.json/pages.json (root or src) and existing native structure; use the owner-configured HBuilderX/CLI Android/iOS packager, not a fabricated artifact.",
|
|
107
|
+
mygo: "Preserve native-UI Go-only projects with go.mod and mygo.json, or existing web projects with go.mod, mygo.config.ts and their original package.json (at the root or under frontend/) and package manager; keep exactly one MyGo config and require github.com/egoist/mygo in go.mod. Native projects need neither package.json nor Bun nor Xcode. Do not rewrite a web project or require Bun solely because its config is TypeScript; preserve its existing runtime and frontend scripts. Preserve the pinned MyGo tool/module version in go.mod; do not use @latest or install tools automatically. Prepare go.sum, any go.work/go.work.sum, frontend dependency locks and generated frontend bindings before proof, preserving the original frontend build process. Use Go vet/tests plus an owner-implemented real macOS desktop acceptance harness that launches and drives the actual app; go test or a headless view test alone is not desktop acceptance, and no iOS simulator is involved. Never generate a stub acceptance command. Packaging stays local-only through the frozen direct go tool mygo build command (or owner-configured pinned direct MyGo invocation), with -sign - and -skip-notarize; never upload, notarize or access signing keychains. Use the frozen output path, architecture and app/dmg target; do not replace the owner packaging profile."
|
|
108
|
+
}[spec.framework]}\nImplement the acceptance criteria, run the configured checks and runtime evidence commands, and leave packaging to the frozen owner command profile. Never replace checks with no-ops or claim unobserved SDK/build results.`;
|
|
109
|
+
}
|
|
110
|
+
function doctorCommands(framework, platform) {
|
|
111
|
+
const commands = [];
|
|
112
|
+
if (framework === "mygo") {
|
|
113
|
+
if (platform !== "macos") throw new Error("MyGo doctor supports only macOS");
|
|
114
|
+
return [
|
|
115
|
+
{
|
|
116
|
+
name: "Go",
|
|
117
|
+
argv: ["go", "version"]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "macOS code signing",
|
|
121
|
+
argv: ["/usr/bin/which", "codesign"]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "macOS disk images",
|
|
125
|
+
argv: ["hdiutil", "help"]
|
|
126
|
+
}
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
if (platform === "macos") throw new Error("macOS doctor supports only MyGo");
|
|
130
|
+
if (framework === "flutter") commands.push({
|
|
131
|
+
name: "Flutter",
|
|
132
|
+
argv: ["flutter", "--version"]
|
|
133
|
+
});
|
|
134
|
+
else if (framework === "swift") commands.push({
|
|
135
|
+
name: "Swift",
|
|
136
|
+
argv: ["swift", "--version"]
|
|
137
|
+
});
|
|
138
|
+
else if (framework === "react-native" || framework === "expo") commands.push({
|
|
139
|
+
name: "Node.js",
|
|
140
|
+
argv: ["node", "--version"]
|
|
141
|
+
}, {
|
|
142
|
+
name: "npm",
|
|
143
|
+
argv: ["npm", "--version"]
|
|
144
|
+
});
|
|
145
|
+
if (platform === "ios") commands.push({
|
|
146
|
+
name: "Xcode",
|
|
147
|
+
argv: ["xcodebuild", "-version"]
|
|
148
|
+
}, {
|
|
149
|
+
name: "Xcode tools",
|
|
150
|
+
argv: ["xcrun", "--version"]
|
|
151
|
+
});
|
|
152
|
+
else commands.push({
|
|
153
|
+
name: "Java",
|
|
154
|
+
argv: ["java", "-version"]
|
|
155
|
+
}, {
|
|
156
|
+
name: "Android platform tools",
|
|
157
|
+
argv: ["adb", "version"]
|
|
158
|
+
});
|
|
159
|
+
return commands;
|
|
160
|
+
}
|
|
161
|
+
export { assertProject, buildInstructions, doctorCommands };
|
|
162
|
+
|
|
163
|
+
//# sourceMappingURL=project-profiles.js.map
|