baa-conductor

git clone 

commit
088343b
parent
14ef5c1
author
im_wower
date
2026-03-22 01:25:16 +0800 CST
Merge remote-tracking branch 'origin/feat/T-020-status-host' into integration/fourth-wave-20260322
7 files changed,  +648, -9
M apps/status-api/package.json
+4, -1
 1@@ -9,6 +9,9 @@
 2   },
 3   "scripts": {
 4     "build": "pnpm exec tsc -p tsconfig.json && BAA_DIST_DIR=apps/status-api/dist BAA_DIST_ENTRY=apps/status-api/src/index.js BAA_FIX_RELATIVE_EXTENSIONS=true pnpm -C ../.. run build:runtime-postprocess",
 5-    "typecheck": "pnpm exec tsc --noEmit -p tsconfig.json"
 6+    "typecheck": "pnpm exec tsc --noEmit -p tsconfig.json",
 7+    "serve": "pnpm run build && node dist/index.js",
 8+    "start": "node dist/index.js",
 9+    "smoke": "pnpm run build && node dist/index.js smoke"
10   }
11 }
A apps/status-api/src/cli.ts
+180, -0
  1@@ -0,0 +1,180 @@
  2+import {
  3+  getDefaultStatusApiHost,
  4+  getDefaultStatusApiPort,
  5+  runStatusApiSmokeCheck,
  6+  startStatusApiServer,
  7+  type StatusApiEnvironment
  8+} from "./host.js";
  9+
 10+type StatusApiCliAction = "help" | "serve" | "smoke";
 11+
 12+export interface StatusApiTextWriter {
 13+  write(chunk: string): unknown;
 14+}
 15+
 16+export interface RunStatusApiCliOptions {
 17+  argv?: readonly string[];
 18+  env?: StatusApiEnvironment;
 19+  stderr?: StatusApiTextWriter;
 20+  stdout?: StatusApiTextWriter;
 21+}
 22+
 23+interface StatusApiCliCommand {
 24+  action: StatusApiCliAction;
 25+  host?: string;
 26+  port?: number;
 27+}
 28+
 29+export async function runStatusApiCli(options: RunStatusApiCliOptions = {}): Promise<number> {
 30+  const stdout = toTextWriter(options.stdout, console.log);
 31+  const stderr = toTextWriter(options.stderr, console.error);
 32+  const command = parseStatusApiCliCommand(options.argv ?? process?.argv ?? [], options.env ?? process?.env ?? {});
 33+
 34+  switch (command.action) {
 35+    case "help":
 36+      stdout.write(renderStatusApiCliHelp());
 37+      return 0;
 38+
 39+    case "serve": {
 40+      const server = await startStatusApiServer({
 41+        host: command.host,
 42+        port: command.port
 43+      });
 44+      const baseUrl =
 45+        server.getBaseUrl() ??
 46+        `http://${command.host ?? getDefaultStatusApiHost()}:${command.port ?? getDefaultStatusApiPort()}`;
 47+
 48+      stdout.write(`Status API listening on ${baseUrl}\n`);
 49+
 50+      for (const route of server.describeSurface()) {
 51+        stdout.write(`- ${route}\n`);
 52+      }
 53+
 54+      return 0;
 55+    }
 56+
 57+    case "smoke": {
 58+      const result = await runStatusApiSmokeCheck({
 59+        host: command.host ?? getDefaultStatusApiHost(),
 60+        port: command.port ?? 0
 61+      });
 62+
 63+      stdout.write(`Smoke OK on ${result.baseUrl}\n`);
 64+
 65+      for (const check of result.checks) {
 66+        stdout.write(`- ${check.status} ${check.path} ${check.detail}\n`);
 67+      }
 68+
 69+      return 0;
 70+    }
 71+  }
 72+}
 73+
 74+function parseStatusApiCliCommand(argv: readonly string[], env: StatusApiEnvironment): StatusApiCliCommand {
 75+  const tokens = argv.slice(2);
 76+  let action: StatusApiCliAction = "serve";
 77+  let actionSet = false;
 78+  let host: string | undefined;
 79+  let port: number | undefined;
 80+
 81+  for (let index = 0; index < tokens.length; index += 1) {
 82+    const token = tokens[index];
 83+
 84+    if (token == null) {
 85+      continue;
 86+    }
 87+
 88+    if (token === "--help" || token === "-h" || token === "help") {
 89+      return { action: "help" };
 90+    }
 91+
 92+    if (token === "--host") {
 93+      host = readCliValue(tokens, index, "--host");
 94+      index += 1;
 95+      continue;
 96+    }
 97+
 98+    if (token === "--port") {
 99+      port = parseCliPort(readCliValue(tokens, index, "--port"));
100+      index += 1;
101+      continue;
102+    }
103+
104+    if (token.startsWith("--")) {
105+      throw new Error(`Unknown status-api flag "${token}".`);
106+    }
107+
108+    if (actionSet) {
109+      throw new Error(`Unexpected extra status-api argument "${token}".`);
110+    }
111+
112+    if (!isStatusApiCliAction(token)) {
113+      throw new Error(`Unknown status-api action "${token}".`);
114+    }
115+
116+    action = token;
117+    actionSet = true;
118+  }
119+
120+  return {
121+    action,
122+    host: host ?? env.BAA_STATUS_API_HOST ?? env.HOST,
123+    port: port
124+  };
125+}
126+
127+function isStatusApiCliAction(value: string): value is Exclude<StatusApiCliAction, "help"> {
128+  return value === "serve" || value === "smoke";
129+}
130+
131+function readCliValue(tokens: readonly string[], index: number, flag: string): string {
132+  const value = tokens[index + 1];
133+
134+  if (value == null || value.startsWith("--")) {
135+    throw new Error(`Missing value for ${flag}.`);
136+  }
137+
138+  return value;
139+}
140+
141+function parseCliPort(value: string): number {
142+  const port = Number(value);
143+
144+  if (!Number.isInteger(port) || port < 0 || port > 65_535) {
145+    throw new Error(`Invalid --port value "${value}".`);
146+  }
147+
148+  return port;
149+}
150+
151+function renderStatusApiCliHelp(): string {
152+  return [
153+    "Usage: node apps/status-api/dist/index.js [serve|smoke|help] [--host <host>] [--port <port>]",
154+    "",
155+    `Default listen address: http://${getDefaultStatusApiHost()}:${getDefaultStatusApiPort()}`,
156+    "Routes:",
157+    "- GET /healthz",
158+    "- GET /v1/status",
159+    "- GET /v1/status/ui",
160+    "",
161+    "Examples:",
162+    "- pnpm --filter @baa-conductor/status-api serve",
163+    "- pnpm --filter @baa-conductor/status-api smoke",
164+    "- node apps/status-api/dist/index.js --host 127.0.0.1 --port 4318"
165+  ].join("\n") + "\n";
166+}
167+
168+function toTextWriter(
169+  writer: StatusApiTextWriter | undefined,
170+  fallback: (message?: unknown, ...optionalParams: unknown[]) => void
171+): StatusApiTextWriter {
172+  if (writer != null) {
173+    return writer;
174+  }
175+
176+  return {
177+    write(chunk: string) {
178+      fallback(chunk.endsWith("\n") ? chunk.slice(0, -1) : chunk);
179+    }
180+  };
181+}
A apps/status-api/src/host.ts
+369, -0
  1@@ -0,0 +1,369 @@
  2+import { createStatusApiRuntime, describeStatusApiRuntimeSurface, type StatusApiRuntime, type StatusApiRuntimeOptions } from "./runtime.js";
  3+import type { StatusApiResponse } from "./contracts.js";
  4+
  5+const DEFAULT_STATUS_API_HOST = "127.0.0.1";
  6+const DEFAULT_STATUS_API_PORT = 4318;
  7+const NODE_HTTP_MODULE_SPECIFIER = "node:http";
  8+
  9+const ERROR_HEADERS = {
 10+  "content-type": "application/json; charset=utf-8",
 11+  "cache-control": "no-store"
 12+} as const;
 13+
 14+export interface StatusApiEnvironment {
 15+  [key: string]: string | undefined;
 16+}
 17+
 18+export interface StatusApiListenOptions {
 19+  host: string;
 20+  port: number;
 21+}
 22+
 23+export interface StatusApiServerOptions extends StatusApiRuntimeOptions {
 24+  host?: string;
 25+  port?: number;
 26+}
 27+
 28+export interface StatusApiServerAddress {
 29+  address: string;
 30+  family: string;
 31+  port: number;
 32+}
 33+
 34+export interface StatusApiServer {
 35+  readonly runtime: StatusApiRuntime;
 36+  close(): Promise<void>;
 37+  describeSurface(): string[];
 38+  getAddress(): StatusApiServerAddress | string | null;
 39+  getBaseUrl(): string | null;
 40+}
 41+
 42+export interface StatusApiSmokeCheck {
 43+  path: string;
 44+  status: number;
 45+  detail: string;
 46+}
 47+
 48+export interface StatusApiSmokeCheckResult {
 49+  baseUrl: string;
 50+  checks: StatusApiSmokeCheck[];
 51+}
 52+
 53+interface NodeHttpAddressInfo {
 54+  address: string;
 55+  family: string;
 56+  port: number;
 57+}
 58+
 59+interface NodeIncomingMessage {
 60+  method?: string;
 61+  url?: string;
 62+}
 63+
 64+interface NodeServerResponse {
 65+  statusCode: number;
 66+  setHeader(name: string, value: string | readonly string[]): void;
 67+  end(chunk?: string | Uint8Array): void;
 68+}
 69+
 70+interface NodeHttpServer {
 71+  address(): NodeHttpAddressInfo | string | null;
 72+  close(callback?: (error?: Error) => void): void;
 73+  listen(port: number, host: string, listeningListener?: () => void): void;
 74+  once(event: string, listener: (...args: unknown[]) => void): void;
 75+}
 76+
 77+interface NodeHttpModule {
 78+  createServer(
 79+    requestListener: (request: NodeIncomingMessage, response: NodeServerResponse) => void | Promise<void>
 80+  ): NodeHttpServer;
 81+}
 82+
 83+export function resolveStatusApiListenOptions(
 84+  options: StatusApiServerOptions = {},
 85+  env: StatusApiEnvironment = process?.env ?? {}
 86+): StatusApiListenOptions {
 87+  return {
 88+    host: resolveStatusApiHost(options.host, env),
 89+    port: resolveStatusApiPort(options.port, env)
 90+  };
 91+}
 92+
 93+export function createStatusApiNodeRequestListener(runtime: StatusApiRuntime) {
 94+  return (request: NodeIncomingMessage, response: NodeServerResponse): void => {
 95+    void handleStatusApiNodeRequest(runtime, request, response);
 96+  };
 97+}
 98+
 99+export async function startStatusApiServer(options: StatusApiServerOptions = {}): Promise<StatusApiServer> {
100+  const { createServer } = await importNodeHttp();
101+  const listenOptions = resolveStatusApiListenOptions(options);
102+  const runtime = createStatusApiRuntime({
103+    snapshotLoader: options.snapshotLoader
104+  });
105+  const server = createServer(createStatusApiNodeRequestListener(runtime));
106+
107+  await listenOnServer(server, listenOptions);
108+
109+  return {
110+    runtime,
111+    close: () => closeServer(server),
112+    describeSurface: () => describeStatusApiRuntimeSurface(runtime),
113+    getAddress: () => toServerAddress(server.address()),
114+    getBaseUrl: () => getStatusApiBaseUrl(server.address(), listenOptions.host)
115+  };
116+}
117+
118+export async function runStatusApiSmokeCheck(
119+  options: StatusApiServerOptions = {}
120+): Promise<StatusApiSmokeCheckResult> {
121+  const server = await startStatusApiServer({
122+    ...options,
123+    host: options.host ?? DEFAULT_STATUS_API_HOST,
124+    port: options.port ?? 0
125+  });
126+
127+  try {
128+    const baseUrl = server.getBaseUrl();
129+
130+    if (baseUrl == null) {
131+      throw new Error("Status API smoke check could not resolve a listening address.");
132+    }
133+
134+    const checks = [
135+      await verifyHealthz(baseUrl),
136+      await verifyStatus(baseUrl),
137+      await verifyUi(baseUrl)
138+    ];
139+
140+    return {
141+      baseUrl,
142+      checks
143+    };
144+  } finally {
145+    await server.close();
146+  }
147+}
148+
149+export function getDefaultStatusApiHost(): string {
150+  return DEFAULT_STATUS_API_HOST;
151+}
152+
153+export function getDefaultStatusApiPort(): number {
154+  return DEFAULT_STATUS_API_PORT;
155+}
156+
157+async function handleStatusApiNodeRequest(
158+  runtime: StatusApiRuntime,
159+  request: NodeIncomingMessage,
160+  response: NodeServerResponse
161+): Promise<void> {
162+  try {
163+    const runtimeResponse = await runtime.handle({
164+      method: request.method ?? "GET",
165+      path: request.url ?? "/"
166+    });
167+
168+    writeStatusApiResponse(response, runtimeResponse);
169+  } catch (error) {
170+    writeUnhandledErrorResponse(response, error);
171+  }
172+}
173+
174+function writeStatusApiResponse(response: NodeServerResponse, runtimeResponse: StatusApiResponse): void {
175+  response.statusCode = runtimeResponse.status;
176+
177+  for (const [name, value] of Object.entries(runtimeResponse.headers)) {
178+    response.setHeader(name, value);
179+  }
180+
181+  response.end(runtimeResponse.body);
182+}
183+
184+function writeUnhandledErrorResponse(response: NodeServerResponse, error: unknown): void {
185+  const message = error instanceof Error ? error.message : String(error);
186+
187+  response.statusCode = 500;
188+
189+  for (const [name, value] of Object.entries(ERROR_HEADERS)) {
190+    response.setHeader(name, value);
191+  }
192+
193+  response.end(
194+    `${JSON.stringify(
195+      {
196+        ok: false,
197+        error: "internal_error",
198+        message
199+      },
200+      null,
201+      2
202+    )}\n`
203+  );
204+}
205+
206+function resolveStatusApiHost(explicitHost: string | undefined, env: StatusApiEnvironment): string {
207+  const host = explicitHost ?? env.BAA_STATUS_API_HOST ?? env.HOST ?? DEFAULT_STATUS_API_HOST;
208+
209+  return host.trim() === "" ? DEFAULT_STATUS_API_HOST : host;
210+}
211+
212+function resolveStatusApiPort(explicitPort: number | undefined, env: StatusApiEnvironment): number {
213+  if (explicitPort != null) {
214+    return normalizePort(explicitPort);
215+  }
216+
217+  const rawPort = env.BAA_STATUS_API_PORT ?? env.PORT;
218+
219+  if (rawPort == null || rawPort.trim() === "") {
220+    return DEFAULT_STATUS_API_PORT;
221+  }
222+
223+  return normalizePort(Number(rawPort));
224+}
225+
226+function normalizePort(value: number): number {
227+  if (!Number.isInteger(value) || value < 0 || value > 65_535) {
228+    throw new TypeError(`Expected a valid TCP port, received "${String(value)}".`);
229+  }
230+
231+  return value;
232+}
233+
234+async function importNodeHttp(): Promise<NodeHttpModule> {
235+  return import(NODE_HTTP_MODULE_SPECIFIER) as Promise<NodeHttpModule>;
236+}
237+
238+function listenOnServer(server: NodeHttpServer, options: StatusApiListenOptions): Promise<void> {
239+  return new Promise((resolve, reject) => {
240+    let settled = false;
241+
242+    server.once("error", (error: unknown) => {
243+      if (settled) {
244+        return;
245+      }
246+
247+      settled = true;
248+      reject(error);
249+    });
250+
251+    server.listen(options.port, options.host, () => {
252+      if (settled) {
253+        return;
254+      }
255+
256+      settled = true;
257+      resolve();
258+    });
259+  });
260+}
261+
262+function closeServer(server: NodeHttpServer): Promise<void> {
263+  return new Promise((resolve, reject) => {
264+    server.close((error?: Error) => {
265+      if (error != null) {
266+        reject(error);
267+        return;
268+      }
269+
270+      resolve();
271+    });
272+  });
273+}
274+
275+function toServerAddress(value: ReturnType<NodeHttpServer["address"]>): StatusApiServerAddress | string | null {
276+  if (value == null || typeof value === "string") {
277+    return value;
278+  }
279+
280+  return {
281+    address: value.address,
282+    family: value.family,
283+    port: value.port
284+  };
285+}
286+
287+function getStatusApiBaseUrl(
288+  value: ReturnType<NodeHttpServer["address"]>,
289+  fallbackHost: string
290+): string | null {
291+  if (value == null || typeof value === "string") {
292+    return null;
293+  }
294+
295+  return `http://${formatHostForUrl(selectReachableHost(value.address, fallbackHost))}:${value.port}`;
296+}
297+
298+function selectReachableHost(address: string, fallbackHost: string): string {
299+  if (address === "::" || address === "0.0.0.0") {
300+    return fallbackHost === "::" || fallbackHost === "0.0.0.0" ? DEFAULT_STATUS_API_HOST : fallbackHost;
301+  }
302+
303+  return address;
304+}
305+
306+function formatHostForUrl(host: string): string {
307+  return host.includes(":") ? `[${host}]` : host;
308+}
309+
310+async function verifyHealthz(baseUrl: string): Promise<StatusApiSmokeCheck> {
311+  const response = await fetch(new URL("/healthz", baseUrl));
312+  const body = await response.text();
313+
314+  assertStatus(response, 200, "/healthz");
315+
316+  if (body.trim() !== "ok") {
317+    throw new Error(`Expected /healthz to return "ok", received ${JSON.stringify(body)}.`);
318+  }
319+
320+  return {
321+    path: "/healthz",
322+    status: response.status,
323+    detail: 'body="ok"'
324+  };
325+}
326+
327+async function verifyStatus(baseUrl: string): Promise<StatusApiSmokeCheck> {
328+  const response = await fetch(new URL("/v1/status", baseUrl));
329+  const payload = (await response.json()) as {
330+    ok?: boolean;
331+    data?: {
332+      source?: string;
333+    };
334+  };
335+
336+  assertStatus(response, 200, "/v1/status");
337+
338+  if (payload.ok !== true || payload.data?.source !== "empty") {
339+    throw new Error(`Expected /v1/status to return an empty snapshot envelope, received ${JSON.stringify(payload)}.`);
340+  }
341+
342+  return {
343+    path: "/v1/status",
344+    status: response.status,
345+    detail: `source=${payload.data.source}`
346+  };
347+}
348+
349+async function verifyUi(baseUrl: string): Promise<StatusApiSmokeCheck> {
350+  const response = await fetch(new URL("/v1/status/ui", baseUrl));
351+  const html = await response.text();
352+
353+  assertStatus(response, 200, "/v1/status/ui");
354+
355+  if (!html.includes("<title>BAA Conductor Status</title>")) {
356+    throw new Error("Expected /v1/status/ui to return the HTML status page shell.");
357+  }
358+
359+  return {
360+    path: "/v1/status/ui",
361+    status: response.status,
362+    detail: "html shell rendered"
363+  };
364+}
365+
366+function assertStatus(response: Response, expectedStatus: number, path: string): void {
367+  if (response.status !== expectedStatus) {
368+    throw new Error(`Expected ${path} to return ${expectedStatus}, received ${response.status}.`);
369+  }
370+}
M apps/status-api/src/index.ts
+57, -0
 1@@ -3,3 +3,60 @@ export * from "./data-source.js";
 2 export * from "./render.js";
 3 export * from "./runtime.js";
 4 export * from "./service.js";
 5+export * from "./host.js";
 6+export * from "./cli.js";
 7+
 8+import { runStatusApiCli } from "./cli.js";
 9+
10+if (shouldRunStatusApiCli(import.meta.url)) {
11+  try {
12+    const exitCode = await runStatusApiCli();
13+
14+    if (exitCode !== 0 && typeof process !== "undefined") {
15+      process.exitCode = exitCode;
16+    }
17+  } catch (error) {
18+    console.error(formatStatusApiCliError(error));
19+
20+    if (typeof process !== "undefined") {
21+      process.exitCode = 1;
22+    }
23+  }
24+}
25+
26+function shouldRunStatusApiCli(metaUrl: string): boolean {
27+  if (typeof process === "undefined") {
28+    return false;
29+  }
30+
31+  const executedPath = normalizeCliEntryPath(process.argv[1]);
32+
33+  if (executedPath == null) {
34+    return false;
35+  }
36+
37+  const sourceEntryPath = normalizeCliEntryPath(toFsPath(metaUrl));
38+  const distShimPath = normalizeCliEntryPath(toFsPath(new URL("../../../index.js", metaUrl).href));
39+
40+  return executedPath === sourceEntryPath || executedPath === distShimPath;
41+}
42+
43+function normalizeCliEntryPath(value: string | undefined): string | null {
44+  if (value == null || value === "") {
45+    return null;
46+  }
47+
48+  return value.endsWith("/") ? value.slice(0, -1) : value;
49+}
50+
51+function toFsPath(value: string): string {
52+  return decodeURIComponent(new URL(value).pathname);
53+}
54+
55+function formatStatusApiCliError(error: unknown): string {
56+  if (error instanceof Error) {
57+    return error.stack ?? `${error.name}: ${error.message}`;
58+  }
59+
60+  return `Status API startup failed: ${String(error)}`;
61+}
A apps/status-api/src/node-shims.d.ts
+11, -0
 1@@ -0,0 +1,11 @@
 2+declare global {
 3+  const process:
 4+    | {
 5+        argv: string[];
 6+        env: Record<string, string | undefined>;
 7+        exitCode?: number;
 8+      }
 9+    | undefined;
10+}
11+
12+export {};
M apps/status-api/tsconfig.json
+1, -1
1@@ -5,5 +5,5 @@
2     "rootDir": "../..",
3     "outDir": "dist"
4   },
5-  "include": ["src/**/*.ts", "../../packages/db/src/**/*.ts"]
6+  "include": ["src/**/*.ts", "src/**/*.d.ts", "../../packages/db/src/**/*.ts"]
7 }
M coordination/tasks/T-020-status-host.md
+26, -7
 1@@ -1,10 +1,10 @@
 2 ---
 3 task_id: T-020
 4 title: Status API 本地宿主进程
 5-status: todo
 6+status: review
 7 branch: feat/T-020-status-host
 8 repo: /Users/george/code/baa-conductor
 9-base_ref: main
10+base_ref: main@458d7cf
11 depends_on:
12   - T-017
13 write_scope:
14@@ -62,23 +62,42 @@ updated_at: 2026-03-22
15 
16 ## files_changed
17 
18-- 待填写
19+- `apps/status-api/package.json`
20+- `apps/status-api/tsconfig.json`
21+- `apps/status-api/src/index.ts`
22+- `apps/status-api/src/cli.ts`
23+- `apps/status-api/src/host.ts`
24+- `apps/status-api/src/node-shims.d.ts`
25+- `coordination/tasks/T-020-status-host.md`
26 
27 ## commands_run
28 
29-- 待填写
30+- `npx --yes pnpm install`
31+- `npx --yes pnpm --filter @baa-conductor/status-api typecheck`
32+- `npx --yes pnpm --filter @baa-conductor/status-api build`
33+- `npx --yes pnpm --filter @baa-conductor/status-api smoke`
34+- `node apps/status-api/dist/index.js --host 127.0.0.1 --port 4328`
35+- `curl --silent --show-error http://127.0.0.1:4328/healthz`
36+- `curl --silent --show-error http://127.0.0.1:4328/v1/status`
37+- `curl --silent --show-error http://127.0.0.1:4328/v1/status/ui | sed -n '1,8p'`
38 
39 ## result
40 
41-- 待填写
42+- 已在 `apps/status-api/**` 内补齐 Node 宿主层,新增本地 HTTP request listener 与 `startStatusApiServer()`,把 `createStatusApiRuntime()` 真正挂到监听端口。
43+- 已补 `runStatusApiCli()` 与主入口直启逻辑,`node apps/status-api/dist/index.js` 现在默认监听 `127.0.0.1:4318`,可直接给 launchd 复用。
44+- 已新增包内脚本:`pnpm --filter @baa-conductor/status-api serve`、`start`、`smoke`;本地可访问 `GET /healthz`、`GET /v1/status`、`GET /v1/status/ui`。
45+- 已补最小真实 HTTP 冒烟验证:`smoke` 会拉起临时端口并验证三条 GET 路由,另外也手动确认了 `node apps/status-api/dist/index.js --host 127.0.0.1 --port 4328` 的实际监听响应。
46 
47 ## risks
48 
49-- 待填写
50+- 当前宿主进程默认仍使用 `StaticStatusSnapshotLoader`,返回的是本地空快照;真实 D1 / 控制平面接线仍需后续整合任务注入。
51+- `dist/index.js` 的直启能力依赖当前统一 build 产物布局;如果后续全仓库再调整 `BAA_DIST_ENTRY` 或 shim 结构,需要同步验证直启检测逻辑。
52 
53 ## next_handoff
54 
55-- 待填写
56+- 在后续整合中,把真实 snapshot loader 注入 `startStatusApiServer()` 或 CLI 启动路径,让 `/v1/status` 返回真实控制平面状态。
57+- 视 launchd/bootstrap 任务需要,在安装副本里补 `BAA_STATUS_API_HOST` / `BAA_STATUS_API_PORT` 等环境变量,或继续沿用默认 `127.0.0.1:4318`。
58+- 如果后续希望把 status-api 嵌入到更大的 Node 宿主里,可直接复用本任务新增的 request listener / server adapter,而不必再改 service/runtime 层。
59 
60 开始时建议直接把 `status` 改为 `in_progress`。
61