@north-light/api 0.0.0-stage → 0.3.414
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +18 -0
- package/README.md +66 -2
- package/dist/api/__tests__/error-codes.test.d.ts +1 -0
- package/dist/api/__tests__/error-codes.test.js +79 -0
- package/dist/api/__tests__/integration/client.test.d.ts +1 -0
- package/dist/api/__tests__/integration/client.test.js +276 -0
- package/dist/api/client.d.ts +501 -0
- package/dist/api/client.js +1281 -0
- package/dist/api/command-manifest/index.d.ts +3 -0
- package/dist/api/command-manifest/index.js +3 -0
- package/dist/api/command-manifest/manifest.d.ts +51 -0
- package/dist/api/command-manifest/manifest.js +332 -0
- package/dist/api/command-manifest/result.d.ts +25 -0
- package/dist/api/command-manifest/result.js +97 -0
- package/dist/api/command-manifest/schema.d.ts +28 -0
- package/dist/api/command-manifest/schema.js +856 -0
- package/dist/api/dto/analytics.d.ts +184 -0
- package/dist/api/dto/analytics.js +3 -0
- package/dist/api/dto/attach.d.ts +22 -0
- package/dist/api/dto/attach.js +13 -0
- package/dist/api/dto/bash-jobs.d.ts +24 -0
- package/dist/api/dto/bash-jobs.js +9 -0
- package/dist/api/dto/bash.d.ts +17 -0
- package/dist/api/dto/bash.js +1 -0
- package/dist/api/dto/broker-ops.d.ts +187 -0
- package/dist/api/dto/broker-ops.js +6 -0
- package/dist/api/dto/broker-signals.d.ts +25 -0
- package/dist/api/dto/broker-signals.js +1 -0
- package/dist/api/dto/broker.d.ts +86 -0
- package/dist/api/dto/broker.js +20 -0
- package/dist/api/dto/canvas.d.ts +359 -0
- package/dist/api/dto/canvas.js +2 -0
- package/dist/api/dto/chat-inventory.d.ts +56 -0
- package/dist/api/dto/chat-inventory.js +11 -0
- package/dist/api/dto/common.d.ts +29 -0
- package/dist/api/dto/common.js +15 -0
- package/dist/api/dto/config.d.ts +46 -0
- package/dist/api/dto/config.js +3 -0
- package/dist/api/dto/crons.d.ts +158 -0
- package/dist/api/dto/crons.js +10 -0
- package/dist/api/dto/custom-objects.d.ts +66 -0
- package/dist/api/dto/custom-objects.js +1 -0
- package/dist/api/dto/delivery.d.ts +71 -0
- package/dist/api/dto/delivery.js +7 -0
- package/dist/api/dto/docs.d.ts +135 -0
- package/dist/api/dto/docs.js +8 -0
- package/dist/api/dto/files.d.ts +21 -0
- package/dist/api/dto/files.js +1 -0
- package/dist/api/dto/focus.d.ts +24 -0
- package/dist/api/dto/focus.js +10 -0
- package/dist/api/dto/grants.d.ts +28 -0
- package/dist/api/dto/grants.js +1 -0
- package/dist/api/dto/health.d.ts +116 -0
- package/dist/api/dto/health.js +2 -0
- package/dist/api/dto/human-requests.d.ts +113 -0
- package/dist/api/dto/human-requests.js +4 -0
- package/dist/api/dto/human.d.ts +28 -0
- package/dist/api/dto/human.js +4 -0
- package/dist/api/dto/inbox.d.ts +273 -0
- package/dist/api/dto/inbox.js +4 -0
- package/dist/api/dto/lifecycle.d.ts +88 -0
- package/dist/api/dto/lifecycle.js +3 -0
- package/dist/api/dto/limits.d.ts +29 -0
- package/dist/api/dto/limits.js +2 -0
- package/dist/api/dto/mail.d.ts +44 -0
- package/dist/api/dto/mail.js +1 -0
- package/dist/api/dto/messages.d.ts +104 -0
- package/dist/api/dto/messages.js +2 -0
- package/dist/api/dto/model-config.d.ts +25 -0
- package/dist/api/dto/model-config.js +1 -0
- package/dist/api/dto/modelauth.d.ts +132 -0
- package/dist/api/dto/modelauth.js +4 -0
- package/dist/api/dto/node-events.d.ts +65 -0
- package/dist/api/dto/node-events.js +4 -0
- package/dist/api/dto/node-outcomes.d.ts +88 -0
- package/dist/api/dto/node-outcomes.js +2 -0
- package/dist/api/dto/node-records.d.ts +35 -0
- package/dist/api/dto/node-records.js +5 -0
- package/dist/api/dto/nodes.d.ts +371 -0
- package/dist/api/dto/nodes.js +3 -0
- package/dist/api/dto/objects.d.ts +172 -0
- package/dist/api/dto/objects.js +5 -0
- package/dist/api/dto/profiles.d.ts +117 -0
- package/dist/api/dto/profiles.js +4 -0
- package/dist/api/dto/recovery.d.ts +104 -0
- package/dist/api/dto/recovery.js +1 -0
- package/dist/api/dto/reports.d.ts +95 -0
- package/dist/api/dto/reports.js +2 -0
- package/dist/api/dto/review-comments.d.ts +146 -0
- package/dist/api/dto/review-comments.js +5 -0
- package/dist/api/dto/reviews.d.ts +113 -0
- package/dist/api/dto/reviews.js +5 -0
- package/dist/api/dto/run-events.d.ts +295 -0
- package/dist/api/dto/run-events.js +6 -0
- package/dist/api/dto/structured-requests.d.ts +64 -0
- package/dist/api/dto/structured-requests.js +5 -0
- package/dist/api/dto/subscriptions.d.ts +14 -0
- package/dist/api/dto/subscriptions.js +2 -0
- package/dist/api/dto/worktree.d.ts +55 -0
- package/dist/api/dto/worktree.js +6 -0
- package/dist/api/error-codes.d.ts +259 -0
- package/dist/api/error-codes.js +56 -0
- package/dist/api/errors.d.ts +47 -0
- package/dist/api/errors.js +66 -0
- package/dist/api/index.d.ts +48 -0
- package/dist/api/index.js +45 -0
- package/dist/api/long-poll.d.ts +59 -0
- package/dist/api/long-poll.js +67 -0
- package/dist/api/node-transport.d.ts +18 -0
- package/dist/api/node-transport.js +105 -0
- package/dist/api/plugin-manifest-schema.d.ts +233 -0
- package/dist/api/plugin-manifest-schema.js +23 -0
- package/dist/api/routes.d.ts +164 -0
- package/dist/api/routes.js +197 -0
- package/dist/api/runtime-version.d.ts +31 -0
- package/dist/api/runtime-version.js +50 -0
- package/dist/core/asset-root.d.ts +7 -0
- package/dist/core/asset-root.js +18 -0
- package/dist/core/canvas/boot-id.d.ts +6 -0
- package/dist/core/canvas/boot-id.js +26 -0
- package/dist/core/canvas/paths.d.ts +72 -0
- package/dist/core/canvas/paths.js +163 -0
- package/dist/core/canvas/pid.d.ts +391 -0
- package/dist/core/canvas/pid.js +948 -0
- package/dist/core/command-plugins/bundle.d.ts +149 -0
- package/dist/core/command-plugins/bundle.js +588 -0
- package/dist/core/command-plugins/endpoint.d.ts +24 -0
- package/dist/core/command-plugins/endpoint.js +51 -0
- package/dist/core/config.d.ts +233 -0
- package/dist/core/config.js +1120 -0
- package/dist/core/env-name.d.ts +6 -0
- package/dist/core/env-name.js +9 -0
- package/dist/core/errors.d.ts +38 -0
- package/dist/core/errors.js +90 -0
- package/dist/core/events/emit.d.ts +6 -0
- package/dist/core/events/emit.js +42 -0
- package/dist/core/events/envelope.d.ts +2 -0
- package/dist/core/events/envelope.js +84 -0
- package/dist/core/events/errors.d.ts +4 -0
- package/dist/core/events/errors.js +69 -0
- package/dist/core/events/operation-id.d.ts +4 -0
- package/dist/core/events/operation-id.js +24 -0
- package/dist/core/events/serialize.d.ts +4 -0
- package/dist/core/events/serialize.js +199 -0
- package/dist/core/events/source.d.ts +16 -0
- package/dist/core/events/source.js +31 -0
- package/dist/core/events/types.d.ts +68 -0
- package/dist/core/events/types.js +11 -0
- package/dist/core/exclusive-lock.d.ts +34 -0
- package/dist/core/exclusive-lock.js +197 -0
- package/dist/core/fs-utils.d.ts +44 -0
- package/dist/core/fs-utils.js +208 -0
- package/dist/core/help.d.ts +309 -0
- package/dist/core/help.js +406 -0
- package/dist/core/human/page-catalog.d.ts +57 -0
- package/dist/core/human/page-catalog.js +172 -0
- package/dist/core/installed-plugins.d.ts +2 -0
- package/dist/core/installed-plugins.js +79 -0
- package/dist/core/io.d.ts +122 -0
- package/dist/core/io.js +373 -0
- package/dist/core/keybindings/attach-control.d.ts +49 -0
- package/dist/core/keybindings/attach-control.js +42 -0
- package/dist/core/keybindings/catalog.d.ts +18 -0
- package/dist/core/keybindings/catalog.js +257 -0
- package/dist/core/keybindings/types.d.ts +42 -0
- package/dist/core/keybindings/types.js +1 -0
- package/dist/core/layout.d.ts +34 -0
- package/dist/core/layout.js +106 -0
- package/dist/core/locked-file.d.ts +27 -0
- package/dist/core/locked-file.js +118 -0
- package/dist/core/log.d.ts +9 -0
- package/dist/core/log.js +89 -0
- package/dist/core/manifest.d.ts +5 -0
- package/dist/core/manifest.js +15 -0
- package/dist/core/plugin-env.d.ts +8 -0
- package/dist/core/plugin-env.js +31 -0
- package/dist/core/plugin-extensions.d.ts +29 -0
- package/dist/core/plugin-extensions.js +191 -0
- package/dist/core/plugin-swap-lock.d.ts +9 -0
- package/dist/core/plugin-swap-lock.js +31 -0
- package/dist/core/preview-result-path.d.ts +4 -0
- package/dist/core/preview-result-path.js +26 -0
- package/dist/core/profiles/env-store.d.ts +22 -0
- package/dist/core/profiles/env-store.js +163 -0
- package/dist/core/profiles/fuzzy-match.d.ts +19 -0
- package/dist/core/profiles/fuzzy-match.js +92 -0
- package/dist/core/profiles/manifest.d.ts +120 -0
- package/dist/core/profiles/manifest.js +529 -0
- package/dist/core/rate-limit-scope.d.ts +25 -0
- package/dist/core/rate-limit-scope.js +64 -0
- package/dist/core/render.d.ts +12 -0
- package/dist/core/render.js +138 -0
- package/dist/core/resolver.d.ts +14 -0
- package/dist/core/resolver.js +111 -0
- package/dist/core/runtime/branded-host.d.ts +55 -0
- package/dist/core/runtime/branded-host.js +485 -0
- package/dist/core/runtime/broker/daemon-ops.d.ts +65 -0
- package/dist/core/runtime/broker/daemon-ops.js +177 -0
- package/dist/core/runtime/broker/signal-stream.d.ts +30 -0
- package/dist/core/runtime/broker/signal-stream.js +149 -0
- package/dist/core/scope.d.ts +32 -0
- package/dist/core/scope.js +184 -0
- package/dist/core/scoped-state/db.d.ts +17 -0
- package/dist/core/scoped-state/db.js +247 -0
- package/dist/core/scoped-state/migrate.d.ts +8 -0
- package/dist/core/scoped-state/migrate.js +187 -0
- package/dist/core/scoped-state/paths.d.ts +9 -0
- package/dist/core/scoped-state/paths.js +27 -0
- package/dist/core/scoped-state/profiles.d.ts +27 -0
- package/dist/core/scoped-state/profiles.js +93 -0
- package/dist/core/scoped-state/providers.d.ts +24 -0
- package/dist/core/scoped-state/providers.js +19 -0
- package/dist/core/scoped-state/schema.d.ts +6 -0
- package/dist/core/scoped-state/schema.js +43 -0
- package/dist/core/scoped-state/settings.d.ts +28 -0
- package/dist/core/scoped-state/settings.js +83 -0
- package/dist/core/spaces/open-beneath.d.ts +71 -0
- package/dist/core/spaces/open-beneath.js +581 -0
- package/dist/core/sqlite-statements.d.ts +4 -0
- package/dist/core/sqlite-statements.js +17 -0
- package/dist/core/subscription-state.d.ts +121 -0
- package/dist/core/subscription-state.js +287 -0
- package/dist/core/user-settings.d.ts +377 -0
- package/dist/core/user-settings.js +458 -0
- package/dist/daemon/broker-signals/bus.d.ts +30 -0
- package/dist/daemon/broker-signals/bus.js +87 -0
- package/dist/daemon/manage.d.ts +176 -0
- package/dist/daemon/manage.js +664 -0
- package/dist/daemon/pidfile.d.ts +8 -0
- package/dist/daemon/pidfile.js +37 -0
- package/dist/daemon/startup-policy.d.ts +1 -0
- package/dist/daemon/startup-policy.js +1 -0
- package/dist/native/linux.d.ts +29 -0
- package/dist/native/linux.js +20 -0
- package/dist/shared/env.d.ts +116 -0
- package/dist/shared/env.js +271 -0
- package/dist/shared/generated-context.d.ts +83 -0
- package/dist/shared/generated-context.js +235 -0
- package/dist/shared/inbox-entry-body.d.ts +22 -0
- package/dist/shared/inbox-entry-body.js +116 -0
- package/dist/shared/predicates.d.ts +2 -0
- package/dist/shared/predicates.js +4 -0
- package/dist/shared/working-activity.d.ts +9 -0
- package/dist/shared/working-activity.js +27 -0
- package/dist/types.d.ts +562 -0
- package/dist/types.js +186 -0
- package/package.json +47 -4
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
// branded-host.ts — a crouter-branded copy of the `node` binary, so the
|
|
2
|
+
// long-lived engine processes (brokers + the crtrd daemon) show "crouter" in
|
|
3
|
+
// macOS' TCC / Full Disk Access list instead of the generic "node".
|
|
4
|
+
//
|
|
5
|
+
// WHY a real copied binary (not a symlink, not process.title): macOS keys a
|
|
6
|
+
// Full Disk Access entry on the Mach-O binary's *filename* — not argv[0], not
|
|
7
|
+
// process.title, and symlinks resolve to their realpath. So the ONLY way to show
|
|
8
|
+
// a branded name is to launch the engine from a real Mach-O file whose filename
|
|
9
|
+
// is the brand. We copy the node launcher to `~/.crouter/host/crouter`.
|
|
10
|
+
//
|
|
11
|
+
// WHY self-contained (libnode beside it): a Homebrew node is a small (~70 KB)
|
|
12
|
+
// launcher that dynamically loads `@rpath/libnode.<abi>.dylib` (~70 MB). node's
|
|
13
|
+
// LC_RPATH includes `@loader_path`, so a copy of that dylib placed *beside* the
|
|
14
|
+
// branded binary resolves with no DYLD_* env var (which SIP can strip) and no
|
|
15
|
+
// dependency on the Cellar path surviving — even after a `brew uninstall` of the
|
|
16
|
+
// old keg, the branded copy keeps running. A statically-linked node (nodejs.org)
|
|
17
|
+
// has no separate libnode; we just copy the binary. The remaining absolute
|
|
18
|
+
// `/opt/homebrew/opt/...` deps resolve by their baked-in absolute paths.
|
|
19
|
+
//
|
|
20
|
+
// SELF-HEAL: ensureBrandedHost() rebuilds the copy whenever the live node drifts
|
|
21
|
+
// (a `brew upgrade node` moves realpath(execPath) to a new Cellar version), the
|
|
22
|
+
// copy is missing, or the copy no longer starts. The last case matters because
|
|
23
|
+
// the copied libnode does NOT protect the other absolute Homebrew deps (e.g.
|
|
24
|
+
// libsimdjson.<n>.dylib): a `brew upgrade` that bumps one leaves the branded host
|
|
25
|
+
// aborting in dyld while its marker still matches. That holds even when the
|
|
26
|
+
// caller is itself running as the branded host (crtrd, a broker): it then
|
|
27
|
+
// rebuilds from a real node found via findRealNode(), never from itself.
|
|
28
|
+
//
|
|
29
|
+
// FALLBACK: inside the host, childNodeExecPath() / hostExecPath() use a real
|
|
30
|
+
// node (same native-module ABI) while the host cannot start, probing
|
|
31
|
+
// asynchronously and rebuilding the host in a detached child — the daemon's
|
|
32
|
+
// event loop never blocks on it.
|
|
33
|
+
import { copyFileSync, mkdirSync, existsSync, readFileSync, writeFileSync, chmodSync, readdirSync, renameSync, realpathSync, unlinkSync, rmSync, linkSync, } from 'node:fs';
|
|
34
|
+
import { homedir } from 'node:os';
|
|
35
|
+
import { dirname, join } from 'node:path';
|
|
36
|
+
import { execFile, spawn, spawnSync } from 'node:child_process';
|
|
37
|
+
import { CRTR_DIR_NAME } from '../../types.js';
|
|
38
|
+
import { emitEvent } from '../events/emit.js';
|
|
39
|
+
import { withExclusiveDirectoryLock } from '../exclusive-lock.js';
|
|
40
|
+
/** The filename the FDA / TCC entry is shown under. */
|
|
41
|
+
const BRANDED_BIN_NAME = 'crouter';
|
|
42
|
+
/** Machine-global host dir in this process owner’s HOME. Only the daemon reconciles it; agents do not install the branded host. Deliberately NOT under `CRTR_HOME` (tests override
|
|
43
|
+
* that to an isolated temp): the branded binary is a per-machine artifact keyed
|
|
44
|
+
* to the installed node, shared across every canvas home. */
|
|
45
|
+
export function brandedHostDir() {
|
|
46
|
+
return join(homedir(), CRTR_DIR_NAME, 'host');
|
|
47
|
+
}
|
|
48
|
+
export function brandedHostBin() {
|
|
49
|
+
return join(brandedHostDir(), BRANDED_BIN_NAME);
|
|
50
|
+
}
|
|
51
|
+
function markerPath() {
|
|
52
|
+
return join(brandedHostDir(), '.source.json');
|
|
53
|
+
}
|
|
54
|
+
/** Locate the `libnode.<abi>.dylib` a Homebrew node loads via `@rpath`. node's
|
|
55
|
+
* rpaths are `@loader_path` and `@loader_path/../lib`, so check the binary's own
|
|
56
|
+
* dir first, then the sibling `lib/`. Returns the absolute path, or null for a
|
|
57
|
+
* statically-linked node that has no separate libnode. */
|
|
58
|
+
function findLibnode(nodeBin) {
|
|
59
|
+
const binDir = dirname(nodeBin);
|
|
60
|
+
for (const dir of [binDir, join(binDir, '..', 'lib')]) {
|
|
61
|
+
if (!existsSync(dir))
|
|
62
|
+
continue;
|
|
63
|
+
const hit = readdirSync(dir).find((f) => f.startsWith('libnode.') && f.endsWith('.dylib'));
|
|
64
|
+
if (hit !== undefined)
|
|
65
|
+
return join(dir, hit);
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
/** Copy `src` → `dst` via a unique temp + atomic rename, so a failed copy
|
|
70
|
+
* never leaves a half-written artifact. Host-wide concurrency is serialized by
|
|
71
|
+
* the dedicated reconcile lock below. */
|
|
72
|
+
function atomicCopy(src, dst, mode) {
|
|
73
|
+
const tmp = `${dst}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
|
|
74
|
+
copyFileSync(src, tmp);
|
|
75
|
+
if (mode !== undefined)
|
|
76
|
+
chmodSync(tmp, mode);
|
|
77
|
+
renameSync(tmp, dst);
|
|
78
|
+
}
|
|
79
|
+
function readMarker(path = markerPath()) {
|
|
80
|
+
try {
|
|
81
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const HOST_LOCK_TIMEOUT_MS = 30_000;
|
|
88
|
+
/** Serialize reconciliation of the one machine-global host tuple. This lock is
|
|
89
|
+
* deliberately separate from the immutable runtime-generation install lock. */
|
|
90
|
+
function withBrandedHostLock(run) {
|
|
91
|
+
const root = join(homedir(), CRTR_DIR_NAME);
|
|
92
|
+
const lockPath = join(root, 'host.lock');
|
|
93
|
+
mkdirSync(root, { recursive: true });
|
|
94
|
+
return withExclusiveDirectoryLock(lockPath, run, {
|
|
95
|
+
timeoutMs: HOST_LOCK_TIMEOUT_MS,
|
|
96
|
+
timeoutError: () => new Error(`timed out waiting for branded host lock: ${lockPath}`),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/** macOS keys FDA grants on the Mach-O cdhash. A real developer identity
|
|
100
|
+
* keeps that hash stable across branded-host rebuilds; ad-hoc signing is the
|
|
101
|
+
* portable default for machines without a local signing certificate. */
|
|
102
|
+
export function codesignIdentitiesForBrandedHost(opts = {}) {
|
|
103
|
+
const configured = opts.configured?.trim();
|
|
104
|
+
if (configured)
|
|
105
|
+
return [configured];
|
|
106
|
+
const discovered = opts.discovered?.trim();
|
|
107
|
+
return [discovered || '-'];
|
|
108
|
+
}
|
|
109
|
+
function discoverCodesignIdentity() {
|
|
110
|
+
if (process.platform !== 'darwin')
|
|
111
|
+
return null;
|
|
112
|
+
const res = spawnSync('security', ['find-identity', '-v', '-p', 'codesigning'], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
113
|
+
if (res.status !== 0)
|
|
114
|
+
return null;
|
|
115
|
+
const stdout = (res.stdout ?? '').toString();
|
|
116
|
+
const identities = stdout
|
|
117
|
+
.split('\n')
|
|
118
|
+
.map((line) => line.match(/\)\s+[A-Fa-f0-9]{40}\s+"([^"]+)"/)?.[1] ?? null)
|
|
119
|
+
.filter((identity) => identity !== null);
|
|
120
|
+
return identities.find((identity) => identity.startsWith('Apple Development:'))
|
|
121
|
+
?? identities.find((identity) => identity.startsWith('Developer ID Application:'))
|
|
122
|
+
?? identities[0]
|
|
123
|
+
?? null;
|
|
124
|
+
}
|
|
125
|
+
function runCodesign(binPath, identity) {
|
|
126
|
+
const res = spawnSync('codesign', ['-s', identity, '--deep', '--force', binPath], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
127
|
+
return { ok: res.status === 0, stderr: (res.stderr ?? '').toString().trim() };
|
|
128
|
+
}
|
|
129
|
+
function signBrandedHost(binPath) {
|
|
130
|
+
if (process.platform !== 'darwin')
|
|
131
|
+
return;
|
|
132
|
+
const identities = codesignIdentitiesForBrandedHost({
|
|
133
|
+
configured: process.env['CRTR_CODESIGN_IDENTITY'],
|
|
134
|
+
discovered: discoverCodesignIdentity(),
|
|
135
|
+
});
|
|
136
|
+
let lastError = '';
|
|
137
|
+
for (const identity of identities) {
|
|
138
|
+
const res = runCodesign(binPath, identity);
|
|
139
|
+
if (res.ok)
|
|
140
|
+
return;
|
|
141
|
+
lastError = res.stderr;
|
|
142
|
+
}
|
|
143
|
+
if (lastError && !lastError.includes('valid on disk')) {
|
|
144
|
+
emitEvent({ event: 'host.codesign.failed', level: 'debug', error: lastError });
|
|
145
|
+
}
|
|
146
|
+
throw new Error(`codesign failed for ${binPath}: ${lastError || 'codesign did not exit successfully'}`);
|
|
147
|
+
}
|
|
148
|
+
export function signBrandedHostArtifacts(binPath, libnodePath, sign = signBrandedHost) {
|
|
149
|
+
if (libnodePath !== null)
|
|
150
|
+
sign(libnodePath);
|
|
151
|
+
sign(binPath);
|
|
152
|
+
}
|
|
153
|
+
export function brandedHostWorks(binPath, timeoutMs = 5_000) {
|
|
154
|
+
try {
|
|
155
|
+
const res = spawnSync(binPath, ['-e', ''], {
|
|
156
|
+
stdio: 'ignore',
|
|
157
|
+
timeout: timeoutMs,
|
|
158
|
+
});
|
|
159
|
+
return res.status === 0;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** Ensure the branded host binary exists and matches the live node; return its
|
|
166
|
+
* absolute path. Idempotent and self-healing. Throws on a genuine build failure
|
|
167
|
+
* (disk full / permissions); hostExecPath() turns that into a logged fallback to
|
|
168
|
+
* a real node so a broken host never blocks launching. */
|
|
169
|
+
export function ensureBrandedHost(opts = {}) {
|
|
170
|
+
return withBrandedHostLock(() => {
|
|
171
|
+
const hostDir = brandedHostDir();
|
|
172
|
+
const dst = brandedHostBin();
|
|
173
|
+
let src = realpathSync(opts.execPath ?? process.execPath);
|
|
174
|
+
// If THIS process is running as the branded host (the daemon or a broker),
|
|
175
|
+
// the live process proves only that the copy *was* loadable. It says nothing
|
|
176
|
+
// about whether a fresh process can start it: a moved Homebrew dylib breaks
|
|
177
|
+
// every new exec while the running one carries on. So re-check, and when the
|
|
178
|
+
// copy is broken rebuild from a real node — we can't copy ourselves.
|
|
179
|
+
if (existsSync(dst) && realpathSync(dst) === src) {
|
|
180
|
+
if (brandedHostWorks(dst))
|
|
181
|
+
return dst;
|
|
182
|
+
const real = findRealNode();
|
|
183
|
+
if (real === null) {
|
|
184
|
+
throw new Error(`branded host cannot start and no working node was found to rebuild it from: ${dst}`);
|
|
185
|
+
}
|
|
186
|
+
src = real;
|
|
187
|
+
}
|
|
188
|
+
const marker = readMarker();
|
|
189
|
+
const libnodeSrc = findLibnode(src);
|
|
190
|
+
const libnodeName = libnodeSrc !== null ? libnodeSrc.split('/').pop() : null;
|
|
191
|
+
const tupleExists = (candidate) => existsSync(dst) &&
|
|
192
|
+
candidate !== null &&
|
|
193
|
+
candidate.version === 2 &&
|
|
194
|
+
(candidate.libnode === null || existsSync(join(hostDir, candidate.libnode)));
|
|
195
|
+
const tupleMatches = (candidate) => tupleExists(candidate) && candidate.source === src && candidate.libnode === libnodeName;
|
|
196
|
+
if (tupleMatches(marker) && brandedHostWorks(dst))
|
|
197
|
+
return dst;
|
|
198
|
+
// Build and validate a complete tuple privately. Failures before publication
|
|
199
|
+
// leave any previous valid host untouched.
|
|
200
|
+
mkdirSync(hostDir, { recursive: true });
|
|
201
|
+
const stageDir = join(hostDir, `.reconcile.${process.pid}.${Math.random().toString(36).slice(2)}`);
|
|
202
|
+
const backupDir = join(stageDir, 'previous');
|
|
203
|
+
mkdirSync(stageDir);
|
|
204
|
+
let publicationStarted = false;
|
|
205
|
+
const previousTupleIsValid = tupleExists(marker) && brandedHostWorks(dst);
|
|
206
|
+
try {
|
|
207
|
+
const stageBin = join(stageDir, BRANDED_BIN_NAME);
|
|
208
|
+
const stageLibnode = libnodeName !== null ? join(stageDir, libnodeName) : null;
|
|
209
|
+
if (libnodeSrc !== null && stageLibnode !== null)
|
|
210
|
+
atomicCopy(libnodeSrc, stageLibnode);
|
|
211
|
+
atomicCopy(src, stageBin, 0o755);
|
|
212
|
+
signBrandedHostArtifacts(stageBin, stageLibnode);
|
|
213
|
+
if (!brandedHostWorks(stageBin)) {
|
|
214
|
+
throw new Error(`rebuilt branded host failed its startup check: ${stageBin}`);
|
|
215
|
+
}
|
|
216
|
+
if (previousTupleIsValid)
|
|
217
|
+
backupHostTuple(backupDir, dst, marker);
|
|
218
|
+
publicationStarted = true;
|
|
219
|
+
if (stageLibnode !== null)
|
|
220
|
+
renameSync(stageLibnode, join(hostDir, libnodeName));
|
|
221
|
+
// Both artifacts were validated together in the stage dir. A libnode keeps
|
|
222
|
+
// its filename only when its ABI is unchanged (the name embeds it), so a
|
|
223
|
+
// launcher that starts between these two renames pairs the old stub with a
|
|
224
|
+
// same-ABI libnode, which loads; a changed ABI gets a new name and never
|
|
225
|
+
// replaces the old file in place.
|
|
226
|
+
renameSync(stageBin, dst);
|
|
227
|
+
atomicCopyMarker({ version: 2, source: src, libnode: libnodeName });
|
|
228
|
+
if (!tupleMatches(readMarker()) || !brandedHostWorks(dst)) {
|
|
229
|
+
throw new Error(`published branded host tuple failed validation: ${dst}`);
|
|
230
|
+
}
|
|
231
|
+
// Cleanup follows successful marker-last publication and validation.
|
|
232
|
+
if (marker?.libnode != null && marker.libnode !== libnodeName) {
|
|
233
|
+
try {
|
|
234
|
+
unlinkSync(join(hostDir, marker.libnode));
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
/* best-effort */
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return dst;
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
if (publicationStarted && previousTupleIsValid)
|
|
244
|
+
restoreHostTuple(backupDir, dst, marker);
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
rmSync(stageDir, { recursive: true, force: true });
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
function atomicCopyMarker(marker) {
|
|
253
|
+
const path = markerPath();
|
|
254
|
+
const tmp = `${path}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
|
|
255
|
+
writeFileSync(tmp, JSON.stringify(marker));
|
|
256
|
+
renameSync(tmp, path);
|
|
257
|
+
}
|
|
258
|
+
function backupHostTuple(backupDir, binPath, marker) {
|
|
259
|
+
mkdirSync(backupDir);
|
|
260
|
+
linkSync(binPath, join(backupDir, BRANDED_BIN_NAME));
|
|
261
|
+
if (marker.libnode !== null)
|
|
262
|
+
linkSync(join(brandedHostDir(), marker.libnode), join(backupDir, marker.libnode));
|
|
263
|
+
writeFileSync(join(backupDir, '.source.json'), JSON.stringify(marker));
|
|
264
|
+
}
|
|
265
|
+
function restoreHostTuple(backupDir, binPath, marker) {
|
|
266
|
+
if (marker.libnode !== null)
|
|
267
|
+
renameSync(join(backupDir, marker.libnode), join(brandedHostDir(), marker.libnode));
|
|
268
|
+
renameSync(join(backupDir, BRANDED_BIN_NAME), binPath);
|
|
269
|
+
renameSync(join(backupDir, '.source.json'), markerPath());
|
|
270
|
+
if (!brandedHostWorks(binPath))
|
|
271
|
+
throw new Error(`failed to restore previous branded host tuple: ${binPath}`);
|
|
272
|
+
}
|
|
273
|
+
/** True when `execPath` is the branded host copy (so a child exec'd from it
|
|
274
|
+
* inherits the copy's dyld dependencies). */
|
|
275
|
+
function isBrandedHostExec(execPath) {
|
|
276
|
+
try {
|
|
277
|
+
const hostDir = realpathSync(brandedHostDir());
|
|
278
|
+
return dirname(realpathSync(execPath)) === hostDir;
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/** Candidate real nodes, most trustworthy first: the installed Homebrew node
|
|
285
|
+
* (the one an upgrade just put in place), this process's own node when it is
|
|
286
|
+
* not the host, PATH entries, then the marker's recorded source (an older keg
|
|
287
|
+
* after an upgrade, so last). */
|
|
288
|
+
function realNodeCandidates() {
|
|
289
|
+
const candidates = ['/opt/homebrew/bin/node', '/usr/local/bin/node'];
|
|
290
|
+
if (!isBrandedHostExec(process.execPath))
|
|
291
|
+
candidates.push(process.execPath);
|
|
292
|
+
for (const dir of (process.env['PATH'] ?? '').split(':'))
|
|
293
|
+
if (dir !== '')
|
|
294
|
+
candidates.push(join(dir, 'node'));
|
|
295
|
+
const marker = readMarker();
|
|
296
|
+
if (marker !== null && typeof marker.source === 'string')
|
|
297
|
+
candidates.push(marker.source);
|
|
298
|
+
const seen = new Set();
|
|
299
|
+
const real = [];
|
|
300
|
+
for (const candidate of candidates) {
|
|
301
|
+
let resolved;
|
|
302
|
+
try {
|
|
303
|
+
resolved = realpathSync(candidate);
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (seen.has(resolved) || isBrandedHostExec(resolved))
|
|
309
|
+
continue;
|
|
310
|
+
seen.add(resolved);
|
|
311
|
+
real.push(resolved);
|
|
312
|
+
}
|
|
313
|
+
return real;
|
|
314
|
+
}
|
|
315
|
+
/** A candidate is acceptable only if it starts AND speaks this process's native
|
|
316
|
+
* module ABI, so addons and engine features built for the running Node keep
|
|
317
|
+
* working — a merely-startable older nvm node is not a fallback. */
|
|
318
|
+
const ABI_PROBE_ARGS = ['-p', 'process.versions.modules'];
|
|
319
|
+
function abiMatches(stdout) {
|
|
320
|
+
return stdout !== null && stdout.trim() === process.versions.modules;
|
|
321
|
+
}
|
|
322
|
+
/** Find a working, ABI-compatible, non-branded `node` to rebuild from. Blocking:
|
|
323
|
+
* for CLI / explicit reconcile only, never the daemon's event loop (use
|
|
324
|
+
* findRealNodeAsync there). Returns null when none works. */
|
|
325
|
+
export function findRealNode() {
|
|
326
|
+
for (const candidate of realNodeCandidates()) {
|
|
327
|
+
try {
|
|
328
|
+
const res = spawnSync(candidate, ABI_PROBE_ARGS, { encoding: 'utf8', timeout: 5_000, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
329
|
+
if (res.status === 0 && abiMatches(res.stdout))
|
|
330
|
+
return candidate;
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
/* try the next candidate */
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
function execFileProbe(bin, args, timeoutMs) {
|
|
339
|
+
return new Promise((resolve) => {
|
|
340
|
+
try {
|
|
341
|
+
execFile(bin, args, { encoding: 'utf8', timeout: timeoutMs }, (error, stdout) => resolve(error === null ? stdout : null));
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
resolve(null);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
/** Non-blocking findRealNode for the daemon's event loop. */
|
|
349
|
+
export async function findRealNodeAsync() {
|
|
350
|
+
for (const candidate of realNodeCandidates()) {
|
|
351
|
+
if (abiMatches(await execFileProbe(candidate, ABI_PROBE_ARGS, 5_000)))
|
|
352
|
+
return candidate;
|
|
353
|
+
}
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
// Health of the branded host as seen from a process RUNNING as that host (crtrd,
|
|
357
|
+
// a broker). It is refreshed asynchronously — never a blocking probe on the
|
|
358
|
+
// event loop — and a fork that dies forces an immediate re-probe, so the first
|
|
359
|
+
// failed fork heals instead of waiting out a TTL.
|
|
360
|
+
const HEALTH_TTL_MS = 15_000;
|
|
361
|
+
const HEAL_THROTTLE_MS = 60_000;
|
|
362
|
+
const hostHealth = { checkedAt: 0, fallback: null, probing: null, lastHealAt: 0 };
|
|
363
|
+
/** Forget remembered host health (tests). */
|
|
364
|
+
export function resetBrandedHostHealthCache() {
|
|
365
|
+
hostHealth.checkedAt = 0;
|
|
366
|
+
hostHealth.fallback = null;
|
|
367
|
+
hostHealth.probing = null;
|
|
368
|
+
hostHealth.lastHealAt = 0;
|
|
369
|
+
}
|
|
370
|
+
/** Rebuild the host from a real node in a detached child, off this process's
|
|
371
|
+
* event loop (copy + codesign are blocking). Throttled. Skipped in source/tsx
|
|
372
|
+
* runs, which never use the host. */
|
|
373
|
+
function healBrandedHostInBackground(realNode) {
|
|
374
|
+
if (import.meta.url.endsWith('.ts'))
|
|
375
|
+
return;
|
|
376
|
+
const now = Date.now();
|
|
377
|
+
if (now - hostHealth.lastHealAt < HEAL_THROTTLE_MS)
|
|
378
|
+
return;
|
|
379
|
+
hostHealth.lastHealAt = now;
|
|
380
|
+
try {
|
|
381
|
+
const script = `import(${JSON.stringify(import.meta.url)}).then((m) => m.ensureBrandedHost())`;
|
|
382
|
+
const child = spawn(realNode, ['--input-type=module', '-e', script], { detached: true, stdio: 'ignore' });
|
|
383
|
+
child.on('error', () => undefined);
|
|
384
|
+
child.unref();
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
/* healing is best-effort; the fallback node already keeps launches working */
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function refreshHostHealth(host) {
|
|
391
|
+
if (hostHealth.probing !== null)
|
|
392
|
+
return hostHealth.probing;
|
|
393
|
+
hostHealth.probing = (async () => {
|
|
394
|
+
try {
|
|
395
|
+
if ((await execFileProbe(host, ['-e', ''], 5_000)) !== null) {
|
|
396
|
+
hostHealth.fallback = null;
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const real = await findRealNodeAsync();
|
|
400
|
+
if (real !== hostHealth.fallback && real !== null) {
|
|
401
|
+
emitEvent({ event: 'host.fallback.node', level: 'warn', fields: { host, node: real } });
|
|
402
|
+
}
|
|
403
|
+
hostHealth.fallback = real;
|
|
404
|
+
if (real !== null)
|
|
405
|
+
healBrandedHostInBackground(real);
|
|
406
|
+
}
|
|
407
|
+
finally {
|
|
408
|
+
hostHealth.checkedAt = Date.now();
|
|
409
|
+
hostHealth.probing = null;
|
|
410
|
+
}
|
|
411
|
+
})();
|
|
412
|
+
return hostHealth.probing;
|
|
413
|
+
}
|
|
414
|
+
/** The node to exec for a short-lived helper child (forked worker, bash
|
|
415
|
+
* supervisor, probe). Outside the branded host this is `process.execPath`
|
|
416
|
+
* untouched. Inside it, a host that no longer starts (dyld cannot resolve a
|
|
417
|
+
* moved Homebrew dependency) would abort every child, so a real node is used
|
|
418
|
+
* instead. Never blocks: it returns the last known answer and refreshes it in
|
|
419
|
+
* the background. A fork that still dies calls recheckChildNode() to learn the
|
|
420
|
+
* truth at once. */
|
|
421
|
+
export function childNodeExecPath(execPath = process.execPath) {
|
|
422
|
+
if (!isBrandedHostExec(execPath))
|
|
423
|
+
return execPath;
|
|
424
|
+
if (Date.now() - hostHealth.checkedAt > HEALTH_TTL_MS && hostHealth.probing === null)
|
|
425
|
+
void refreshHostHealth(execPath);
|
|
426
|
+
return hostHealth.fallback ?? execPath;
|
|
427
|
+
}
|
|
428
|
+
/** Re-probe the host now (after a child died) and return the node to retry on;
|
|
429
|
+
* equals `execPath` when the host is fine or no real node exists. */
|
|
430
|
+
export async function recheckChildNode(execPath = process.execPath) {
|
|
431
|
+
if (!isBrandedHostExec(execPath))
|
|
432
|
+
return execPath;
|
|
433
|
+
await refreshHostHealth(execPath);
|
|
434
|
+
return hostHealth.fallback ?? execPath;
|
|
435
|
+
}
|
|
436
|
+
/** Like childNodeExecPath(), but first awaits a probe no older than `maxAgeMs`
|
|
437
|
+
* (coalesced with any probe in flight). Use it right before a fork whose death
|
|
438
|
+
* would lose work (a broker, a bash job, the inventory child) so a host broken
|
|
439
|
+
* since the last probe is caught before the exec, not after. The probe is a
|
|
440
|
+
* non-blocking `node -e ''`; outside the branded host this costs nothing. */
|
|
441
|
+
export async function freshChildNodeExecPath(execPath = process.execPath, maxAgeMs = 1_000) {
|
|
442
|
+
if (!isBrandedHostExec(execPath))
|
|
443
|
+
return execPath;
|
|
444
|
+
if (hostHealth.probing === null && Date.now() - hostHealth.checkedAt >= maxAgeMs)
|
|
445
|
+
await refreshHostHealth(execPath);
|
|
446
|
+
else if (hostHealth.probing !== null)
|
|
447
|
+
await hostHealth.probing;
|
|
448
|
+
return hostHealth.fallback ?? execPath;
|
|
449
|
+
}
|
|
450
|
+
/** hostExecPath() for async launchers: inside the host it is verified fresh
|
|
451
|
+
* rather than taken from the cache. */
|
|
452
|
+
export async function hostExecPathFresh() {
|
|
453
|
+
if (process.env['CRTR_BRANDED_HOST'] === '0' || process.platform !== 'darwin' || import.meta.url.endsWith('.ts'))
|
|
454
|
+
return hostExecPath();
|
|
455
|
+
if (isBrandedHostExec(process.execPath))
|
|
456
|
+
return freshChildNodeExecPath();
|
|
457
|
+
return hostExecPath();
|
|
458
|
+
}
|
|
459
|
+
/** The path the engine (broker / daemon) should be launched from. On macOS in a
|
|
460
|
+
* compiled (production) build this is the validated branded host; otherwise —
|
|
461
|
+
* non-darwin (TCC is macOS-only), source/tsx (dev + the test suite: never copy
|
|
462
|
+
* 70 MB into the real ~/.crouter), or the explicit `CRTR_BRANDED_HOST=0`
|
|
463
|
+
* opt-out — it is the live node. */
|
|
464
|
+
export function hostExecPath() {
|
|
465
|
+
if (process.env['CRTR_BRANDED_HOST'] === '0')
|
|
466
|
+
return process.execPath;
|
|
467
|
+
if (process.platform !== 'darwin')
|
|
468
|
+
return process.execPath;
|
|
469
|
+
if (import.meta.url.endsWith('.ts'))
|
|
470
|
+
return process.execPath;
|
|
471
|
+
// Running AS the host (crtrd spawning a broker): never block this event loop.
|
|
472
|
+
// The host is used while it starts and a real node otherwise, with a
|
|
473
|
+
// background rebuild — see childNodeExecPath().
|
|
474
|
+
if (isBrandedHostExec(process.execPath))
|
|
475
|
+
return childNodeExecPath();
|
|
476
|
+
try {
|
|
477
|
+
return ensureBrandedHost();
|
|
478
|
+
}
|
|
479
|
+
catch (error) {
|
|
480
|
+
// A host that cannot be (re)built must not stop every node from launching.
|
|
481
|
+
// This process is a real node, so run unbranded and say so.
|
|
482
|
+
emitEvent({ event: 'host.fallback.node', level: 'warn', error, fields: { host: brandedHostBin(), node: process.execPath } });
|
|
483
|
+
return process.execPath;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type BrokerSignalCachedNode } from '../../../daemon/broker-signals/bus.js';
|
|
2
|
+
import type { BrokerExtensionStateDTO, BrokerGeneratedNameRequest, BrokerGeneratedNameResultDTO, BrokerModelCommitRequest, BrokerModelCommitResultDTO, BrokerParkActivityResultDTO, BrokerParkCompleteRequest, BrokerPersonaAckRequest, BrokerPersonaAckResultDTO, BrokerSessionBoundRequest, BrokerSessionBoundResultDTO, BrokerSettleDirective, BrokerSettleRequest, BrokerTelemetryRequest } from '../../../api/dto/broker-ops.js';
|
|
3
|
+
import type { AcknowledgeMailResultDTO, ClaimMailResultDTO, MailReceiptDTO } from '../../../api/dto/mail.js';
|
|
4
|
+
import type { BrokerFaultInputDTO, BrokerFaultRequest, BrokerFaultResultDTO, BrokerProviderRetryRequest, BrokerProviderRetryResultDTO, BrokerTurnRequest, BrokerTurnResultDTO, RecoveryStateDTO } from '../../../api/dto/recovery.js';
|
|
5
|
+
import type { RelaunchRootResultDTO } from '../../../api/dto/lifecycle.js';
|
|
6
|
+
import type { PushReportRequest } from '../../../api/dto/reports.js';
|
|
7
|
+
import type { BackgroundedJobDTO, BackgroundedJobRequest, DeliveryRequest, DeliveryResponse } from '../../../api/dto/delivery.js';
|
|
8
|
+
import type { NodeLogAppendResultDTO, NodeMessageBodyDTO, NodePushedFinalResultDTO, NodeTelemetryRecordRequest } from '../../../api/dto/node-records.js';
|
|
9
|
+
export declare function appendBrokerLog(nodeId: string, record: string): Promise<NodeLogAppendResultDTO>;
|
|
10
|
+
export declare function putBrokerTelemetry(nodeId: string, telemetry: Omit<NodeTelemetryRecordRequest, 'expected_execution_id'>): Promise<void>;
|
|
11
|
+
export declare function putBrokerRecap(nodeId: string, briefing: string): Promise<void>;
|
|
12
|
+
/** The body of a direct message the daemon spilled into this node's records. */
|
|
13
|
+
export declare function readBrokerPassiveMessage(nodeId: string, ref: string): Promise<NodeMessageBodyDTO>;
|
|
14
|
+
export declare function clearBrokerPushedFinal(nodeId: string): Promise<NodePushedFinalResultDTO>;
|
|
15
|
+
export declare function consumeBrokerPushedFinal(nodeId: string): Promise<NodePushedFinalResultDTO>;
|
|
16
|
+
export declare function startBrokerBashJob(nodeId: string, request: {
|
|
17
|
+
job_id: string;
|
|
18
|
+
cwd: string;
|
|
19
|
+
env: NodeJS.ProcessEnv;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
pid: number;
|
|
22
|
+
}>;
|
|
23
|
+
/** Record a command the valve just backgrounded as this node's job object. */
|
|
24
|
+
export declare function recordBackgroundedJob(nodeId: string, jobId: string, request: BackgroundedJobRequest): Promise<BackgroundedJobDTO>;
|
|
25
|
+
export declare function stopBrokerBashJob(nodeId: string, jobId: string): Promise<void>;
|
|
26
|
+
export declare function brokerExecutionId(): string;
|
|
27
|
+
export declare function bindBrokerSession(nodeId: string, request: Omit<BrokerSessionBoundRequest, 'executionId'>): Promise<BrokerSessionBoundResultDTO>;
|
|
28
|
+
type BrokerInput<T> = T extends unknown ? Omit<T, 'expected_execution_id'> : never;
|
|
29
|
+
export declare function settleBroker(nodeId: string, request: BrokerInput<BrokerSettleRequest>): Promise<BrokerSettleDirective>;
|
|
30
|
+
export declare function completeBrokerPark(nodeId: string, request: BrokerInput<BrokerParkCompleteRequest>): Promise<BrokerSettleDirective>;
|
|
31
|
+
export declare function recordBrokerParkActivity(nodeId: string): Promise<BrokerParkActivityResultDTO>;
|
|
32
|
+
export declare function recordBrokerTelemetry(nodeId: string, request: Omit<BrokerTelemetryRequest, 'expected_execution_id'>): Promise<void>;
|
|
33
|
+
export declare function claimBrokerMail(nodeId: string, channel: 'wake' | 'passive' | 'deferred_run', reportBodies: Map<string, Promise<string>>): Promise<ClaimMailResultDTO>;
|
|
34
|
+
export declare function acknowledgeBrokerMail(nodeId: string, deliveries: MailReceiptDTO[]): Promise<AcknowledgeMailResultDTO>;
|
|
35
|
+
export declare function recordBrokerTurn(nodeId: string, request: Omit<BrokerTurnRequest, 'expected_execution_id'>): Promise<BrokerTurnResultDTO>;
|
|
36
|
+
export declare function mutateBrokerProviderRetry(nodeId: string, request: Omit<BrokerProviderRetryRequest, 'expected_execution_id'>): Promise<BrokerProviderRetryResultDTO>;
|
|
37
|
+
export declare function mutateBrokerFault(nodeId: string, request: Omit<BrokerFaultRequest, 'expected_execution_id'>): Promise<BrokerFaultResultDTO>;
|
|
38
|
+
export declare function brokerRecovery(nodeId: string): Promise<RecoveryStateDTO>;
|
|
39
|
+
/** Node-scoped fault writes are for non-broker clients such as attach transport.
|
|
40
|
+
* Broker-owned recovery mutations use the execution-fenced wrappers above. */
|
|
41
|
+
export declare function recordNodeFault(nodeId: string, input: BrokerFaultInputDTO): Promise<BrokerFaultResultDTO>;
|
|
42
|
+
export declare function clearNodeFault(nodeId: string, opts?: {
|
|
43
|
+
link?: BrokerFaultInputDTO['link'];
|
|
44
|
+
preserveEpisode?: boolean;
|
|
45
|
+
}): Promise<boolean>;
|
|
46
|
+
/** Fixed canvas projection for broker extension rendering. It deliberately
|
|
47
|
+
* exposes no generic node/database facade. */
|
|
48
|
+
export declare function commitBrokerModel(nodeId: string, request: BrokerInput<BrokerModelCommitRequest>): Promise<BrokerModelCommitResultDTO>;
|
|
49
|
+
export declare function brokerExtensionState(nodeId: string): Promise<BrokerExtensionStateDTO>;
|
|
50
|
+
/** Only node-owned fields are cached. A disconnected or stopped stream cannot
|
|
51
|
+
* guarantee freshness, so events at those boundaries read the daemon instead. */
|
|
52
|
+
export declare function cachedBrokerNode(nodeId: string): Promise<BrokerSignalCachedNode>;
|
|
53
|
+
/** The node's one delivery route: every rule-driven document delivery the
|
|
54
|
+
* broker applies, recorded by crtrd in the same request. */
|
|
55
|
+
export declare function brokerDelivery(nodeId: string, request: BrokerInput<DeliveryRequest>): Promise<DeliveryResponse>;
|
|
56
|
+
export declare function commitBrokerGeneratedName(nodeId: string, request: BrokerInput<BrokerGeneratedNameRequest>): Promise<BrokerGeneratedNameResultDTO>;
|
|
57
|
+
export declare function commitBrokerPersonaAck(nodeId: string, request: BrokerInput<BrokerPersonaAckRequest>): Promise<BrokerPersonaAckResultDTO>;
|
|
58
|
+
/** Reports still use the canonical daemon report handler; this broker adapter
|
|
59
|
+
* only preserves terminal structured output's idempotent finalization result. */
|
|
60
|
+
export declare function publishBrokerReport(nodeId: string, request: PushReportRequest): Promise<void>;
|
|
61
|
+
/** Root `/new` remains on the dedicated relaunch operation because it launches
|
|
62
|
+
* a replacement broker and tears down this broker. Callers must not await it
|
|
63
|
+
* from Pi's session-start dispatch. */
|
|
64
|
+
export declare function relaunchBrokerRoot(nodeId: string): Promise<RelaunchRootResultDTO>;
|
|
65
|
+
export {};
|