chati-dev 4.5.28 → 4.5.29
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/bin/chati.js +151 -123
- package/framework/agents/build/dev.md +4 -4
- package/framework/agents/deploy/devops.md +3 -3
- package/framework/agents/discover/brief.md +18 -6
- package/framework/agents/discover/brownfield-wu.md +3 -3
- package/framework/agents/discover/greenfield-wu.md +4 -4
- package/framework/agents/plan/architect.md +2 -2
- package/framework/agents/plan/detail.md +4 -4
- package/framework/agents/plan/phases.md +3 -3
- package/framework/agents/plan/tasks.md +47 -19
- package/framework/agents/plan/ux-brand-architect.md +1 -1
- package/framework/agents/plan/ux.md +26 -2
- package/framework/agents/quality/qa-implementation.md +8 -8
- package/framework/agents/quality/qa-planning.md +58 -4
- package/framework/agents/quality/qa-visual.md +4 -4
- package/framework/config.yaml +2 -2
- package/framework/constitution.md +12 -1
- package/framework/context/governance.md +2 -1
- package/framework/context/root.md +3 -1
- package/framework/domains/constitution.yaml +1 -1
- package/framework/domains/global.yaml +1 -1
- package/framework/domains/workflows/standard-flow.yaml +7 -8
- package/framework/hooks/advance-trigger.js +3 -2
- package/framework/hooks/brief-validator.js +1 -1
- package/framework/hooks/post-dev.js +1 -1
- package/framework/hooks/team-quality-gate.js +9 -2
- package/framework/i18n/en.yaml +1 -0
- package/framework/i18n/es.yaml +1 -0
- package/framework/i18n/fr.yaml +1 -0
- package/framework/i18n/pt.yaml +1 -0
- package/framework/manifest.json +83 -83
- package/framework/manifest.sig +1 -1
- package/framework/orchestrator/chati-router.js +14 -6
- package/framework/orchestrator/chati.md +61 -23
- package/framework/rules/human-writing-style.md +7 -0
- package/framework/schemas/session.schema.json +25 -2
- package/framework/schemas/task.schema.json +26 -0
- package/framework/tasks/orchestrator-handoff.md +10 -10
- package/framework/tasks/orchestrator-resume.md +3 -3
- package/framework/workflows/brownfield-fullstack.yaml +6 -3
- package/framework/workflows/brownfield-service.yaml +5 -3
- package/framework/workflows/brownfield-ui.yaml +5 -3
- package/framework/workflows/greenfield-fullstack.yaml +6 -3
- package/framework/workflows/standard-flow.yaml +8 -3
- package/node_modules/@chati/core/native/browser-proof-wire.h +29 -0
- package/node_modules/@chati/core/native/darwin-browser-listener.c +36 -0
- package/node_modules/@chati/core/native/darwin-browser-listener.h +32 -0
- package/node_modules/@chati/core/native/darwin-browser-proof.h +86 -0
- package/node_modules/@chati/core/native/darwin-code-identity.h +35 -0
- package/node_modules/@chati/core/native/darwin-launch-messages.h +37 -0
- package/node_modules/@chati/core/native/darwin-launcher.c +312 -0
- package/node_modules/@chati/core/native/descriptor-fs.c +407 -0
- package/node_modules/@chati/core/native/execution-admission.h +59 -0
- package/node_modules/@chati/core/native/execution-gate.c +41 -0
- package/node_modules/@chati/core/native/linux-process.c +41 -0
- package/node_modules/@chati/core/native/prebuilds/darwin-arm64/darwin-launcher +0 -0
- package/node_modules/@chati/core/native/prebuilds/darwin-arm64/descriptor-fs.node +0 -0
- package/node_modules/@chati/core/native/prebuilds/darwin-arm64/execution-gate +0 -0
- package/node_modules/@chati/core/native/prebuilds/linux-x64/descriptor-fs.node +0 -0
- package/node_modules/@chati/core/native/prebuilds/linux-x64/execution-gate +0 -0
- package/node_modules/@chati/core/native/process-info-pipe.c +91 -0
- package/node_modules/@chati/core/package.json +2 -1
- package/node_modules/@chati/core/scripts/build-native.js +36 -0
- package/node_modules/@chati/core/src/darwin-browser-proof.js +87 -0
- package/node_modules/@chati/core/src/darwin-host-boot.js +58 -0
- package/node_modules/@chati/core/src/darwin-process.js +79 -0
- package/node_modules/@chati/core/src/darwin-supervisor-channels.js +82 -0
- package/node_modules/@chati/core/src/darwin-terminal-receipt.js +111 -0
- package/node_modules/@chati/core/src/descriptor-fs.js +222 -0
- package/node_modules/@chati/core/src/execution-gate.js +8 -0
- package/node_modules/@chati/core/src/host-boot-marker.js +45 -0
- package/node_modules/@chati/core/src/index.js +18 -0
- package/node_modules/@chati/core/src/linux-host-boot.js +218 -0
- package/node_modules/@chati/core/src/linux-process.js +133 -0
- package/node_modules/@chati/planning/src/browser-verification-plan.js +71 -0
- package/node_modules/@chati/planning/src/index.js +33 -0
- package/node_modules/@chati/planning/src/planning-material.js +64 -0
- package/node_modules/@chati/planning/src/verification-plan.js +57 -0
- package/node_modules/@chati/rail/package.json +1 -0
- package/node_modules/@chati/rail/src/execution-restoration.js +92 -0
- package/node_modules/@chati/rail/src/index.js +812 -167
- package/node_modules/@chati/rail/src/provider-processing.js +118 -0
- package/node_modules/@chati/rail/src/terminal-containment.js +23 -0
- package/node_modules/@chati/release-lane/src/index.js +40 -43
- package/node_modules/@chati/review-council/src/index.js +223 -0
- package/node_modules/@chati/tracking-clickup/src/index.js +67 -58
- package/package-artifact-manifest.json +1 -1
- package/package-artifact-manifest.sig +1 -1
- package/package.json +3 -2
- package/scripts/verify-real-harness-e2e.js +20 -8
- package/src/installer/core.js +147 -107
- package/src/installer/package-artifact.js +7 -0
- package/src/installer/preflight.js +13 -0
- package/src/installer/provider-overlay.js +1 -2
- package/src/installer/templates.js +58 -0
- package/src/installer-v2/catalog-client.js +31 -1
- package/src/installer-v2/clickup-connection-storage.js +92 -0
- package/src/installer-v2/clickup-mcp-client.js +155 -0
- package/src/installer-v2/clickup-oauth-loopback.js +96 -0
- package/src/installer-v2/clickup-oauth.js +233 -0
- package/src/installer-v2/index.js +141 -15
- package/src/installer-v2/installation-authority.js +98 -4
- package/src/installer-v2/provider-executable.js +117 -12
- package/src/installer-v2/wizard-installation.js +11 -0
- package/src/orchestrator/agent-selector.js +39 -1
- package/src/orchestrator/browser-artifact-store.js +131 -0
- package/src/orchestrator/cli.js +762 -205
- package/src/orchestrator/clickup-mcp-observer.js +226 -0
- package/src/orchestrator/clickup-runtime.js +45 -24
- package/src/orchestrator/council-gate-runtime.js +903 -0
- package/src/orchestrator/evidence-authority-enrollment.js +75 -0
- package/src/orchestrator/evidence-authority-request.js +46 -0
- package/src/orchestrator/handoff-engine.js +4 -11
- package/src/orchestrator/pipeline-manager.js +42 -4
- package/src/orchestrator/planning-contract-revision.js +711 -0
- package/src/orchestrator/planning-reconciliation.js +302 -0
- package/src/orchestrator/planning-runtime.js +476 -29
- package/src/orchestrator/rail-adjudication-evidence.js +15 -24
- package/src/orchestrator/rail-human-effort-authority.js +46 -0
- package/src/orchestrator/rail-human-effort.js +149 -0
- package/src/orchestrator/rail-runtime.js +291 -47
- package/src/orchestrator/rail-verification.js +278 -0
- package/src/orchestrator/review-runtime.js +37 -2
- package/src/orchestrator/runtime-installation-v2.js +143 -24
- package/src/orchestrator/session-manager.js +1457 -29
- package/src/tasks/handoff.js +57 -33
- package/src/terminal/adapters/claude-adapter.js +3 -0
- package/src/terminal/adapters/codex-adapter.js +13 -0
- package/src/terminal/adapters/grok-adapter.js +1 -0
- package/src/terminal/agent-attempt-budget.js +35 -0
- package/src/terminal/browser-endpoint-proxy.js +174 -0
- package/src/terminal/browser-observer-driver.js +94 -0
- package/src/terminal/browser-observer-supervisor.js +73 -0
- package/src/terminal/browser-observer.js +95 -0
- package/src/terminal/browser-process-facts.js +75 -0
- package/src/terminal/browser-runtime.js +131 -0
- package/src/terminal/darwin-authority-policy.js +213 -0
- package/src/terminal/darwin-browser-application.js +116 -0
- package/src/terminal/darwin-browser-observer.js +57 -0
- package/src/terminal/darwin-execution.js +185 -0
- package/src/terminal/executable-snapshot.js +202 -0
- package/src/terminal/handoff-parser.js +48 -2
- package/src/terminal/isolation.js +14 -13
- package/src/terminal/prompt-builder.js +176 -15
- package/src/terminal/provider-preflight.js +47 -27
- package/src/terminal/rail-execution-worktree.js +22 -6
- package/src/terminal/rail-process-observer.js +10 -0
- package/src/terminal/rail-prompts.js +21 -3
- package/src/terminal/rail-readonly-workspace.js +54 -5
- package/src/terminal/rail-worktree-checkpoint.js +315 -0
- package/src/terminal/rail-worktree-restoration.js +485 -0
- package/src/terminal/run-agent.js +164 -42
- package/src/terminal/run-council-gate.js +338 -0
- package/src/terminal/run-planning-reconciliation.js +49 -0
- package/src/terminal/run-rail-adjudication.js +29 -7
- package/src/terminal/run-rail-recovery.js +97 -0
- package/src/terminal/run-rail-review.js +36 -15
- package/src/terminal/run-rail-rework.js +26 -15
- package/src/terminal/run-rail-task.js +22 -9
- package/src/terminal/runtime-native-helper.js +85 -0
- package/src/terminal/spawner.js +899 -142
- package/src/terminal/verification-process-facts.js +49 -0
- package/src/upgrade/entrypoints.js +68 -0
- package/src/upgrade/generation.js +194 -0
- package/src/upgrade/maintenance.js +79 -0
- package/src/upgrade/paths.js +81 -0
- package/src/upgrade/upgrade.js +267 -0
- package/src/utils/execution-role.js +15 -0
- package/src/utils/handoff-paths.js +59 -0
- package/src/wizard/i18n.js +1 -0
- package/src/wizard/index.js +17 -5
- package/src/wizard/questions.js +24 -12
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/* macOS native-image launch boundary. No model routing or receipt signing here.
|
|
2
|
+
* ABI 1: inspect PATH | launch CDHASH PATH KEEP_PROMPT -- PROVIDER_ARGS...
|
|
3
|
+
* Admission ABI 1: launch-admitted CDHASH PATH KEEP_PROMPT NONCE -- ARGS...
|
|
4
|
+
* fd 5: one-shot GO, fd 6: READY output, fd 7: completion output,
|
|
5
|
+
* fd 8: separate owner-lifetime input, kept open without data after GO.
|
|
6
|
+
* launch inherits fd 4 as project cwd and optionally fd 3 as a private prompt.
|
|
7
|
+
* The provider receives only stdio and that explicitly allowed prompt descriptor.
|
|
8
|
+
* A separate reaper provides best-effort process-group cleanup on helper death.
|
|
9
|
+
* Detached descendants are NOT a PID namespace and are NOT claimed terminated.
|
|
10
|
+
*/
|
|
11
|
+
#include <CoreFoundation/CoreFoundation.h>
|
|
12
|
+
#include <Security/Security.h>
|
|
13
|
+
#include <errno.h>
|
|
14
|
+
#include <poll.h>
|
|
15
|
+
#include <signal.h>
|
|
16
|
+
#include <spawn.h>
|
|
17
|
+
#include <stdio.h>
|
|
18
|
+
#include <stdlib.h>
|
|
19
|
+
#include <string.h>
|
|
20
|
+
#include <sys/stat.h>
|
|
21
|
+
#include <sys/wait.h>
|
|
22
|
+
#include <time.h>
|
|
23
|
+
#include <unistd.h>
|
|
24
|
+
#include "execution-admission.h"
|
|
25
|
+
#include "darwin-launch-messages.h"
|
|
26
|
+
#include "darwin-browser-proof.h"
|
|
27
|
+
#include "darwin-code-identity.h"
|
|
28
|
+
#include "darwin-browser-listener.h"
|
|
29
|
+
|
|
30
|
+
static volatile sig_atomic_t provider_pid = -1;
|
|
31
|
+
static volatile sig_atomic_t received_signal = 0;
|
|
32
|
+
static void forward_signal(int number) {
|
|
33
|
+
received_signal = number;
|
|
34
|
+
if (provider_pid > 0) kill(-(pid_t)provider_pid, number == SIGALRM ? SIGKILL : number);
|
|
35
|
+
if (number == SIGALRM) _exit(124);
|
|
36
|
+
}
|
|
37
|
+
static int fail(const char *phase, int code) {
|
|
38
|
+
fprintf(stderr, "CHATI_DARWIN_LAUNCH:%s:%d\n", phase, code);
|
|
39
|
+
return 125;
|
|
40
|
+
}
|
|
41
|
+
static int read_byte(int fd, int milliseconds, char *byte) {
|
|
42
|
+
struct pollfd poller = { .fd = fd, .events = POLLIN | POLLHUP };
|
|
43
|
+
int result;
|
|
44
|
+
do { result = poll(&poller, 1, milliseconds); } while (result < 0 && errno == EINTR);
|
|
45
|
+
if (result <= 0) return -1;
|
|
46
|
+
ssize_t count;
|
|
47
|
+
do { count = read(fd, byte, 1); } while (count < 0 && errno == EINTR);
|
|
48
|
+
return count == 1 ? 0 : -1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static int control_output(int fd) {
|
|
52
|
+
struct stat state;
|
|
53
|
+
return fstat(fd, &state) == 0 && S_ISFIFO(state.st_mode);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static int control_message(int fd, const char *message, size_t length) {
|
|
57
|
+
// One small message per pipe, never mixed with provider stdout/stderr.
|
|
58
|
+
ssize_t count;
|
|
59
|
+
do { count = write(fd, message, length); } while (count < 0 && errno == EINTR);
|
|
60
|
+
int closed = close(fd);
|
|
61
|
+
return count == (ssize_t)length && closed == 0 ? 0 : -1;
|
|
62
|
+
}
|
|
63
|
+
static int inspect(const char *path) {
|
|
64
|
+
struct stat stat;
|
|
65
|
+
if (lstat(path, &stat) < 0 || !S_ISREG(stat.st_mode) || stat.st_nlink != 1
|
|
66
|
+
|| stat.st_size < 1 || stat.st_size > 1024LL * 1024 * 1024) return fail("image_shape", errno);
|
|
67
|
+
CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL,
|
|
68
|
+
(const UInt8 *)path, (CFIndex)strlen(path), false);
|
|
69
|
+
if (!url) return fail("image_url", 0);
|
|
70
|
+
SecStaticCodeRef code = NULL;
|
|
71
|
+
CFDictionaryRef information = NULL;
|
|
72
|
+
OSStatus status = SecStaticCodeCreateWithPath(url, kSecCSDefaultFlags, &code);
|
|
73
|
+
CFRelease(url);
|
|
74
|
+
if (status == errSecSuccess) status = SecStaticCodeCheckValidity(code, kSecCSDefaultFlags, NULL);
|
|
75
|
+
if (status == errSecSuccess) status = SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &information);
|
|
76
|
+
if (status != errSecSuccess) {
|
|
77
|
+
if (code) CFRelease(code);
|
|
78
|
+
return fail("static_identity", (int)status);
|
|
79
|
+
}
|
|
80
|
+
CFDataRef hash = CFDictionaryGetValue(information, kSecCodeInfoUnique);
|
|
81
|
+
if (!hash || CFGetTypeID(hash) != CFDataGetTypeID() || CFDataGetLength(hash) != 20) {
|
|
82
|
+
CFRelease(information); CFRelease(code);
|
|
83
|
+
return fail("cdhash_shape", 0);
|
|
84
|
+
}
|
|
85
|
+
const UInt8 *bytes = CFDataGetBytePtr(hash);
|
|
86
|
+
char hex[41];
|
|
87
|
+
for (int i = 0; i < 20; i++) snprintf(hex + i * 2, 3, "%02x", bytes[i]);
|
|
88
|
+
printf("{\"schema_version\":1,\"cdhash\":\"%s\"}\n", hex);
|
|
89
|
+
CFRelease(information); CFRelease(code);
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
static OSStatus check_child(pid_t child, const char *hex) {
|
|
93
|
+
// inspect() retains full signature validation before the sandbox is applied.
|
|
94
|
+
// Here the kernel must identify that exact sealed image while it is suspended.
|
|
95
|
+
// Repeating CMS/trust evaluation inside the script's sandbox requires host
|
|
96
|
+
// services that the script must not gain access to.
|
|
97
|
+
return chati_darwin_code_identity(child, hex);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static pid_t start_reaper(pid_t child, int owner_fd, int *watch_writer, int *ack_reader) {
|
|
101
|
+
int watch[2], ack[2];
|
|
102
|
+
if (pipe(watch) < 0) return -1;
|
|
103
|
+
if (pipe(ack) < 0) { close(watch[0]); close(watch[1]); return -1; }
|
|
104
|
+
pid_t reaper = fork();
|
|
105
|
+
if (reaper < 0) {
|
|
106
|
+
close(watch[0]); close(watch[1]); close(ack[0]); close(ack[1]); return -1;
|
|
107
|
+
}
|
|
108
|
+
if (reaper == 0) {
|
|
109
|
+
// Only async-signal-safe calls after fork. No Security/CF or user code here.
|
|
110
|
+
if (setsid() < 0) _exit(125);
|
|
111
|
+
signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGHUP, SIG_DFL);
|
|
112
|
+
signal(SIGALRM, SIG_DFL); signal(SIGPIPE, SIG_IGN);
|
|
113
|
+
int limit = getdtablesize();
|
|
114
|
+
for (int fd = 0; fd < limit; fd++) if (fd != watch[0] && fd != ack[1] && fd != owner_fd) close(fd);
|
|
115
|
+
if (write(ack[1], "R", 1) != 1) _exit(125);
|
|
116
|
+
struct pollfd inputs[2] = {
|
|
117
|
+
{ .fd = watch[0], .events = POLLIN }, { .fd = owner_fd, .events = POLLIN }
|
|
118
|
+
};
|
|
119
|
+
int observed;
|
|
120
|
+
do { observed = poll(inputs, 2, -1); } while (observed < 0 && errno == EINTR);
|
|
121
|
+
// Data, EOF, a broken watcher or helper death all end this group's life.
|
|
122
|
+
// Closing the one-shot GO channel does not touch either lifetime input.
|
|
123
|
+
int stopped = kill(-child, SIGKILL) == 0 || errno == ESRCH;
|
|
124
|
+
unsigned char signal_error = (unsigned char)errno;
|
|
125
|
+
(void)write(ack[1], stopped ? "K" : "E", 1);
|
|
126
|
+
if (!stopped) (void)write(ack[1], &signal_error, 1);
|
|
127
|
+
close(watch[0]); close(ack[1]);
|
|
128
|
+
// XNU killpg excludes zombies and may return EPERM for a zombie-only group.
|
|
129
|
+
// Report the actual error; the parent must still prove ESRCH after reaping.
|
|
130
|
+
_exit(stopped || signal_error == EPERM ? 0 : 125);
|
|
131
|
+
}
|
|
132
|
+
close(watch[0]); close(ack[1]);
|
|
133
|
+
*watch_writer = watch[1]; *ack_reader = ack[0];
|
|
134
|
+
return reaper;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
static int launch(int argc, char **argv, char **environment, int admitted, int browser) {
|
|
138
|
+
int separator = browser ? 7 : admitted ? 6 : 5;
|
|
139
|
+
if (argc <= separator || strlen(argv[2]) != 40 || strspn(argv[2], "0123456789abcdef") != 40
|
|
140
|
+
|| argv[3][0] != '/' || (strcmp(argv[4], "0") && strcmp(argv[4], "1"))
|
|
141
|
+
|| strcmp(argv[separator], "--")) return fail("arguments", EINVAL);
|
|
142
|
+
if (admitted && (strlen(argv[5]) != 64 || strspn(argv[5], "0123456789abcdef") != 64
|
|
143
|
+
|| !chati_admission_channel(5) || !control_output(6) || !control_output(7)
|
|
144
|
+
|| !chati_admission_channel(8))) return fail("admission_channels", EINVAL);
|
|
145
|
+
if (browser && (!chati_admission_channel(9) || !control_output(10) || strcmp(argv[4], "0"))) {
|
|
146
|
+
return fail("browser_proof_channels", EINVAL);
|
|
147
|
+
}
|
|
148
|
+
unsigned int browser_port = 0;
|
|
149
|
+
uint64_t listener_generation = 0, observed_generation = 0;
|
|
150
|
+
if (browser) {
|
|
151
|
+
if (!argv[6][0] || strlen(argv[6]) > 5 || strspn(argv[6], "0123456789") != strlen(argv[6])) return fail("browser_listener_port", EINVAL);
|
|
152
|
+
browser_port = (unsigned int)strtoul(argv[6], NULL, 10);
|
|
153
|
+
if (!chati_darwin_listener(getpid(), 11, browser_port, &listener_generation)) return fail("browser_listener", EINVAL);
|
|
154
|
+
}
|
|
155
|
+
struct stat directory;
|
|
156
|
+
if (fstat(4, &directory) < 0 || !S_ISDIR(directory.st_mode)) return fail("project_descriptor", errno);
|
|
157
|
+
int keep_prompt = !strcmp(argv[4], "1");
|
|
158
|
+
if (keep_prompt) {
|
|
159
|
+
struct stat prompt;
|
|
160
|
+
if (fstat(3, &prompt) < 0 || !S_ISREG(prompt.st_mode)) return fail("prompt_descriptor", errno);
|
|
161
|
+
}
|
|
162
|
+
signal(SIGTERM, forward_signal); signal(SIGINT, forward_signal);
|
|
163
|
+
signal(SIGHUP, forward_signal); signal(SIGALRM, forward_signal); signal(SIGPIPE, SIG_IGN);
|
|
164
|
+
// Pre-SIGCONT budget: the reaper-ready wait (5s) and the durable-dispatch
|
|
165
|
+
// admission wait (5s) each have their own timeout. The watchdog must sit
|
|
166
|
+
// above their sum plus spawn/identity overhead, never race them.
|
|
167
|
+
alarm(20);
|
|
168
|
+
posix_spawnattr_t attributes;
|
|
169
|
+
posix_spawn_file_actions_t actions;
|
|
170
|
+
int status = posix_spawnattr_init(&attributes);
|
|
171
|
+
if (status) return fail("spawn_attributes", status);
|
|
172
|
+
status = posix_spawn_file_actions_init(&actions);
|
|
173
|
+
if (status) { posix_spawnattr_destroy(&attributes); return fail("spawn_actions", status); }
|
|
174
|
+
status = posix_spawnattr_setflags(&attributes,
|
|
175
|
+
POSIX_SPAWN_START_SUSPENDED | POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_CLOEXEC_DEFAULT);
|
|
176
|
+
if (!status) status = posix_spawnattr_setpgroup(&attributes, 0);
|
|
177
|
+
// Available since macOS 10.15, also present on the actual tested ARM64 host.
|
|
178
|
+
#pragma clang diagnostic push
|
|
179
|
+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
180
|
+
if (!status) status = posix_spawn_file_actions_addfchdir_np(&actions, 4);
|
|
181
|
+
#pragma clang diagnostic pop
|
|
182
|
+
for (int fd = 0; fd <= (keep_prompt ? 3 : 2) && !status; fd++) {
|
|
183
|
+
status = posix_spawn_file_actions_addinherit_np(&actions, fd);
|
|
184
|
+
}
|
|
185
|
+
if (browser && !status) status = posix_spawn_file_actions_addinherit_np(&actions, 11);
|
|
186
|
+
char **child_args = calloc((size_t)(argc - separator + 1), sizeof(char *));
|
|
187
|
+
if (!child_args) status = ENOMEM;
|
|
188
|
+
if (!status) {
|
|
189
|
+
child_args[0] = argv[3];
|
|
190
|
+
for (int i = separator + 1; i < argc; i++) child_args[i - separator] = argv[i];
|
|
191
|
+
}
|
|
192
|
+
pid_t child = -1;
|
|
193
|
+
if (!status) status = posix_spawn(&child, argv[3], &actions, &attributes, child_args, environment);
|
|
194
|
+
free(child_args);
|
|
195
|
+
posix_spawn_file_actions_destroy(&actions); posix_spawnattr_destroy(&attributes);
|
|
196
|
+
if (status) return fail("spawn", status);
|
|
197
|
+
provider_pid = child;
|
|
198
|
+
close(3); close(4);
|
|
199
|
+
int watch = -1, ack = -1;
|
|
200
|
+
pid_t reaper = start_reaper(child, admitted ? 8 : -1, &watch, &ack);
|
|
201
|
+
char ready = 0;
|
|
202
|
+
if (reaper < 0 || read_byte(ack, 5000, &ready) < 0 || ready != 'R') {
|
|
203
|
+
if (browser) { close(9); close(10); close(11); }
|
|
204
|
+
kill(-child, SIGKILL); waitpid(child, NULL, 0);
|
|
205
|
+
if (watch >= 0) close(watch);
|
|
206
|
+
if (ack >= 0) close(ack);
|
|
207
|
+
return fail("reaper_ready", 0);
|
|
208
|
+
}
|
|
209
|
+
OSStatus identity = check_child(child, argv[2]);
|
|
210
|
+
int continued = identity == errSecSuccess && !received_signal;
|
|
211
|
+
const char *admission_error = NULL;
|
|
212
|
+
if (browser && (!chati_darwin_listener(child, 11, browser_port, &observed_generation)
|
|
213
|
+
|| observed_generation != listener_generation)) {
|
|
214
|
+
continued = 0; admission_error = "browser_listener_child";
|
|
215
|
+
}
|
|
216
|
+
long long started = chati_admission_monotonic_ms();
|
|
217
|
+
if (admitted) {
|
|
218
|
+
if (continued) {
|
|
219
|
+
char message[512];
|
|
220
|
+
int length = chati_format_darwin_ready(message, sizeof(message),
|
|
221
|
+
argv[5], getpid(), child, argv[2]);
|
|
222
|
+
if (length < 0 || (size_t)length >= sizeof(message)) {
|
|
223
|
+
close(6); admission_error = "ready_message";
|
|
224
|
+
} else if (control_message(6, message, (size_t)length) < 0) admission_error = "ready_channel";
|
|
225
|
+
if (!admission_error) admission_error = chati_wait_for_admission(5, 8, 5000);
|
|
226
|
+
// Recheck the suspended image after the durable-dispatch/GO pause.
|
|
227
|
+
if (!admission_error) identity = check_child(child, argv[2]);
|
|
228
|
+
if (browser && !admission_error
|
|
229
|
+
&& (!chati_darwin_listener(getpid(), 11, browser_port, &observed_generation)
|
|
230
|
+
|| observed_generation != listener_generation
|
|
231
|
+
|| !chati_darwin_listener(child, 11, browser_port, &observed_generation)
|
|
232
|
+
|| observed_generation != listener_generation)) admission_error = "browser_listener_changed";
|
|
233
|
+
} else close(6);
|
|
234
|
+
if (close(5) < 0 && !admission_error) admission_error = "go_channel_close";
|
|
235
|
+
close(8); // The already-ready separate reaper owns the lifetime reader.
|
|
236
|
+
started = chati_admission_monotonic_ms();
|
|
237
|
+
if (started < 0 && !admission_error) admission_error = "clock";
|
|
238
|
+
continued = continued && !admission_error && identity == errSecSuccess && !received_signal;
|
|
239
|
+
}
|
|
240
|
+
if (browser) close(11); // Only the admitted child and trusted caller retain the listener.
|
|
241
|
+
if (kill(child, continued ? SIGCONT : SIGKILL) < 0) continued = 0;
|
|
242
|
+
alarm(0);
|
|
243
|
+
siginfo_t exited;
|
|
244
|
+
memset(&exited, 0, sizeof(exited));
|
|
245
|
+
int waited;
|
|
246
|
+
// Keep the primary child unreaped while the reaper signals its process group.
|
|
247
|
+
if (browser && continued) waited = chati_browser_wait(child, argv[5], &exited);
|
|
248
|
+
else do { waited = waitid(P_PID, (id_t)child, &exited, WEXITED | WNOWAIT); } while (waited < 0 && errno == EINTR);
|
|
249
|
+
int wait_error = waited < 0 ? errno : 0;
|
|
250
|
+
// Irrevocably revoke ownership service before reaper signalling or reap.
|
|
251
|
+
if (browser) { close(9); close(10); }
|
|
252
|
+
close(watch);
|
|
253
|
+
char stopped = 0;
|
|
254
|
+
int acknowledged = read_byte(ack, 5000, &stopped) == 0 && stopped == 'K';
|
|
255
|
+
char signal_error = 0;
|
|
256
|
+
if (stopped == 'E') (void)read_byte(ack, 1000, &signal_error);
|
|
257
|
+
close(ack);
|
|
258
|
+
int child_status = 0, reaper_status = 0;
|
|
259
|
+
pid_t reaped;
|
|
260
|
+
do { reaped = waitpid(child, &child_status, 0); } while (reaped < 0 && errno == EINTR);
|
|
261
|
+
int reap_error = reaped < 0 ? errno : 0;
|
|
262
|
+
do { status = waitpid(reaper, &reaper_status, 0); } while (status < 0 && errno == EINTR);
|
|
263
|
+
provider_pid = -1;
|
|
264
|
+
if (admission_error) return fail(admission_error, 0);
|
|
265
|
+
if (!continued) return fail("dynamic_identity", (int)identity);
|
|
266
|
+
if (waited < 0) return fail("waitid", wait_error);
|
|
267
|
+
if (reaped != child) return fail("reap_provider", reap_error);
|
|
268
|
+
if (!acknowledged && !(stopped == 'E' && (unsigned char)signal_error == EPERM)) {
|
|
269
|
+
return fail("reaper_ack", signal_error ? (unsigned char)signal_error : (int)stopped);
|
|
270
|
+
}
|
|
271
|
+
if (status != reaper || !WIFEXITED(reaper_status) || WEXITSTATUS(reaper_status) != 0) return fail("reap_reaper", reaper_status);
|
|
272
|
+
int group_empty = 0;
|
|
273
|
+
for (int attempt = 0; attempt < 500; attempt++) {
|
|
274
|
+
if (kill(-child, 0) < 0) {
|
|
275
|
+
if (errno == ESRCH) { group_empty = 1; break; }
|
|
276
|
+
if (errno != EPERM) break;
|
|
277
|
+
// A transient zombie-only group may still exist until launchd reaps it.
|
|
278
|
+
// EPERM is not proof of emptiness: keep waiting for actual ESRCH.
|
|
279
|
+
}
|
|
280
|
+
struct timespec delay = { .tv_sec = 0, .tv_nsec = 10000000 };
|
|
281
|
+
nanosleep(&delay, NULL);
|
|
282
|
+
}
|
|
283
|
+
if (!group_empty) return fail("process_group_not_empty", 0);
|
|
284
|
+
if (admitted) {
|
|
285
|
+
long long finished = chati_admission_monotonic_ms();
|
|
286
|
+
if (finished < started) return fail("completion_clock", 0);
|
|
287
|
+
char message[768];
|
|
288
|
+
const struct chati_darwin_completion result = {
|
|
289
|
+
.elapsed_ms = finished - started,
|
|
290
|
+
.exit_code = WIFEXITED(child_status) ? WEXITSTATUS(child_status) : -1,
|
|
291
|
+
.signal = WIFSIGNALED(child_status) ? WTERMSIG(child_status) : 0,
|
|
292
|
+
.supervisor_signal = received_signal,
|
|
293
|
+
.reaper_acknowledged = acknowledged,
|
|
294
|
+
};
|
|
295
|
+
int length = chati_format_darwin_completion(message, sizeof(message),
|
|
296
|
+
argv[5], getpid(), child, argv[2], &result);
|
|
297
|
+
if (length < 0 || (size_t)length >= sizeof(message)) return fail("completion_message", 0);
|
|
298
|
+
if (control_message(7, message, (size_t)length) < 0) return fail("completion_channel", 0);
|
|
299
|
+
}
|
|
300
|
+
if (received_signal) return 128 + received_signal;
|
|
301
|
+
if (WIFSIGNALED(child_status)) return 128 + WTERMSIG(child_status);
|
|
302
|
+
return WIFEXITED(child_status) ? WEXITSTATUS(child_status) : fail("child_status", 0);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
int main(int argc, char **argv, char **environment) {
|
|
306
|
+
if (argc == 3 && !strcmp(argv[1], "inspect")) return inspect(argv[2]);
|
|
307
|
+
if (argc >= 6 && !strcmp(argv[1], "launch")) return launch(argc, argv, environment, 0, 0);
|
|
308
|
+
if (argc >= 7 && !strcmp(argv[1], "launch-admitted")) return launch(argc, argv, environment, 1, 0);
|
|
309
|
+
// No legacy self-binding browser launch: stale callers fail closed.
|
|
310
|
+
if (argc >= 8 && !strcmp(argv[1], "launch-browser-prebound-admitted")) return launch(argc, argv, environment, 1, 1);
|
|
311
|
+
return fail("mode", EINVAL);
|
|
312
|
+
}
|