@phigon/observer 8.1.0 → 8.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +244 -18
- package/dist/bin/guardian-runner-darwin-amd64 +0 -0
- package/dist/bin/guardian-runner-darwin-arm64 +0 -0
- package/dist/bin/guardian-runner-linux-amd64 +0 -0
- package/dist/bin/guardian-runner-linux-arm64 +0 -0
- package/dist/bin/guardian-runner-windows-amd64.exe +0 -0
- package/dist/bin/guardian-runner-windows-arm64.exe +0 -0
- package/dist/src/abortable-delay.d.ts +10 -0
- package/dist/src/abortable-delay.d.ts.map +1 -0
- package/dist/src/abortable-delay.js +21 -0
- package/dist/src/abortable-delay.js.map +1 -0
- package/dist/src/action-policy.d.ts.map +1 -1
- package/dist/src/action-policy.js +4 -2
- package/dist/src/action-policy.js.map +1 -1
- package/dist/src/activity/adapters/claude.d.ts.map +1 -1
- package/dist/src/activity/adapters/claude.js +28 -2
- package/dist/src/activity/adapters/claude.js.map +1 -1
- package/dist/src/activity/adapters/cline-task.d.ts +7 -0
- package/dist/src/activity/adapters/cline-task.d.ts.map +1 -0
- package/dist/src/activity/adapters/cline-task.js +197 -0
- package/dist/src/activity/adapters/cline-task.js.map +1 -0
- package/dist/src/activity/adapters/cline.d.ts +3 -0
- package/dist/src/activity/adapters/cline.d.ts.map +1 -0
- package/dist/src/activity/adapters/cline.js +102 -0
- package/dist/src/activity/adapters/cline.js.map +1 -0
- package/dist/src/activity/adapters/codex.d.ts.map +1 -1
- package/dist/src/activity/adapters/codex.js +76 -0
- package/dist/src/activity/adapters/codex.js.map +1 -1
- package/dist/src/activity/adapters/copilot-chat.d.ts +3 -0
- package/dist/src/activity/adapters/copilot-chat.d.ts.map +1 -0
- package/dist/src/activity/adapters/copilot-chat.js +176 -0
- package/dist/src/activity/adapters/copilot-chat.js.map +1 -0
- package/dist/src/activity/adapters/copilot.d.ts +2 -1
- package/dist/src/activity/adapters/copilot.d.ts.map +1 -1
- package/dist/src/activity/adapters/copilot.js +173 -2
- package/dist/src/activity/adapters/copilot.js.map +1 -1
- package/dist/src/activity/adapters/cursor.d.ts +3 -0
- package/dist/src/activity/adapters/cursor.d.ts.map +1 -0
- package/dist/src/activity/adapters/cursor.js +86 -0
- package/dist/src/activity/adapters/cursor.js.map +1 -0
- package/dist/src/activity/adapters/factory-droid.d.ts +3 -0
- package/dist/src/activity/adapters/factory-droid.d.ts.map +1 -0
- package/dist/src/activity/adapters/factory-droid.js +126 -0
- package/dist/src/activity/adapters/factory-droid.js.map +1 -0
- package/dist/src/activity/adapters/gemini.d.ts +3 -0
- package/dist/src/activity/adapters/gemini.d.ts.map +1 -0
- package/dist/src/activity/adapters/gemini.js +177 -0
- package/dist/src/activity/adapters/gemini.js.map +1 -0
- package/dist/src/activity/adapters/goose.d.ts +3 -0
- package/dist/src/activity/adapters/goose.d.ts.map +1 -0
- package/dist/src/activity/adapters/goose.js +116 -0
- package/dist/src/activity/adapters/goose.js.map +1 -0
- package/dist/src/activity/adapters/kiro.d.ts +3 -0
- package/dist/src/activity/adapters/kiro.d.ts.map +1 -0
- package/dist/src/activity/adapters/kiro.js +47 -0
- package/dist/src/activity/adapters/kiro.js.map +1 -0
- package/dist/src/activity/adapters/openai-compatible.d.ts.map +1 -1
- package/dist/src/activity/adapters/openai-compatible.js +9 -1
- package/dist/src/activity/adapters/openai-compatible.js.map +1 -1
- package/dist/src/activity/adapters/opencode.d.ts.map +1 -1
- package/dist/src/activity/adapters/opencode.js +108 -1
- package/dist/src/activity/adapters/opencode.js.map +1 -1
- package/dist/src/activity/adapters/zed.d.ts +3 -0
- package/dist/src/activity/adapters/zed.d.ts.map +1 -0
- package/dist/src/activity/adapters/zed.js +134 -0
- package/dist/src/activity/adapters/zed.js.map +1 -0
- package/dist/src/activity/collector.d.ts +43 -1
- package/dist/src/activity/collector.d.ts.map +1 -1
- package/dist/src/activity/collector.js +490 -77
- package/dist/src/activity/collector.js.map +1 -1
- package/dist/src/activity/goose-sqlite-source.d.ts +33 -0
- package/dist/src/activity/goose-sqlite-source.d.ts.map +1 -0
- package/dist/src/activity/goose-sqlite-source.js +274 -0
- package/dist/src/activity/goose-sqlite-source.js.map +1 -0
- package/dist/src/activity/index.d.ts +3 -0
- package/dist/src/activity/index.d.ts.map +1 -1
- package/dist/src/activity/index.js +3 -0
- package/dist/src/activity/index.js.map +1 -1
- package/dist/src/activity/ingest.d.ts +9 -0
- package/dist/src/activity/ingest.d.ts.map +1 -1
- package/dist/src/activity/ingest.js +94 -4
- package/dist/src/activity/ingest.js.map +1 -1
- package/dist/src/activity/json-document-source.d.ts +151 -0
- package/dist/src/activity/json-document-source.d.ts.map +1 -0
- package/dist/src/activity/json-document-source.js +586 -0
- package/dist/src/activity/json-document-source.js.map +1 -0
- package/dist/src/activity/native-capabilities.d.ts.map +1 -1
- package/dist/src/activity/native-capabilities.js +8 -12
- package/dist/src/activity/native-capabilities.js.map +1 -1
- package/dist/src/activity/normalize.d.ts.map +1 -1
- package/dist/src/activity/normalize.js +64 -6
- package/dist/src/activity/normalize.js.map +1 -1
- package/dist/src/activity/opencode-message-tables.d.ts +87 -0
- package/dist/src/activity/opencode-message-tables.d.ts.map +1 -0
- package/dist/src/activity/opencode-message-tables.js +546 -0
- package/dist/src/activity/opencode-message-tables.js.map +1 -0
- package/dist/src/activity/opencode-sqlite-source.d.ts +7 -0
- package/dist/src/activity/opencode-sqlite-source.d.ts.map +1 -1
- package/dist/src/activity/opencode-sqlite-source.js +154 -63
- package/dist/src/activity/opencode-sqlite-source.js.map +1 -1
- package/dist/src/activity/source-reader.d.ts +60 -4
- package/dist/src/activity/source-reader.d.ts.map +1 -1
- package/dist/src/activity/source-reader.js +593 -75
- package/dist/src/activity/source-reader.js.map +1 -1
- package/dist/src/activity/sqlite-connections.d.ts +77 -0
- package/dist/src/activity/sqlite-connections.d.ts.map +1 -0
- package/dist/src/activity/sqlite-connections.js +220 -0
- package/dist/src/activity/sqlite-connections.js.map +1 -0
- package/dist/src/activity/zed-sqlite-source.d.ts +65 -0
- package/dist/src/activity/zed-sqlite-source.d.ts.map +1 -0
- package/dist/src/activity/zed-sqlite-source.js +547 -0
- package/dist/src/activity/zed-sqlite-source.js.map +1 -0
- package/dist/src/agent-action-executor.d.ts +18 -1
- package/dist/src/agent-action-executor.d.ts.map +1 -1
- package/dist/src/agent-action-executor.js +113 -58
- package/dist/src/agent-action-executor.js.map +1 -1
- package/dist/src/agentic-guardian.d.ts.map +1 -1
- package/dist/src/agentic-guardian.js +6 -5
- package/dist/src/agentic-guardian.js.map +1 -1
- package/dist/src/atomic-replace.d.ts +7 -0
- package/dist/src/atomic-replace.d.ts.map +1 -1
- package/dist/src/atomic-replace.js +28 -3
- package/dist/src/atomic-replace.js.map +1 -1
- package/dist/src/bridge-journal.d.ts +32 -0
- package/dist/src/bridge-journal.d.ts.map +1 -0
- package/dist/src/bridge-journal.js +113 -0
- package/dist/src/bridge-journal.js.map +1 -0
- package/dist/src/cli.d.ts +7 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +534 -50
- package/dist/src/cli.js.map +1 -1
- package/dist/src/collection-notice.d.ts +34 -0
- package/dist/src/collection-notice.d.ts.map +1 -0
- package/dist/src/collection-notice.js +73 -0
- package/dist/src/collection-notice.js.map +1 -0
- package/dist/src/config.d.ts +39 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +187 -36
- package/dist/src/config.js.map +1 -1
- package/dist/src/connection-config.d.ts.map +1 -1
- package/dist/src/connection-config.js +5 -0
- package/dist/src/connection-config.js.map +1 -1
- package/dist/src/content-redaction.d.ts +17 -1
- package/dist/src/content-redaction.d.ts.map +1 -1
- package/dist/src/content-redaction.js +51 -3
- package/dist/src/content-redaction.js.map +1 -1
- package/dist/src/credential-store.d.ts +20 -1
- package/dist/src/credential-store.d.ts.map +1 -1
- package/dist/src/credential-store.js +29 -4
- package/dist/src/credential-store.js.map +1 -1
- package/dist/src/custom-agent-registry.d.ts.map +1 -1
- package/dist/src/custom-agent-registry.js +2 -1
- package/dist/src/custom-agent-registry.js.map +1 -1
- package/dist/src/discovery/adapters/generic.d.ts.map +1 -1
- package/dist/src/discovery/adapters/generic.js +160 -2
- package/dist/src/discovery/adapters/generic.js.map +1 -1
- package/dist/src/discovery/engine.d.ts +7 -1
- package/dist/src/discovery/engine.d.ts.map +1 -1
- package/dist/src/discovery/engine.js +21 -16
- package/dist/src/discovery/engine.js.map +1 -1
- package/dist/src/discovery/known-agent.d.ts +7 -0
- package/dist/src/discovery/known-agent.d.ts.map +1 -1
- package/dist/src/discovery/known-agent.js +9 -4
- package/dist/src/discovery/known-agent.js.map +1 -1
- package/dist/src/discovery/system-evidence.d.ts +40 -1
- package/dist/src/discovery/system-evidence.d.ts.map +1 -1
- package/dist/src/discovery/system-evidence.js +189 -16
- package/dist/src/discovery/system-evidence.js.map +1 -1
- package/dist/src/doctor-check.d.ts +51 -0
- package/dist/src/doctor-check.d.ts.map +1 -0
- package/dist/src/doctor-check.js +174 -0
- package/dist/src/doctor-check.js.map +1 -0
- package/dist/src/enroll.d.ts +86 -0
- package/dist/src/enroll.d.ts.map +1 -0
- package/dist/src/enroll.js +245 -0
- package/dist/src/enroll.js.map +1 -0
- package/dist/src/entry.d.ts +3 -0
- package/dist/src/entry.d.ts.map +1 -0
- package/dist/src/entry.js +14 -0
- package/dist/src/entry.js.map +1 -0
- package/dist/src/event-queue.d.ts +13 -1
- package/dist/src/event-queue.d.ts.map +1 -1
- package/dist/src/event-queue.js +137 -42
- package/dist/src/event-queue.js.map +1 -1
- package/dist/src/event-spool.d.ts +61 -3
- package/dist/src/event-spool.d.ts.map +1 -1
- package/dist/src/event-spool.js +341 -44
- package/dist/src/event-spool.js.map +1 -1
- package/dist/src/generated/phigon/v1/identity_pb.d.ts +7 -0
- package/dist/src/generated/phigon/v1/identity_pb.d.ts.map +1 -1
- package/dist/src/generated/phigon/v1/identity_pb.js +1 -1
- package/dist/src/generated/phigon/v1/identity_pb.js.map +1 -1
- package/dist/src/guardian-containment.d.ts +2 -6
- package/dist/src/guardian-containment.d.ts.map +1 -1
- package/dist/src/guardian-containment.js +12 -63
- package/dist/src/guardian-containment.js.map +1 -1
- package/dist/src/guardian-decision-journal.d.ts.map +1 -1
- package/dist/src/guardian-decision-journal.js +4 -19
- package/dist/src/guardian-decision-journal.js.map +1 -1
- package/dist/src/guardian-managed-exec.d.ts.map +1 -1
- package/dist/src/guardian-managed-exec.js +6 -18
- package/dist/src/guardian-managed-exec.js.map +1 -1
- package/dist/src/guardian-model-bridge.d.ts.map +1 -1
- package/dist/src/guardian-model-bridge.js +2 -1
- package/dist/src/guardian-model-bridge.js.map +1 -1
- package/dist/src/guardian-native.d.ts +0 -1
- package/dist/src/guardian-native.d.ts.map +1 -1
- package/dist/src/guardian-native.js +0 -6
- package/dist/src/guardian-native.js.map +1 -1
- package/dist/src/guardian-process-monitor.d.ts +4 -0
- package/dist/src/guardian-process-monitor.d.ts.map +1 -1
- package/dist/src/guardian-process-monitor.js +82 -26
- package/dist/src/guardian-process-monitor.js.map +1 -1
- package/dist/src/health.d.ts +19 -1
- package/dist/src/health.d.ts.map +1 -1
- package/dist/src/health.js +22 -1
- package/dist/src/health.js.map +1 -1
- package/dist/src/historical-identity-store.d.ts +8 -1
- package/dist/src/historical-identity-store.d.ts.map +1 -1
- package/dist/src/historical-identity-store.js +28 -10
- package/dist/src/historical-identity-store.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/installation.d.ts +2 -0
- package/dist/src/installation.d.ts.map +1 -1
- package/dist/src/installation.js +6 -3
- package/dist/src/installation.js.map +1 -1
- package/dist/src/instance-lock.d.ts +45 -0
- package/dist/src/instance-lock.d.ts.map +1 -0
- package/dist/src/instance-lock.js +243 -0
- package/dist/src/instance-lock.js.map +1 -0
- package/dist/src/machine-command-executor.d.ts +22 -7
- package/dist/src/machine-command-executor.d.ts.map +1 -1
- package/dist/src/machine-command-executor.js +80 -19
- package/dist/src/machine-command-executor.js.map +1 -1
- package/dist/src/node-version.d.ts +8 -0
- package/dist/src/node-version.d.ts.map +1 -0
- package/dist/src/node-version.js +17 -0
- package/dist/src/node-version.js.map +1 -0
- package/dist/src/observer-log.d.ts +50 -0
- package/dist/src/observer-log.d.ts.map +1 -0
- package/dist/src/observer-log.js +141 -0
- package/dist/src/observer-log.js.map +1 -0
- package/dist/src/pair.d.ts +68 -1
- package/dist/src/pair.d.ts.map +1 -1
- package/dist/src/pair.js +204 -65
- package/dist/src/pair.js.map +1 -1
- package/dist/src/private-directory.d.ts +10 -0
- package/dist/src/private-directory.d.ts.map +1 -0
- package/dist/src/private-directory.js +66 -0
- package/dist/src/private-directory.js.map +1 -0
- package/dist/src/process-tree.d.ts +73 -0
- package/dist/src/process-tree.d.ts.map +1 -0
- package/dist/src/process-tree.js +256 -0
- package/dist/src/process-tree.js.map +1 -0
- package/dist/src/proxy.d.ts +68 -0
- package/dist/src/proxy.d.ts.map +1 -0
- package/dist/src/proxy.js +209 -0
- package/dist/src/proxy.js.map +1 -0
- package/dist/src/purge.d.ts +17 -0
- package/dist/src/purge.d.ts.map +1 -0
- package/dist/src/purge.js +84 -0
- package/dist/src/purge.js.map +1 -0
- package/dist/src/remove-machine.d.ts +5 -1
- package/dist/src/remove-machine.d.ts.map +1 -1
- package/dist/src/remove-machine.js +28 -14
- package/dist/src/remove-machine.js.map +1 -1
- package/dist/src/server-authority.d.ts +29 -0
- package/dist/src/server-authority.d.ts.map +1 -0
- package/dist/src/server-authority.js +106 -0
- package/dist/src/server-authority.js.map +1 -0
- package/dist/src/server-clock.d.ts +32 -0
- package/dist/src/server-clock.d.ts.map +1 -0
- package/dist/src/server-clock.js +72 -0
- package/dist/src/server-clock.js.map +1 -0
- package/dist/src/service-command.d.ts +20 -0
- package/dist/src/service-command.d.ts.map +1 -0
- package/dist/src/service-command.js +136 -0
- package/dist/src/service-command.js.map +1 -0
- package/dist/src/service.d.ts +98 -0
- package/dist/src/service.d.ts.map +1 -0
- package/dist/src/service.js +472 -0
- package/dist/src/service.js.map +1 -0
- package/dist/src/stream-client.d.ts +21 -1
- package/dist/src/stream-client.d.ts.map +1 -1
- package/dist/src/stream-client.js +137 -24
- package/dist/src/stream-client.js.map +1 -1
- package/dist/src/stream-state.d.ts +2 -0
- package/dist/src/stream-state.d.ts.map +1 -1
- package/dist/src/stream-state.js +5 -2
- package/dist/src/stream-state.js.map +1 -1
- package/dist/src/system-inspection.d.ts +78 -0
- package/dist/src/system-inspection.d.ts.map +1 -0
- package/dist/src/system-inspection.js +281 -0
- package/dist/src/system-inspection.js.map +1 -0
- package/dist/src/update.d.ts.map +1 -1
- package/dist/src/update.js +9 -3
- package/dist/src/update.js.map +1 -1
- package/dist/src/user-notices.d.ts +64 -0
- package/dist/src/user-notices.d.ts.map +1 -0
- package/dist/src/user-notices.js +141 -0
- package/dist/src/user-notices.js.map +1 -0
- package/package.json +7 -7
- package/dist/src/windows-process.d.ts +0 -7
- package/dist/src/windows-process.d.ts.map +0 -1
- package/dist/src/windows-process.js +0 -18
- package/dist/src/windows-process.js.map +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ObserverLogLevel } from "./config.js";
|
|
2
|
+
/** The log folder in the data directory, and the file the observer writes. */
|
|
3
|
+
export declare const logDirectoryName = "logs";
|
|
4
|
+
export declare const logFileName = "observer.log";
|
|
5
|
+
/** About 5 MB per file and three files: the current one and two older ones. */
|
|
6
|
+
export declare const defaultLogFileBytes: number;
|
|
7
|
+
export declare const defaultLogFiles = 3;
|
|
8
|
+
export declare function logFilePath(dataDirectory: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* `PHIGON_LOG_LEVEL`, read before the rest of the configuration so that a
|
|
11
|
+
* configuration error is logged too. An invalid value logs at info; the
|
|
12
|
+
* configuration check then reports it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function logLevelFrom(value: string | undefined): ObserverLogLevel;
|
|
15
|
+
export type RotatingLogFileOptions = Readonly<{
|
|
16
|
+
maxBytes?: number;
|
|
17
|
+
files?: number;
|
|
18
|
+
now?: () => Date;
|
|
19
|
+
/** Told once when the file cannot be written; logging then stops. */
|
|
20
|
+
onFailure?: (message: string) => void;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* A log file in the data directory that a background service keeps, since
|
|
24
|
+
* a scheduled task or LaunchAgent throws its output away. It is written
|
|
25
|
+
* synchronously, one short line at a time, and rotated by size, so it never
|
|
26
|
+
* holds anything in memory and never grows past `files` × `maxBytes`. It
|
|
27
|
+
* only ever receives messages that are already safe to print.
|
|
28
|
+
*/
|
|
29
|
+
export declare class RotatingLogFile {
|
|
30
|
+
#private;
|
|
31
|
+
constructor(dataDirectory: string, options?: RotatingLogFileOptions);
|
|
32
|
+
get path(): string;
|
|
33
|
+
write(level: ObserverLogLevel, message: string): void;
|
|
34
|
+
}
|
|
35
|
+
export type LogOutput = Readonly<{
|
|
36
|
+
stdout: (value: string) => void;
|
|
37
|
+
stderr: (value: string) => void;
|
|
38
|
+
}>;
|
|
39
|
+
/** `LogOutput` that also takes errors and debug details. */
|
|
40
|
+
export type LevelledOutput = LogOutput & Readonly<{
|
|
41
|
+
error: (value: string) => void;
|
|
42
|
+
debug: (value: string) => void;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Applies `PHIGON_LOG_LEVEL` to what a command prints and copies each
|
|
46
|
+
* message it keeps to the log file: status lines are info, problems on
|
|
47
|
+
* stderr are warnings, and the message a command fails with is an error.
|
|
48
|
+
*/
|
|
49
|
+
export declare function levelledOutput(output: LogOutput, level: ObserverLogLevel, log?: RotatingLogFile): LevelledOutput;
|
|
50
|
+
//# sourceMappingURL=observer-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observer-log.d.ts","sourceRoot":"","sources":["../../src/observer-log.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,SAAS,CAAC;AACvC,eAAO,MAAM,WAAW,iBAAiB,CAAC;AAC1C,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,QAAkB,CAAC;AACnD,eAAO,MAAM,eAAe,IAAI,CAAC;AAUjC,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,CAExE;AAED,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,qEAAqE;IACrE,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,qBAAa,eAAe;;gBAUd,aAAa,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B;IASvE,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,KAAK,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;CAsDtD;AASD,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC;IAC/B,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,CAAC,CAAC;AAEH,4DAA4D;AAC5D,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,CAAC;IAChD,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC,CAAC,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,gBAAgB,EACvB,GAAG,CAAC,EAAE,eAAe,GACpB,cAAc,CAchB"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { atomicReplaceSync } from "./atomic-replace.js";
|
|
4
|
+
import { makeDirectoryPrivateSync } from "./private-directory.js";
|
|
5
|
+
/** The log folder in the data directory, and the file the observer writes. */
|
|
6
|
+
export const logDirectoryName = "logs";
|
|
7
|
+
export const logFileName = "observer.log";
|
|
8
|
+
/** About 5 MB per file and three files: the current one and two older ones. */
|
|
9
|
+
export const defaultLogFileBytes = 5 * 1024 * 1024;
|
|
10
|
+
export const defaultLogFiles = 3;
|
|
11
|
+
const maxLineBytes = 4 * 1024;
|
|
12
|
+
const severity = Object.freeze({
|
|
13
|
+
debug: 10,
|
|
14
|
+
info: 20,
|
|
15
|
+
warn: 30,
|
|
16
|
+
error: 40,
|
|
17
|
+
});
|
|
18
|
+
export function logFilePath(dataDirectory) {
|
|
19
|
+
return join(dataDirectory, logDirectoryName, logFileName);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* `PHIGON_LOG_LEVEL`, read before the rest of the configuration so that a
|
|
23
|
+
* configuration error is logged too. An invalid value logs at info; the
|
|
24
|
+
* configuration check then reports it.
|
|
25
|
+
*/
|
|
26
|
+
export function logLevelFrom(value) {
|
|
27
|
+
return value === "debug" || value === "warn" || value === "error" ? value : "info";
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A log file in the data directory that a background service keeps, since
|
|
31
|
+
* a scheduled task or LaunchAgent throws its output away. It is written
|
|
32
|
+
* synchronously, one short line at a time, and rotated by size, so it never
|
|
33
|
+
* holds anything in memory and never grows past `files` × `maxBytes`. It
|
|
34
|
+
* only ever receives messages that are already safe to print.
|
|
35
|
+
*/
|
|
36
|
+
export class RotatingLogFile {
|
|
37
|
+
#dataDirectory;
|
|
38
|
+
#path;
|
|
39
|
+
#maxBytes;
|
|
40
|
+
#files;
|
|
41
|
+
#now;
|
|
42
|
+
#onFailure;
|
|
43
|
+
#size;
|
|
44
|
+
#failed = false;
|
|
45
|
+
constructor(dataDirectory, options = {}) {
|
|
46
|
+
this.#dataDirectory = dataDirectory;
|
|
47
|
+
this.#path = logFilePath(dataDirectory);
|
|
48
|
+
this.#maxBytes = options.maxBytes ?? defaultLogFileBytes;
|
|
49
|
+
this.#files = Math.max(1, options.files ?? defaultLogFiles);
|
|
50
|
+
this.#now = options.now ?? (() => new Date());
|
|
51
|
+
this.#onFailure = options.onFailure;
|
|
52
|
+
}
|
|
53
|
+
get path() {
|
|
54
|
+
return this.#path;
|
|
55
|
+
}
|
|
56
|
+
write(level, message) {
|
|
57
|
+
if (this.#failed)
|
|
58
|
+
return;
|
|
59
|
+
const stamp = this.#now().toISOString();
|
|
60
|
+
const label = level.toUpperCase().padEnd(5);
|
|
61
|
+
const text = message
|
|
62
|
+
.split(/\r?\n/u)
|
|
63
|
+
.map(clean)
|
|
64
|
+
.filter((line) => line.length > 0)
|
|
65
|
+
.map((line) => `${stamp} ${label} ${line}\n`)
|
|
66
|
+
.join("");
|
|
67
|
+
if (text.length === 0)
|
|
68
|
+
return;
|
|
69
|
+
try {
|
|
70
|
+
if (this.#size === undefined)
|
|
71
|
+
this.#size = this.#open();
|
|
72
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
73
|
+
if (this.#size > 0 && this.#size + bytes > this.#maxBytes) {
|
|
74
|
+
this.#rotate();
|
|
75
|
+
this.#size = 0;
|
|
76
|
+
}
|
|
77
|
+
appendFileSync(this.#path, text, { encoding: "utf8", mode: 0o600 });
|
|
78
|
+
this.#size += bytes;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// The observer keeps running without its log; say so once.
|
|
82
|
+
this.#failed = true;
|
|
83
|
+
this.#onFailure?.(`The observer log could not be written to ${this.#path}; messages are no longer logged there.\n`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
#open() {
|
|
87
|
+
const directory = join(this.#dataDirectory, logDirectoryName);
|
|
88
|
+
if (!existsSync(this.#dataDirectory)) {
|
|
89
|
+
mkdirSync(this.#dataDirectory, { recursive: true, mode: 0o700 });
|
|
90
|
+
}
|
|
91
|
+
makeDirectoryPrivateSync(this.#dataDirectory);
|
|
92
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
93
|
+
try {
|
|
94
|
+
return statSync(this.#path).size;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (error.code === "ENOENT")
|
|
98
|
+
return 0;
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
#rotate() {
|
|
103
|
+
for (let index = this.#files - 1; index >= 1; index--) {
|
|
104
|
+
const from = index === 1 ? this.#path : `${this.#path}.${index - 1}`;
|
|
105
|
+
if (existsSync(from))
|
|
106
|
+
atomicReplaceSync(from, `${this.#path}.${index}`);
|
|
107
|
+
}
|
|
108
|
+
// With a single file there is nothing to keep: start it again.
|
|
109
|
+
if (this.#files === 1 && existsSync(this.#path)) {
|
|
110
|
+
appendFileSync(this.#path, "", { flag: "w" });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Drops control characters, such as terminal colour codes, and caps the length. */
|
|
115
|
+
function clean(line) {
|
|
116
|
+
const printable = line.replace(/[\u0000-\u0008\u000b-\u001f\u007f\u009b]/gu, "").trimEnd();
|
|
117
|
+
if (Buffer.byteLength(printable, "utf8") <= maxLineBytes)
|
|
118
|
+
return printable;
|
|
119
|
+
return `${Buffer.from(printable, "utf8").subarray(0, maxLineBytes).toString("utf8").replace(/�$/u, "")}…`;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Applies `PHIGON_LOG_LEVEL` to what a command prints and copies each
|
|
123
|
+
* message it keeps to the log file: status lines are info, problems on
|
|
124
|
+
* stderr are warnings, and the message a command fails with is an error.
|
|
125
|
+
*/
|
|
126
|
+
export function levelledOutput(output, level, log) {
|
|
127
|
+
const emit = (messageLevel, write) => (value) => {
|
|
128
|
+
if (severity[messageLevel] < severity[level])
|
|
129
|
+
return;
|
|
130
|
+
// The log first: a background service's console may already be gone.
|
|
131
|
+
log?.write(messageLevel, value);
|
|
132
|
+
write(value);
|
|
133
|
+
};
|
|
134
|
+
return Object.freeze({
|
|
135
|
+
stdout: emit("info", output.stdout),
|
|
136
|
+
stderr: emit("warn", output.stderr),
|
|
137
|
+
error: emit("error", output.stderr),
|
|
138
|
+
debug: emit("debug", output.stdout),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=observer-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observer-log.js","sourceRoot":"","sources":["../../src/observer-log.ts"],"names":[],"mappings":"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"}
|
package/dist/src/pair.d.ts
CHANGED
|
@@ -1,14 +1,43 @@
|
|
|
1
1
|
import type { ObserverConfig } from "./config.js";
|
|
2
|
-
import { type CredentialStore } from "./credential-store.js";
|
|
2
|
+
import { type CredentialStore, type StoredMachineCredential } from "./credential-store.js";
|
|
3
3
|
export declare const OBSERVER_CONSENT_POLICY_VERSION = "observer-policy-v1";
|
|
4
4
|
export type ConsentProfile = "metadata" | "full";
|
|
5
|
+
export type PairingMachine = Readonly<{
|
|
6
|
+
id: string;
|
|
7
|
+
tenant_id: string;
|
|
8
|
+
display_name: string;
|
|
9
|
+
state: string;
|
|
10
|
+
}>;
|
|
11
|
+
/** What the pairing exchange and an enrollment both answer with. */
|
|
12
|
+
export type ConnectedMachine = Readonly<{
|
|
13
|
+
machine: PairingMachine;
|
|
14
|
+
credential: string;
|
|
15
|
+
}>;
|
|
5
16
|
export type PairResult = Readonly<{
|
|
6
17
|
machineId: string;
|
|
7
18
|
displayName: string;
|
|
8
19
|
state: string;
|
|
9
20
|
controlPlaneOrigin: string;
|
|
10
21
|
credentialStored: true;
|
|
22
|
+
/**
|
|
23
|
+
* The earlier machine this computer was paired as, when the workspace no
|
|
24
|
+
* longer accepted its key and the computer was paired as a new machine.
|
|
25
|
+
*/
|
|
26
|
+
replacedMachineId?: string;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* One exchange with the control plane. A failure other than a stored key
|
|
30
|
+
* the server refused is `credentialRejected: false`, and its error is
|
|
31
|
+
* thrown as is.
|
|
32
|
+
*/
|
|
33
|
+
export type ExchangeOutcome<T extends ConnectedMachine> = Readonly<{
|
|
34
|
+
paired: T;
|
|
35
|
+
}> | Readonly<{
|
|
36
|
+
error: Error;
|
|
37
|
+
credentialRejected: boolean;
|
|
11
38
|
}>;
|
|
39
|
+
/** The consent evidence a connection stores with the machine credential. */
|
|
40
|
+
export type ConsentEvidence = Pick<StoredMachineCredential, "consentProfile" | "consentPolicyVersion" | "consentedAt" | "consentSource" | "enrollmentCodeId" | "enrollmentCodeLabel">;
|
|
12
41
|
export type PairOptions = Readonly<{
|
|
13
42
|
config: ObserverConfig;
|
|
14
43
|
code: string;
|
|
@@ -18,4 +47,42 @@ export type PairOptions = Readonly<{
|
|
|
18
47
|
fetcher?: typeof fetch;
|
|
19
48
|
}>;
|
|
20
49
|
export declare function pairMachine(options: PairOptions): Promise<PairResult>;
|
|
50
|
+
export type ConnectOptions<T extends ConnectedMachine> = Readonly<{
|
|
51
|
+
config: ObserverConfig;
|
|
52
|
+
endpoint: URL;
|
|
53
|
+
fetcher: typeof fetch;
|
|
54
|
+
credentialStore?: CredentialStore;
|
|
55
|
+
/** Exchanges a code for this installation, with the stored key if any. */
|
|
56
|
+
exchange: (installationID: string, existingCredential?: string) => Promise<ExchangeOutcome<T>>;
|
|
57
|
+
/** The consent evidence to store with the credential `paired` returned. */
|
|
58
|
+
evidence: (paired: T, at: string) => ConsentEvidence;
|
|
59
|
+
}>;
|
|
60
|
+
/**
|
|
61
|
+
* Connects this computer with a code, keeping its machine when the server
|
|
62
|
+
* still accepts its stored key: pairing again, or enrolling, renews consent
|
|
63
|
+
* and keeps the same machine, so its queued activity stays deliverable.
|
|
64
|
+
* When the server no longer accepts the key, the computer connects as a
|
|
65
|
+
* new machine and its old queue moves aside.
|
|
66
|
+
*/
|
|
67
|
+
export declare function connectMachine<T extends ConnectedMachine>(options: ConnectOptions<T>): Promise<Readonly<{
|
|
68
|
+
paired: T;
|
|
69
|
+
result: PairResult;
|
|
70
|
+
}>>;
|
|
71
|
+
/**
|
|
72
|
+
* Whether a failed exchange refused the stored key. The server answers a
|
|
73
|
+
* key it no longer accepts with HTTP 400 and "machine credential is
|
|
74
|
+
* invalid".
|
|
75
|
+
*/
|
|
76
|
+
export declare function credentialRejected(status: number, failure: Readonly<{
|
|
77
|
+
code?: string;
|
|
78
|
+
message?: string;
|
|
79
|
+
}>): boolean;
|
|
80
|
+
/** A response with a machine and a machine credential. */
|
|
81
|
+
export declare function isConnectedMachine(value: unknown): value is ConnectedMachine;
|
|
82
|
+
/** The `{ error: { code, message } }` body of a failed control-plane call. */
|
|
83
|
+
export declare function apiError(response: Response): Promise<Readonly<{
|
|
84
|
+
code?: string;
|
|
85
|
+
message?: string;
|
|
86
|
+
}>>;
|
|
87
|
+
export declare function normalizedOperatingSystem(): "macos" | "linux" | "windows";
|
|
21
88
|
//# sourceMappingURL=pair.d.ts.map
|
package/dist/src/pair.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pair.d.ts","sourceRoot":"","sources":["../../src/pair.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,
|
|
1
|
+
{"version":3,"file":"pair.d.ts","sourceRoot":"","sources":["../../src/pair.ts"],"names":[],"mappings":"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"}
|
package/dist/src/pair.js
CHANGED
|
@@ -1,25 +1,141 @@
|
|
|
1
1
|
import { arch, hostname, platform } from "node:os";
|
|
2
|
-
import { FileCredentialStore, } from "./credential-store.js";
|
|
3
|
-
import {
|
|
2
|
+
import { FileCredentialStore, credentialFileName, } from "./credential-store.js";
|
|
3
|
+
import { retirePairingState } from "./event-spool.js";
|
|
4
|
+
import { installationFileName, loadOrCreateInstallationID } from "./installation.js";
|
|
5
|
+
import { InstanceLock } from "./instance-lock.js";
|
|
4
6
|
export const OBSERVER_CONSENT_POLICY_VERSION = "observer-policy-v1";
|
|
5
7
|
export async function pairMachine(options) {
|
|
6
8
|
const code = options.code.trim();
|
|
7
9
|
if (code.length === 0) {
|
|
8
10
|
throw new Error("Pairing code is required.");
|
|
9
11
|
}
|
|
10
|
-
const installationID = await loadOrCreateInstallationID(options.config.dataDirectory);
|
|
11
12
|
if (options.config.observabilityProfile === "full" &&
|
|
12
13
|
options.consentProfile !== "full") {
|
|
13
14
|
throw new Error("The configured full observability profile requires full-content consent.");
|
|
14
15
|
}
|
|
15
16
|
const endpoint = new URL("/v1/pairing/exchange", options.config.controlPlaneUrl);
|
|
16
17
|
const fetcher = options.fetcher ?? fetch;
|
|
18
|
+
const connected = await connectMachine({
|
|
19
|
+
config: options.config,
|
|
20
|
+
endpoint,
|
|
21
|
+
fetcher,
|
|
22
|
+
...(options.credentialStore === undefined ? {} : { credentialStore: options.credentialStore }),
|
|
23
|
+
exchange: (installationID, existingCredential) => exchangePairing(fetcher, endpoint, {
|
|
24
|
+
code,
|
|
25
|
+
installation_id: installationID,
|
|
26
|
+
display_name: options.displayName?.trim() || hostname(),
|
|
27
|
+
hostname: hostname(),
|
|
28
|
+
operating_system: normalizedOperatingSystem(),
|
|
29
|
+
architecture: arch(),
|
|
30
|
+
consent: {
|
|
31
|
+
granted: true,
|
|
32
|
+
profile: options.consentProfile,
|
|
33
|
+
policy_version: OBSERVER_CONSENT_POLICY_VERSION,
|
|
34
|
+
},
|
|
35
|
+
...(existingCredential === undefined ? {} : { existing_credential: existingCredential }),
|
|
36
|
+
}),
|
|
37
|
+
evidence: (_paired, at) => ({
|
|
38
|
+
consentProfile: options.consentProfile,
|
|
39
|
+
consentPolicyVersion: OBSERVER_CONSENT_POLICY_VERSION,
|
|
40
|
+
consentedAt: at,
|
|
41
|
+
consentSource: "owner",
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
return connected.result;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Connects this computer with a code, keeping its machine when the server
|
|
48
|
+
* still accepts its stored key: pairing again, or enrolling, renews consent
|
|
49
|
+
* and keeps the same machine, so its queued activity stays deliverable.
|
|
50
|
+
* When the server no longer accepts the key, the computer connects as a
|
|
51
|
+
* new machine and its old queue moves aside.
|
|
52
|
+
*/
|
|
53
|
+
export async function connectMachine(options) {
|
|
54
|
+
const dataDirectory = options.config.dataDirectory;
|
|
55
|
+
const installationID = await loadOrCreateInstallationID(dataDirectory);
|
|
17
56
|
const credentialStore = options.credentialStore ??
|
|
18
|
-
new FileCredentialStore(
|
|
57
|
+
new FileCredentialStore(dataDirectory);
|
|
19
58
|
const previous = await credentialStore.load();
|
|
20
|
-
if (previous !== null && previous.controlPlaneOrigin !== endpoint.origin) {
|
|
59
|
+
if (previous !== null && previous.controlPlaneOrigin !== options.endpoint.origin) {
|
|
21
60
|
throw new Error("The stored machine belongs to a different control plane. Use a separate data directory to pair another workspace.");
|
|
22
61
|
}
|
|
62
|
+
let paired;
|
|
63
|
+
if (previous !== null) {
|
|
64
|
+
const renewal = await options.exchange(installationID, previous.credential);
|
|
65
|
+
if ("paired" in renewal) {
|
|
66
|
+
paired = renewal.paired;
|
|
67
|
+
}
|
|
68
|
+
else if (!renewal.credentialRejected) {
|
|
69
|
+
throw renewal.error;
|
|
70
|
+
}
|
|
71
|
+
else if (await credentialStillAccepted(options.fetcher, options.config.controlPlaneUrl, previous.credential)) {
|
|
72
|
+
// The exchange also refuses a key that is valid in another workspace.
|
|
73
|
+
throw new Error("This computer is paired with another workspace on this server. Use a separate data directory to pair another workspace.");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
let lock;
|
|
77
|
+
try {
|
|
78
|
+
if (paired === undefined || !sameMachine(previous, paired)) {
|
|
79
|
+
// A new machine. Its queue, stream cursor and credential start empty;
|
|
80
|
+
// the observer must not be using the old ones while they move.
|
|
81
|
+
lock = await InstanceLock.acquire(dataDirectory);
|
|
82
|
+
// The old credential goes with its queue: it is the only key that can
|
|
83
|
+
// decrypt it. Once it is gone, a retry after a lost response pairs
|
|
84
|
+
// without it, and the server answers with the same new machine.
|
|
85
|
+
//
|
|
86
|
+
// A key the server no longer accepts also retires its installation ID:
|
|
87
|
+
// a server can still hold that ID for the deleted machine, and the
|
|
88
|
+
// built-in SQLite server refuses to pair it again. A new ID pairs
|
|
89
|
+
// cleanly everywhere.
|
|
90
|
+
const replacing = paired === undefined && previous !== null;
|
|
91
|
+
retirePairingState(dataDirectory, previous?.machineId, [
|
|
92
|
+
credentialFileName,
|
|
93
|
+
...(replacing ? [installationFileName] : []),
|
|
94
|
+
]);
|
|
95
|
+
if (paired === undefined) {
|
|
96
|
+
const freshInstallationID = replacing
|
|
97
|
+
? await loadOrCreateInstallationID(dataDirectory)
|
|
98
|
+
: installationID;
|
|
99
|
+
const fresh = await options.exchange(freshInstallationID);
|
|
100
|
+
if (!("paired" in fresh))
|
|
101
|
+
throw fresh.error;
|
|
102
|
+
paired = fresh.paired;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const pairedAt = new Date().toISOString();
|
|
106
|
+
await credentialStore.save({
|
|
107
|
+
machineId: paired.machine.id,
|
|
108
|
+
tenantId: paired.machine.tenant_id,
|
|
109
|
+
credential: paired.credential,
|
|
110
|
+
controlPlaneOrigin: options.config.controlPlaneUrl.origin,
|
|
111
|
+
grpcOrigin: options.config.grpcUrl.origin,
|
|
112
|
+
createdAt: pairedAt,
|
|
113
|
+
...options.evidence(paired, pairedAt),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
await lock?.release();
|
|
118
|
+
}
|
|
119
|
+
const replaced = previous !== null && previous.machineId !== paired.machine.id;
|
|
120
|
+
return Object.freeze({
|
|
121
|
+
paired,
|
|
122
|
+
result: Object.freeze({
|
|
123
|
+
machineId: paired.machine.id,
|
|
124
|
+
displayName: paired.machine.display_name,
|
|
125
|
+
state: paired.machine.state,
|
|
126
|
+
controlPlaneOrigin: options.config.controlPlaneUrl.origin,
|
|
127
|
+
credentialStored: true,
|
|
128
|
+
...(replaced ? { replacedMachineId: previous.machineId } : {}),
|
|
129
|
+
}),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function sameMachine(previous, paired) {
|
|
133
|
+
return previous !== null &&
|
|
134
|
+
previous.machineId === paired.machine.id &&
|
|
135
|
+
previous.tenantId === paired.machine.tenant_id &&
|
|
136
|
+
previous.credential === paired.credential;
|
|
137
|
+
}
|
|
138
|
+
async function exchangePairing(fetcher, endpoint, body) {
|
|
23
139
|
let response;
|
|
24
140
|
try {
|
|
25
141
|
response = await fetcher(endpoint, {
|
|
@@ -28,20 +144,7 @@ export async function pairMachine(options) {
|
|
|
28
144
|
"Content-Type": "application/json",
|
|
29
145
|
Accept: "application/json",
|
|
30
146
|
},
|
|
31
|
-
body: JSON.stringify(
|
|
32
|
-
code,
|
|
33
|
-
...(previous === null ? {} : { existing_credential: previous.credential }),
|
|
34
|
-
installation_id: installationID,
|
|
35
|
-
display_name: options.displayName?.trim() || hostname(),
|
|
36
|
-
hostname: hostname(),
|
|
37
|
-
operating_system: normalizedOperatingSystem(),
|
|
38
|
-
architecture: arch(),
|
|
39
|
-
consent: {
|
|
40
|
-
granted: true,
|
|
41
|
-
profile: options.consentProfile,
|
|
42
|
-
policy_version: OBSERVER_CONSENT_POLICY_VERSION,
|
|
43
|
-
},
|
|
44
|
-
}),
|
|
147
|
+
body: JSON.stringify(body),
|
|
45
148
|
redirect: "error",
|
|
46
149
|
signal: AbortSignal.timeout(10_000),
|
|
47
150
|
});
|
|
@@ -53,7 +156,11 @@ export async function pairMachine(options) {
|
|
|
53
156
|
throw new Error(`Could not reach ${endpoint.origin}. Check your internet connection and try again.`);
|
|
54
157
|
}
|
|
55
158
|
if (!response.ok) {
|
|
56
|
-
|
|
159
|
+
const failure = await apiError(response);
|
|
160
|
+
return {
|
|
161
|
+
error: new Error(failure.message ?? `Pairing failed with HTTP ${response.status}.`),
|
|
162
|
+
credentialRejected: credentialRejected(response.status, failure),
|
|
163
|
+
};
|
|
57
164
|
}
|
|
58
165
|
let value;
|
|
59
166
|
try {
|
|
@@ -64,66 +171,98 @@ export async function pairMachine(options) {
|
|
|
64
171
|
// print fragments. The server supports retries for this installation.
|
|
65
172
|
throw new Error("The pairing response was incomplete or invalid. Retry with the same code before it expires; keep the existing data directory.");
|
|
66
173
|
}
|
|
67
|
-
|
|
68
|
-
await credentialStore.save({
|
|
69
|
-
machineId: paired.machine.id,
|
|
70
|
-
tenantId: paired.machine.tenant_id,
|
|
71
|
-
credential: paired.credential,
|
|
72
|
-
controlPlaneOrigin: options.config.controlPlaneUrl.origin,
|
|
73
|
-
grpcOrigin: options.config.grpcUrl.origin,
|
|
74
|
-
createdAt: new Date().toISOString(),
|
|
75
|
-
consentProfile: options.consentProfile,
|
|
76
|
-
consentPolicyVersion: OBSERVER_CONSENT_POLICY_VERSION,
|
|
77
|
-
consentedAt: new Date().toISOString(),
|
|
78
|
-
});
|
|
79
|
-
return Object.freeze({
|
|
80
|
-
machineId: paired.machine.id,
|
|
81
|
-
displayName: paired.machine.display_name,
|
|
82
|
-
state: paired.machine.state,
|
|
83
|
-
controlPlaneOrigin: options.config.controlPlaneUrl.origin,
|
|
84
|
-
credentialStored: true,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
function parsePairingResponse(value) {
|
|
88
|
-
if (typeof value !== "object" ||
|
|
89
|
-
value === null ||
|
|
90
|
-
!("credential" in value) ||
|
|
91
|
-
typeof value.credential !== "string" ||
|
|
92
|
-
!value.credential.startsWith("phg_m_") ||
|
|
93
|
-
!("machine" in value) ||
|
|
94
|
-
typeof value.machine !== "object" ||
|
|
95
|
-
value.machine === null ||
|
|
96
|
-
!("id" in value.machine) ||
|
|
97
|
-
typeof value.machine.id !== "string" ||
|
|
98
|
-
!("tenant_id" in value.machine) ||
|
|
99
|
-
typeof value.machine.tenant_id !== "string" ||
|
|
100
|
-
!("display_name" in value.machine) ||
|
|
101
|
-
typeof value.machine.display_name !== "string" ||
|
|
102
|
-
!("state" in value.machine) ||
|
|
103
|
-
typeof value.machine.state !== "string") {
|
|
174
|
+
if (!isConnectedMachine(value)) {
|
|
104
175
|
throw new Error("Control plane returned an invalid pairing response.");
|
|
105
176
|
}
|
|
106
|
-
return value;
|
|
177
|
+
return { paired: value };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Whether a failed exchange refused the stored key. The server answers a
|
|
181
|
+
* key it no longer accepts with HTTP 400 and "machine credential is
|
|
182
|
+
* invalid".
|
|
183
|
+
*/
|
|
184
|
+
export function credentialRejected(status, failure) {
|
|
185
|
+
return failure.code === "invalid_credential" ||
|
|
186
|
+
(status === 400 &&
|
|
187
|
+
/^machine credential is (?:invalid|revoked)\b/iu.test(failure.message ?? ""));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Whether a server still accepts a stored key. Only a key the server
|
|
191
|
+
* itself calls invalid or revoked, because an administrator removed the
|
|
192
|
+
* computer or revoked the key, may be set aside for a new pairing.
|
|
193
|
+
*/
|
|
194
|
+
async function credentialStillAccepted(fetcher, controlPlaneUrl, credential) {
|
|
195
|
+
const endpoint = new URL("/v1/machine/heartbeat", controlPlaneUrl);
|
|
196
|
+
let response;
|
|
197
|
+
try {
|
|
198
|
+
response = await fetcher(endpoint, {
|
|
199
|
+
method: "POST",
|
|
200
|
+
headers: {
|
|
201
|
+
Authorization: `Bearer ${credential}`,
|
|
202
|
+
Accept: "application/json",
|
|
203
|
+
},
|
|
204
|
+
redirect: "error",
|
|
205
|
+
signal: AbortSignal.timeout(10_000),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
throw new Error(`Could not reach ${endpoint.origin} to check this computer's earlier pairing. Check your internet connection and try again.`);
|
|
210
|
+
}
|
|
211
|
+
if (response.ok) {
|
|
212
|
+
await response.body?.cancel();
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
if (response.status === 401 &&
|
|
216
|
+
(await apiError(response)).code === "invalid_credential") {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
throw new Error(`Could not check this computer's earlier pairing (HTTP ${response.status}). Try again.`);
|
|
220
|
+
}
|
|
221
|
+
/** A response with a machine and a machine credential. */
|
|
222
|
+
export function isConnectedMachine(value) {
|
|
223
|
+
return typeof value === "object" &&
|
|
224
|
+
value !== null &&
|
|
225
|
+
"credential" in value &&
|
|
226
|
+
typeof value.credential === "string" &&
|
|
227
|
+
value.credential.startsWith("phg_m_") &&
|
|
228
|
+
"machine" in value &&
|
|
229
|
+
typeof value.machine === "object" &&
|
|
230
|
+
value.machine !== null &&
|
|
231
|
+
"id" in value.machine &&
|
|
232
|
+
typeof value.machine.id === "string" &&
|
|
233
|
+
"tenant_id" in value.machine &&
|
|
234
|
+
typeof value.machine.tenant_id === "string" &&
|
|
235
|
+
"display_name" in value.machine &&
|
|
236
|
+
typeof value.machine.display_name === "string" &&
|
|
237
|
+
"state" in value.machine &&
|
|
238
|
+
typeof value.machine.state === "string";
|
|
107
239
|
}
|
|
108
|
-
|
|
240
|
+
/** The `{ error: { code, message } }` body of a failed control-plane call. */
|
|
241
|
+
export async function apiError(response) {
|
|
109
242
|
try {
|
|
110
243
|
const value = await response.json();
|
|
111
244
|
if (typeof value === "object" &&
|
|
112
245
|
value !== null &&
|
|
113
246
|
"error" in value &&
|
|
114
247
|
typeof value.error === "object" &&
|
|
115
|
-
value.error !== null
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
248
|
+
value.error !== null) {
|
|
249
|
+
const error = value.error;
|
|
250
|
+
return {
|
|
251
|
+
...("code" in error && typeof error.code === "string"
|
|
252
|
+
? { code: error.code }
|
|
253
|
+
: {}),
|
|
254
|
+
...("message" in error && typeof error.message === "string"
|
|
255
|
+
? { message: error.message }
|
|
256
|
+
: {}),
|
|
257
|
+
};
|
|
119
258
|
}
|
|
120
259
|
}
|
|
121
260
|
catch {
|
|
122
261
|
// The status code still provides an actionable, non-sensitive failure.
|
|
123
262
|
}
|
|
124
|
-
return
|
|
263
|
+
return {};
|
|
125
264
|
}
|
|
126
|
-
function normalizedOperatingSystem() {
|
|
265
|
+
export function normalizedOperatingSystem() {
|
|
127
266
|
const value = platform();
|
|
128
267
|
if (value === "darwin")
|
|
129
268
|
return "macos";
|