@planu/cli 5.3.27 → 5.3.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cli/commands/doctor.d.ts +4 -10
  3. package/dist/cli/commands/doctor.js +5 -31
  4. package/dist/cli/commands/inspect.js +2 -14
  5. package/dist/cli/commands/offline.d.ts +1 -2
  6. package/dist/cli/commands/offline.js +24 -41
  7. package/dist/config/release-policy.json +1 -79
  8. package/dist/engine/auto-updater/release-verifier.js +17 -35
  9. package/dist/engine/core-bridge.d.ts +6 -23
  10. package/dist/engine/core-bridge.js +35 -376
  11. package/dist/engine/execution/job-runtime.d.ts +0 -11
  12. package/dist/engine/execution/job-runtime.js +0 -23
  13. package/dist/engine/living-spec/annotation-parser.js +0 -14
  14. package/dist/engine/project-graph/native.js +2 -2
  15. package/dist/engine/sandbox/profiles/macos.sb +33 -0
  16. package/dist/engine/scan-project/module-discoverer.js +10 -53
  17. package/dist/engine/validation/validation-receipt.d.ts +5 -0
  18. package/dist/engine/validation/validation-receipt.js +9 -1
  19. package/dist/engine/validator/deep-code-checker.js +19 -26
  20. package/dist/security/adapters/macos-keychain.d.ts +5 -3
  21. package/dist/security/adapters/macos-keychain.js +79 -18
  22. package/dist/tools/create-spec/autopilot-analyzer.d.ts +1 -1
  23. package/dist/tools/create-spec/autopilot-analyzer.js +2 -2
  24. package/dist/tools/create-spec/relevance-scorer.d.ts +1 -1
  25. package/dist/tools/create-spec/relevance-scorer.js +1 -17
  26. package/dist/tools/create-spec.js +1 -1
  27. package/dist/types/atomic-upgrade.d.ts +0 -2
  28. package/dist/types/core-bridge.d.ts +0 -108
  29. package/dist/types/core-bridge.js +1 -1
  30. package/dist/types/durable-job.d.ts +0 -40
  31. package/dist/types/security.d.ts +0 -5
  32. package/package.json +7 -43
  33. package/planu-plugin.json +1 -1
  34. package/dist/config/native-release-public-key.pem +0 -3
  35. package/dist/config/native-targets.json +0 -87
  36. package/dist/engine/execution/job-executor.d.ts +0 -16
  37. package/dist/engine/execution/job-executor.js +0 -140
  38. package/dist/engine/execution/native-relevance-scorer.d.ts +0 -6
  39. package/dist/engine/execution/native-relevance-scorer.js +0 -64
  40. package/dist/engine/execution/native-worker-dispatch.d.ts +0 -3
  41. package/dist/engine/execution/native-worker-dispatch.js +0 -46
  42. package/dist/engine/execution/native-worker-protocol.d.ts +0 -6
  43. package/dist/engine/execution/native-worker-protocol.js +0 -33
  44. package/dist/engine/execution/native-worker-runtime.d.ts +0 -4
  45. package/dist/engine/execution/native-worker-runtime.js +0 -177
  46. package/dist/engine/execution/native-worker-thread.d.ts +0 -2
  47. package/dist/engine/execution/native-worker-thread.js +0 -66
  48. package/dist/engine/macos-keychain-native-port.d.ts +0 -10
  49. package/dist/engine/macos-keychain-native-port.js +0 -64
  50. package/dist/engine/native-capability-policy.d.ts +0 -4
  51. package/dist/engine/native-capability-policy.js +0 -24
  52. package/dist/engine/native-platform.d.ts +0 -3
  53. package/dist/engine/native-platform.js +0 -44
  54. package/dist/engine/native-runtime-handshake.d.ts +0 -20
  55. package/dist/engine/native-runtime-handshake.js +0 -230
  56. package/dist/engine/planu-core.darwin-arm64.node.manifest.json +0 -39
  57. package/dist/engine/planu-core.darwin-arm64.node.sbom.json +0 -1310
  58. package/dist/engine/planu-core.darwin-x64.node.manifest.json +0 -39
  59. package/dist/engine/planu-core.darwin-x64.node.sbom.json +0 -1310
  60. package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +0 -38
  61. package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +0 -1310
  62. package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +0 -38
  63. package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +0 -1310
  64. package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +0 -38
  65. package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +0 -1310
  66. package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +0 -38
  67. package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +0 -1310
  68. package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +0 -38
  69. package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +0 -1310
  70. package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +0 -38
  71. package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +0 -1310
  72. package/dist/types/native-worker.d.ts +0 -48
  73. package/dist/types/native-worker.js +0 -2
  74. package/planu-native.json +0 -82
@@ -1,177 +0,0 @@
1
- import { randomBytes, randomUUID } from 'node:crypto';
2
- import { Worker } from 'node:worker_threads';
3
- import { nativeWorkerSchemaHash, NATIVE_WORKER_OPERATIONS, } from './native-worker-protocol.js';
4
- import { NATIVE_CAPABILITY_ABI, NATIVE_PROTOCOL_ABI, RUNTIME_STORAGE_SCHEMA, } from '../native-runtime-handshake.js';
5
- import { getExecutionContext } from './context.js';
6
- import { redactFailureMessage, reportClassifiedDegradation, } from '../../errors/classified-degradation.js';
7
- function validateCall(call) {
8
- if (call.signal?.aborted) {
9
- throw call.signal.reason instanceof Error
10
- ? call.signal.reason
11
- : new Error('[Planu] Native worker cancelled');
12
- }
13
- if (!call.projectId.trim() || !Number.isSafeInteger(call.deadlineAt)) {
14
- throw new Error('[Planu] Native worker call requires projectId and an absolute deadline');
15
- }
16
- if (!NATIVE_WORKER_OPERATIONS.includes(call.operation)) {
17
- throw new Error('[Planu] Native worker operation is not allowlisted');
18
- }
19
- if (Date.now() >= call.deadlineAt) {
20
- throw new Error('[Planu] Native worker deadline already expired');
21
- }
22
- }
23
- function validateHandshake(handshake, operation) {
24
- const capabilities = new Set(handshake.capabilities);
25
- if (handshake.protocolAbi !== NATIVE_PROTOCOL_ABI ||
26
- handshake.capabilityAbi !== NATIVE_CAPABILITY_ABI ||
27
- handshake.storageSchema !== RUNTIME_STORAGE_SCHEMA ||
28
- handshake.schemaHash !== nativeWorkerSchemaHash() ||
29
- capabilities.size !== NATIVE_WORKER_OPERATIONS.length ||
30
- !NATIVE_WORKER_OPERATIONS.every((capability) => capabilities.has(capability)) ||
31
- !capabilities.has(operation) ||
32
- typeof handshake.engineVersion !== 'string' ||
33
- handshake.engineVersion.trim() === '' ||
34
- typeof handshake.target !== 'string' ||
35
- handshake.target.trim() === '' ||
36
- typeof handshake.artifactHash !== 'string' ||
37
- !/^[a-f0-9]{64}$/.test(handshake.artifactHash) ||
38
- !handshake.compatible) {
39
- throw new Error('[Planu] Native worker handshake is incompatible');
40
- }
41
- }
42
- // eslint-disable-next-line max-lines-per-function -- worker lifecycle keeps cancellation and identity fencing in one closure
43
- export async function runNativeWorkerOperation(call, workerFactory = (url, options) => new Worker(url, {
44
- ...options,
45
- execArgv: process.execArgv.filter((argument) => !argument.startsWith('--input-type')),
46
- })) {
47
- const parent = getExecutionContext();
48
- const deadlineAt = Math.min(call.deadlineAt, parent?.deadlineAt ?? Number.POSITIVE_INFINITY);
49
- const cancellation = linkCancellation(call.signal, parent?.signal);
50
- try {
51
- validateCall({ ...call, deadlineAt, signal: cancellation.signal });
52
- }
53
- catch (error) {
54
- cancellation.dispose();
55
- throw error;
56
- }
57
- const secret = randomBytes(32).toString('hex');
58
- const requestId = call.requestId ?? randomUUID();
59
- const cancellationId = call.cancellationId ?? randomUUID();
60
- const worker = workerFactory(new URL('./native-worker-thread.js', import.meta.url), {
61
- workerData: { secret },
62
- });
63
- return new Promise((resolve, reject) => {
64
- let settled = false;
65
- let requestSent = false;
66
- const timeoutMs = Math.max(1, deadlineAt - Date.now());
67
- const finish = (error, value) => {
68
- if (settled) {
69
- return;
70
- }
71
- settled = true;
72
- clearTimeout(timer);
73
- cancellation.signal.removeEventListener('abort', abort);
74
- cancellation.dispose();
75
- void worker.terminate();
76
- if (error) {
77
- reject(error);
78
- }
79
- else {
80
- resolve(value);
81
- }
82
- };
83
- const abort = () => {
84
- const reason = cancellation.signal.reason;
85
- finish(reason instanceof Error ? reason : new Error('[Planu] Native worker cancelled'));
86
- };
87
- const timer = setTimeout(() => {
88
- finish(new Error('[Planu] Native worker deadline exceeded'));
89
- }, timeoutMs);
90
- cancellation.signal.addEventListener('abort', abort, { once: true });
91
- if (cancellation.signal.aborted) {
92
- abort();
93
- return;
94
- }
95
- worker.once('error', (error) => {
96
- finish(error instanceof Error ? error : new Error(String(error)));
97
- });
98
- worker.once('exit', (code) => {
99
- if (!settled && code !== 0) {
100
- finish(new Error(`[Planu] Native worker exited with code ${String(code)}`));
101
- }
102
- });
103
- worker.on('message', (value) => {
104
- if (!value || typeof value !== 'object' || !('kind' in value)) {
105
- finish(new Error('[Planu] Native worker sent a malformed message'));
106
- return;
107
- }
108
- const message = value;
109
- if (message.kind === 'handshake') {
110
- try {
111
- if (requestSent) {
112
- throw new Error('[Planu] Native worker sent a duplicate handshake');
113
- }
114
- validateHandshake(message, call.operation);
115
- const request = {
116
- kind: 'request',
117
- secret,
118
- requestId,
119
- projectId: call.projectId,
120
- deadlineAt,
121
- cancellationId,
122
- operation: call.operation,
123
- payload: call.payload,
124
- };
125
- requestSent = true;
126
- worker.postMessage(request);
127
- }
128
- catch (error) {
129
- /* reliability-optional: NATIVE_WORKER_MESSAGE_FAILURE — promise rejects through finish */
130
- reportClassifiedDegradation('NATIVE_WORKER_MESSAGE_FAILURE', error);
131
- finish(new Error(redactFailureMessage(error)));
132
- }
133
- return;
134
- }
135
- if (!requestSent || message.requestId !== requestId) {
136
- finish(new Error('[Planu] Native worker response identity mismatch'));
137
- return;
138
- }
139
- if (message.kind === 'error') {
140
- finish(new Error(message.message));
141
- }
142
- else {
143
- finish(undefined, message.value);
144
- }
145
- });
146
- });
147
- }
148
- function linkCancellation(child, parent) {
149
- const controller = new AbortController();
150
- const signals = [child, parent].filter((signal) => signal !== undefined);
151
- const abort = (signal) => {
152
- if (!controller.signal.aborted) {
153
- controller.abort(signal.reason);
154
- }
155
- };
156
- const listeners = signals.map((signal) => {
157
- const listener = () => {
158
- abort(signal);
159
- };
160
- if (signal.aborted) {
161
- abort(signal);
162
- }
163
- else {
164
- signal.addEventListener('abort', listener, { once: true });
165
- }
166
- return { signal, listener };
167
- });
168
- return {
169
- signal: controller.signal,
170
- dispose: () => {
171
- for (const listener of listeners) {
172
- listener.signal.removeEventListener('abort', listener.listener);
173
- }
174
- },
175
- };
176
- }
177
- //# sourceMappingURL=native-worker-runtime.js.map
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=native-worker-thread.d.ts.map
@@ -1,66 +0,0 @@
1
- import { parentPort, workerData } from 'node:worker_threads';
2
- import { isNativeActive, nativeLoadDiagnostic, NATIVE_CAPABILITY_ABI, NATIVE_PROTOCOL_ABI, RUNTIME_STORAGE_SCHEMA, } from '../core-bridge.js';
3
- import { PLANU_VERSION } from '../../config/version.js';
4
- import { redactFailureMessage, reportClassifiedDegradation, } from '../../errors/classified-degradation.js';
5
- import { isNativeWorkerOperation, nativeWorkerSchemaHash, NATIVE_WORKER_OPERATIONS, } from './native-worker-protocol.js';
6
- import { dispatchNativeWorkerRequest } from './native-worker-dispatch.js';
7
- const bootstrap = workerData;
8
- const bootstrapSecret = bootstrap && typeof bootstrap === 'object' && 'secret' in bootstrap
9
- ? bootstrap.secret
10
- : undefined;
11
- if (!parentPort || typeof bootstrapSecret !== 'string' || bootstrapSecret.length < 32) {
12
- throw new Error('[Planu] Native worker bootstrap authentication failed');
13
- }
14
- const port = parentPort;
15
- isNativeActive();
16
- const diagnostic = nativeLoadDiagnostic();
17
- const nativeHandshake = diagnostic.compatibility?.handshake ?? null;
18
- port.postMessage({
19
- kind: 'handshake',
20
- protocolAbi: NATIVE_PROTOCOL_ABI,
21
- capabilityAbi: NATIVE_CAPABILITY_ABI,
22
- storageSchema: RUNTIME_STORAGE_SCHEMA,
23
- schemaHash: nativeWorkerSchemaHash(),
24
- capabilities: NATIVE_WORKER_OPERATIONS,
25
- engineVersion: nativeHandshake?.engineVersion ?? PLANU_VERSION,
26
- target: nativeHandshake?.target ?? null,
27
- artifactHash: diagnostic.artifactSha256 ?? null,
28
- compatible: diagnostic.compatibility?.compatible === true,
29
- });
30
- port.on('message', (value) => {
31
- const request = value;
32
- const requestId = typeof request.requestId === 'string' ? request.requestId : 'invalid';
33
- const respond = (response) => {
34
- port.postMessage(response);
35
- };
36
- try {
37
- if (request.kind !== 'request' ||
38
- request.secret !== bootstrapSecret ||
39
- !isNativeWorkerOperation(request.operation) ||
40
- typeof request.projectId !== 'string' ||
41
- request.projectId.trim() === '' ||
42
- typeof request.cancellationId !== 'string' ||
43
- request.cancellationId.trim() === '' ||
44
- typeof request.deadlineAt !== 'number' ||
45
- Date.now() >= request.deadlineAt ||
46
- !request.payload ||
47
- typeof request.payload !== 'object') {
48
- throw new Error('[Planu] Native worker rejected malformed or expired request');
49
- }
50
- respond({
51
- kind: 'result',
52
- requestId,
53
- value: dispatchNativeWorkerRequest(request),
54
- });
55
- }
56
- catch (error) {
57
- /* reliability-optional: NATIVE_WORKER_OPERATION_FAILURE — typed error response is emitted */
58
- reportClassifiedDegradation('NATIVE_WORKER_OPERATION_FAILURE', error);
59
- respond({
60
- kind: 'error',
61
- requestId,
62
- message: redactFailureMessage(error),
63
- });
64
- }
65
- });
66
- //# sourceMappingURL=native-worker-thread.js.map
@@ -1,10 +0,0 @@
1
- import type { NativeRuntimeCompatibility, PlanuCore } from '../types/core-bridge.js';
2
- import type { MacOSKeychainNativePort } from '../types/security.js';
3
- export declare class ValidationReceiptSignerUnavailableError extends Error {
4
- readonly reason: 'PLATFORM_UNSUPPORTED' | 'NATIVE_DISABLED' | 'ARTIFACT_MISSING' | 'STALE_ARTIFACT' | 'NONCANONICAL_ORIGIN' | 'CAPABILITY_MISSING' | 'PORT_INCOMPLETE' | 'OVERRIDE_REJECTED' | 'PROVIDER_NONCANONICAL' | 'PROVIDER_UNAVAILABLE';
5
- readonly code: 'VALIDATION_RECEIPT_SIGNER_UNAVAILABLE';
6
- constructor(reason: 'PLATFORM_UNSUPPORTED' | 'NATIVE_DISABLED' | 'ARTIFACT_MISSING' | 'STALE_ARTIFACT' | 'NONCANONICAL_ORIGIN' | 'CAPABILITY_MISSING' | 'PORT_INCOMPLETE' | 'OVERRIDE_REJECTED' | 'PROVIDER_NONCANONICAL' | 'PROVIDER_UNAVAILABLE', remediation?: string);
7
- }
8
- /** Build the privileged main-thread port only from a canonical, verified native module. */
9
- export declare function createMacOSKeychainNativePort(platform: NodeJS.Platform, overrideDefined: boolean, disabled: boolean, nativeModule: PlanuCore | null, origin: 'override' | 'local' | 'package' | undefined, capabilities: readonly string[] | undefined, compatibility?: NativeRuntimeCompatibility): MacOSKeychainNativePort;
10
- //# sourceMappingURL=macos-keychain-native-port.d.ts.map
@@ -1,64 +0,0 @@
1
- import { PLANU_VERSION } from '../config/version.js';
2
- const DEFAULT_REMEDIATION = 'Restart the MCP host and retry.';
3
- /** Recover the native target triple from a buildId shaped `<epoch>-<engineVersion>-<triple>`. */
4
- function deriveNativeTargetTriple(buildId, engineVersion) {
5
- const separator = `-${engineVersion}-`;
6
- const index = buildId.indexOf(separator);
7
- if (index === -1) {
8
- return null;
9
- }
10
- const triple = buildId.slice(index + separator.length);
11
- return triple.length > 0 ? triple : null;
12
- }
13
- function staleArtifactRemediation(artifactVersion, buildId) {
14
- const triple = buildId ? deriveNativeTargetTriple(buildId, artifactVersion) : null;
15
- if (!triple) {
16
- return (`Native artifact reports engineVersion ${artifactVersion}, expected ${PLANU_VERSION}. ` +
17
- 'Could not determine the native target triple from the artifact build id; run: ' +
18
- 'bash scripts/build-rust-local.sh --host after manually removing the stale native artifact for your platform.');
19
- }
20
- return (`Native artifact reports engineVersion ${artifactVersion}, expected ${PLANU_VERSION}. Run: ` +
21
- `cargo clean -p planu-core --release --target ${triple} --manifest-path rust/planu-core/Cargo.toml && bash scripts/build-rust-local.sh --host`);
22
- }
23
- export class ValidationReceiptSignerUnavailableError extends Error {
24
- reason;
25
- code = 'VALIDATION_RECEIPT_SIGNER_UNAVAILABLE';
26
- constructor(reason, remediation = DEFAULT_REMEDIATION) {
27
- super(`[Planu] VALIDATION_RECEIPT_SIGNER_UNAVAILABLE:${reason} ${remediation}`);
28
- this.reason = reason;
29
- this.name = 'ValidationReceiptSignerUnavailableError';
30
- }
31
- }
32
- /** Build the privileged main-thread port only from a canonical, verified native module. */
33
- export function createMacOSKeychainNativePort(platform, overrideDefined, disabled, nativeModule, origin, capabilities, compatibility) {
34
- if (platform !== 'darwin') {
35
- throw new ValidationReceiptSignerUnavailableError('PLATFORM_UNSUPPORTED');
36
- }
37
- if (overrideDefined || origin === 'override') {
38
- throw new ValidationReceiptSignerUnavailableError('OVERRIDE_REJECTED');
39
- }
40
- if (disabled) {
41
- throw new ValidationReceiptSignerUnavailableError('NATIVE_DISABLED');
42
- }
43
- if (!nativeModule) {
44
- if (compatibility && !compatibility.compatible) {
45
- const artifactVersion = compatibility.handshake?.engineVersion ?? 'unknown';
46
- throw new ValidationReceiptSignerUnavailableError('STALE_ARTIFACT', staleArtifactRemediation(artifactVersion, compatibility.handshake?.buildId));
47
- }
48
- throw new ValidationReceiptSignerUnavailableError('ARTIFACT_MISSING');
49
- }
50
- if (origin !== 'local' && origin !== 'package') {
51
- throw new ValidationReceiptSignerUnavailableError('NONCANONICAL_ORIGIN');
52
- }
53
- if (!capabilities?.includes('macos-keychain-v1')) {
54
- throw new ValidationReceiptSignerUnavailableError('CAPABILITY_MISSING');
55
- }
56
- const set = nativeModule.macosKeychainSet?.bind(nativeModule);
57
- const get = nativeModule.macosKeychainGet?.bind(nativeModule);
58
- const remove = nativeModule.macosKeychainDelete?.bind(nativeModule);
59
- if (!set || !get || !remove) {
60
- throw new ValidationReceiptSignerUnavailableError('PORT_INCOMPLETE');
61
- }
62
- return { set, get, delete: remove };
63
- }
64
- //# sourceMappingURL=macos-keychain-native-port.js.map
@@ -1,4 +0,0 @@
1
- import type { NativeCapabilityPolicy, NativeFallbackMode } from '../types/core-bridge.js';
2
- export declare const NATIVE_CAPABILITY_POLICIES: readonly NativeCapabilityPolicy[];
3
- export declare function nativeFallbackMode(capability: string): NativeFallbackMode | null;
4
- //# sourceMappingURL=native-capability-policy.d.ts.map
@@ -1,24 +0,0 @@
1
- export const NATIVE_CAPABILITY_POLICIES = [
2
- {
3
- capability: 'core-acceleration-v1',
4
- classification: 'optimization',
5
- fallback: 'typescript-visible-degradation',
6
- parityContract: 'tests/integration/native-platform-matrix.test.ts',
7
- },
8
- {
9
- capability: 'project-graph-v1',
10
- classification: 'optimization',
11
- fallback: 'typescript-visible-degradation',
12
- parityContract: 'tests/integration/native-project-graph-e2e.test.ts',
13
- },
14
- {
15
- capability: 'macos-keychain-v1',
16
- classification: 'correctness-critical',
17
- fallback: 'fail-closed',
18
- parityContract: 'tests/security/macos-keychain.integration.test.ts',
19
- },
20
- ];
21
- export function nativeFallbackMode(capability) {
22
- return (NATIVE_CAPABILITY_POLICIES.find((policy) => policy.capability === capability)?.fallback ?? null);
23
- }
24
- //# sourceMappingURL=native-capability-policy.js.map
@@ -1,3 +0,0 @@
1
- /** Detects musl through independent local signals and reports failed probes. */
2
- export declare function detectMusl(): boolean;
3
- //# sourceMappingURL=native-platform.d.ts.map
@@ -1,44 +0,0 @@
1
- import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
- import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
3
- let cachedMusl;
4
- function probeMuslSignal(code, probe) {
5
- try {
6
- return probe();
7
- }
8
- catch (error) {
9
- void error;
10
- /* reliability-optional: NATIVE_LIBC_PROBE_FAILED */
11
- reportClassifiedDegradation(code, new Error('Native libc probe unavailable'));
12
- return false;
13
- }
14
- }
15
- /** Detects musl through independent local signals and reports failed probes. */
16
- export function detectMusl() {
17
- if (cachedMusl !== undefined) {
18
- return cachedMusl;
19
- }
20
- if (process.platform !== 'linux') {
21
- cachedMusl = false;
22
- return cachedMusl;
23
- }
24
- if (probeMuslSignal('NATIVE_LIBC_ALPINE_PROBE_FAILED', () => existsSync('/etc/alpine-release'))) {
25
- cachedMusl = true;
26
- return cachedMusl;
27
- }
28
- if (probeMuslSignal('NATIVE_LIBC_REPORT_PROBE_FAILED', () => {
29
- const reportApi = process.report;
30
- const report = reportApi?.getReport();
31
- return (report !== undefined &&
32
- /musl/iu.test(typeof report === 'string' ? report : JSON.stringify(report)));
33
- })) {
34
- cachedMusl = true;
35
- return cachedMusl;
36
- }
37
- if (probeMuslSignal('NATIVE_LIBC_LDD_PROBE_FAILED', () => existsSync('/usr/bin/ldd') && /musl/iu.test(readFileSync('/usr/bin/ldd', 'utf8')))) {
38
- cachedMusl = true;
39
- return cachedMusl;
40
- }
41
- cachedMusl = probeMuslSignal('NATIVE_LIBC_LINKER_PROBE_FAILED', () => readdirSync('/lib').some((entry) => entry.startsWith('ld-musl-')));
42
- return cachedMusl;
43
- }
44
- //# sourceMappingURL=native-platform.js.map
@@ -1,20 +0,0 @@
1
- import type { NativeArtifactManifest, NativeRuntimeCompatibility, NativeRuntimeHandshake } from '../types/core-bridge.js';
2
- export declare const NATIVE_PROTOCOL_ABI = 1;
3
- export declare const NATIVE_CAPABILITY_ABI = 2;
4
- export declare const NATIVE_NODE_API_ABI = 4;
5
- /** Must match RuntimeDatabase.schemaVersion and the Rust/native release contract. */
6
- export declare const RUNTIME_STORAGE_SCHEMA = 4;
7
- export declare function inspectNativeArtifact(artifactPath: string, trustedPublicKeyInput?: string | Buffer): {
8
- artifactSha256: string;
9
- manifest: NativeArtifactManifest | null;
10
- };
11
- export declare function negotiateNativeRuntime(handshake: NativeRuntimeHandshake | null, manifest: NativeArtifactManifest | null, expected: {
12
- cliVersion: string;
13
- target: string;
14
- artifactSha256: string;
15
- protocolAbi?: number;
16
- capabilityAbi?: number;
17
- nodeApiAbi?: number;
18
- storageSchema?: number;
19
- }): NativeRuntimeCompatibility;
20
- //# sourceMappingURL=native-runtime-handshake.d.ts.map
@@ -1,230 +0,0 @@
1
- /* eslint-disable complexity -- the fail-closed manifest boundary validates every signed field */
2
- /* eslint-disable @typescript-eslint/no-unnecessary-condition -- Partial models untrusted JSON at runtime */
3
- import { createHash, createPublicKey, verify as verifySignature } from 'node:crypto';
4
- import { lstatSync, readFileSync } from 'node:fs';
5
- import { basename, dirname, join } from 'node:path';
6
- import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
7
- export const NATIVE_PROTOCOL_ABI = 1;
8
- export const NATIVE_CAPABILITY_ABI = 2;
9
- export const NATIVE_NODE_API_ABI = 4;
10
- /** Must match RuntimeDatabase.schemaVersion and the Rust/native release contract. */
11
- export const RUNTIME_STORAGE_SCHEMA = 4;
12
- export function inspectNativeArtifact(artifactPath, trustedPublicKeyInput) {
13
- const hash = (value) => createHash('sha256').update(value).digest('hex');
14
- let artifactSha256 = '';
15
- const manifestPath = `${artifactPath}.manifest.json`;
16
- try {
17
- const currentUid = process.getuid?.();
18
- for (const path of [artifactPath, manifestPath]) {
19
- const stat = lstatSync(path);
20
- if (stat.isSymbolicLink() ||
21
- (process.platform !== 'win32' &&
22
- ((currentUid !== undefined && stat.uid !== currentUid) || (stat.mode & 0o022) !== 0))) {
23
- return { artifactSha256, manifest: null };
24
- }
25
- }
26
- artifactSha256 = hash(readFileSync(artifactPath));
27
- const parsed = JSON.parse(readFileSync(manifestPath, 'utf8'));
28
- if (typeof parsed !== 'object' ||
29
- parsed === null ||
30
- !('schemaVersion' in parsed) ||
31
- parsed.schemaVersion !== 2 ||
32
- !('artifactSha256' in parsed) ||
33
- typeof parsed.artifactSha256 !== 'string' ||
34
- !('capabilities' in parsed) ||
35
- !Array.isArray(parsed.capabilities) ||
36
- !parsed.capabilities.every((capability) => typeof capability === 'string')) {
37
- return { artifactSha256, manifest: null };
38
- }
39
- const candidate = parsed;
40
- const expectedSbomName = `${basename(artifactPath)}.sbom.json`;
41
- const exactSemver = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/u;
42
- const expectedCapabilities = [
43
- 'core-acceleration-v1',
44
- 'project-graph-v1',
45
- ...(candidate.platform === 'darwin' ? ['macos-keychain-v1'] : []),
46
- ];
47
- if (!exactSemver.test(candidate.cliVersion ?? '') ||
48
- !exactSemver.test(candidate.engineVersion ?? '') ||
49
- !/^[A-Za-z0-9._:-]{1,200}$/u.test(candidate.buildId ?? '') ||
50
- candidate.protocolAbi !== NATIVE_PROTOCOL_ABI ||
51
- candidate.capabilityAbi !== NATIVE_CAPABILITY_ABI ||
52
- candidate.nodeApiAbi !== NATIVE_NODE_API_ABI ||
53
- !Number.isSafeInteger(candidate.storageSchemaMin) ||
54
- !Number.isSafeInteger(candidate.storageSchemaMax) ||
55
- (candidate.storageSchemaMin ?? 0) < 1 ||
56
- (candidate.storageSchemaMax ?? 0) < (candidate.storageSchemaMin ?? 0) ||
57
- !['darwin', 'linux', 'win32'].includes(candidate.platform ?? '') ||
58
- !['arm64', 'x64'].includes(candidate.arch ?? '') ||
59
- (candidate.platform === 'linux'
60
- ? !['glibc', 'musl'].includes(candidate.libc ?? '')
61
- : candidate.libc !== null) ||
62
- candidate.target !==
63
- [candidate.platform, candidate.arch, candidate.libc].filter(Boolean).join('-') ||
64
- JSON.stringify(candidate.capabilities) !== JSON.stringify(expectedCapabilities) ||
65
- !/^[a-f0-9]{64}$/u.test(candidate.artifactSha256 ?? '') ||
66
- candidate.sbom?.format !== 'CycloneDX-1.5' ||
67
- candidate.sbom.path !== expectedSbomName ||
68
- !/^[a-f0-9]{64}$/u.test(candidate.sbom.sha256 ?? '') ||
69
- !/^[a-f0-9]{40}$/u.test(candidate.provenance?.sourceCommit ?? '') ||
70
- typeof candidate.provenance?.sourceTreeDirty !== 'boolean' ||
71
- !Number.isSafeInteger(candidate.provenance?.sourceDateEpoch) ||
72
- (candidate.provenance?.sourceDateEpoch ?? -1) < 0 ||
73
- !candidate.provenance?.builder ||
74
- candidate.provenance.builder.length > 256 ||
75
- !candidate.provenance.rustToolchain ||
76
- (candidate.provenance.zigVersion !== null &&
77
- typeof candidate.provenance.zigVersion !== 'string') ||
78
- candidate.signature?.algorithm !== 'Ed25519' ||
79
- !/^[a-f0-9]{64}$/u.test(candidate.signature.keyId ?? '') ||
80
- !/^[A-Za-z0-9+/]{86}==$/u.test(candidate.signature.value ?? '')) {
81
- return { artifactSha256, manifest: null };
82
- }
83
- const sbomPath = join(dirname(artifactPath), expectedSbomName);
84
- const sbomStat = lstatSync(sbomPath);
85
- if (sbomStat.isSymbolicLink() ||
86
- (process.platform !== 'win32' &&
87
- ((process.getuid?.() !== undefined && sbomStat.uid !== process.getuid?.()) ||
88
- (sbomStat.mode & 0o022) !== 0)) ||
89
- hash(readFileSync(sbomPath)) !== candidate.sbom.sha256) {
90
- return { artifactSha256, manifest: null };
91
- }
92
- const sbomValue = JSON.parse(readFileSync(sbomPath, 'utf8'));
93
- if (!isValidSbom(sbomValue, candidate.engineVersion, artifactSha256)) {
94
- return { artifactSha256, manifest: null };
95
- }
96
- const { signature, ...unsigned } = candidate;
97
- const publicKey = createPublicKey(trustedPublicKeyInput ??
98
- readFileSync(join(import.meta.dirname, '..', 'config', 'native-release-public-key.pem')));
99
- const publicDer = publicKey.export({ type: 'spki', format: 'der' });
100
- if (hash(publicDer) !== signature.keyId ||
101
- !verifySignature(null, Buffer.from(JSON.stringify(unsigned)), publicKey, Buffer.from(signature.value, 'base64'))) {
102
- return { artifactSha256, manifest: null };
103
- }
104
- return { artifactSha256, manifest: candidate };
105
- }
106
- catch (error) {
107
- void error;
108
- /* reliability-optional: NATIVE_ARTIFACT_INSPECTION_FAILED */
109
- reportClassifiedDegradation('NATIVE_ARTIFACT_INSPECTION_FAILED', new Error('Native artifact inspection failed closed'));
110
- return { artifactSha256, manifest: null };
111
- }
112
- }
113
- function isValidSbom(value, engineVersion, artifactSha256) {
114
- if (typeof value !== 'object' || value === null) {
115
- return false;
116
- }
117
- const sbom = value;
118
- const componentsValid = Array.isArray(sbom.components) &&
119
- sbom.components.length > 0 &&
120
- sbom.components.every((component) => typeof component === 'object' &&
121
- component !== null &&
122
- typeof component['bom-ref'] === 'string' &&
123
- typeof component.name === 'string' &&
124
- typeof component.version === 'string');
125
- const dependenciesValid = Array.isArray(sbom.dependencies) &&
126
- sbom.dependencies.some((dependency) => typeof dependency === 'object' &&
127
- dependency !== null &&
128
- Array.isArray(dependency.dependsOn));
129
- const hashes = sbom.metadata?.component?.hashes;
130
- const artifactBound = Array.isArray(hashes) &&
131
- hashes.some((entry) => typeof entry === 'object' &&
132
- entry !== null &&
133
- entry.alg === 'SHA-256' &&
134
- entry.content === artifactSha256);
135
- return (sbom.bomFormat === 'CycloneDX' &&
136
- sbom.specVersion === '1.5' &&
137
- sbom.version === 1 &&
138
- sbom.metadata?.component?.name === 'planu-core' &&
139
- sbom.metadata.component.version === engineVersion &&
140
- componentsValid &&
141
- dependenciesValid &&
142
- artifactBound);
143
- }
144
- export function negotiateNativeRuntime(handshake, manifest, expected) {
145
- if (!handshake) {
146
- return {
147
- compatible: false,
148
- reasons: ['native runtime does not expose planuRuntimeHandshake'],
149
- handshake: null,
150
- };
151
- }
152
- if (!manifest) {
153
- return {
154
- compatible: false,
155
- reasons: ['native artifact manifest is missing, unsafe or invalid'],
156
- handshake,
157
- };
158
- }
159
- const reasons = [];
160
- const protocolAbi = expected.protocolAbi ?? NATIVE_PROTOCOL_ABI;
161
- const capabilityAbi = expected.capabilityAbi ?? NATIVE_CAPABILITY_ABI;
162
- const nodeApiAbi = expected.nodeApiAbi ?? NATIVE_NODE_API_ABI;
163
- const storageSchema = expected.storageSchema ?? RUNTIME_STORAGE_SCHEMA;
164
- const handshakeCapabilitiesValid = Array.isArray(handshake.capabilities) &&
165
- handshake.capabilities.every((capability) => typeof capability === 'string');
166
- const manifestCapabilitiesValid = Array.isArray(manifest.capabilities) &&
167
- manifest.capabilities.every((capability) => typeof capability === 'string');
168
- if (manifest.cliVersion !== expected.cliVersion) {
169
- reasons.push(`CLI version mismatch: expected ${expected.cliVersion}, got ${manifest.cliVersion}`);
170
- }
171
- if (handshake.engineVersion !== expected.cliVersion) {
172
- reasons.push(`engine version mismatch: expected ${expected.cliVersion}, got ${handshake.engineVersion}`);
173
- }
174
- if (handshake.engineVersion !== manifest.engineVersion) {
175
- reasons.push('native engine version does not match artifact manifest');
176
- }
177
- if (handshake.buildId !== manifest.buildId) {
178
- reasons.push('native build identity does not match artifact manifest');
179
- }
180
- if (handshake.protocolAbi !== protocolAbi) {
181
- reasons.push(`protocol ABI mismatch: expected ${protocolAbi}, got ${handshake.protocolAbi}`);
182
- }
183
- if (handshake.protocolAbi !== manifest.protocolAbi) {
184
- reasons.push('native protocol ABI does not match artifact manifest');
185
- }
186
- if (handshake.capabilityAbi !== capabilityAbi) {
187
- reasons.push(`capability ABI mismatch: expected ${capabilityAbi}, got ${handshake.capabilityAbi}`);
188
- }
189
- if (handshake.capabilityAbi !== manifest.capabilityAbi) {
190
- reasons.push('native capability ABI does not match artifact manifest');
191
- }
192
- if (handshake.nodeApiAbi !== nodeApiAbi) {
193
- reasons.push(`Node-API ABI mismatch: expected ${nodeApiAbi}, got ${handshake.nodeApiAbi}`);
194
- }
195
- if (handshake.nodeApiAbi !== manifest.nodeApiAbi) {
196
- reasons.push('native Node-API ABI does not match artifact manifest');
197
- }
198
- if (!handshakeCapabilitiesValid) {
199
- reasons.push('native runtime capabilities are malformed');
200
- }
201
- if (!manifestCapabilitiesValid) {
202
- reasons.push('native artifact manifest capabilities are malformed');
203
- }
204
- if (handshakeCapabilitiesValid &&
205
- manifestCapabilitiesValid &&
206
- JSON.stringify(handshake.capabilities) !== JSON.stringify(manifest.capabilities)) {
207
- reasons.push('native capabilities do not match artifact manifest');
208
- }
209
- if (storageSchema < handshake.storageSchemaMin || storageSchema > handshake.storageSchemaMax) {
210
- reasons.push(`storage schema ${storageSchema} is outside native range ${handshake.storageSchemaMin}-${handshake.storageSchemaMax}`);
211
- }
212
- if (handshake.storageSchemaMin !== manifest.storageSchemaMin ||
213
- handshake.storageSchemaMax !== manifest.storageSchemaMax) {
214
- reasons.push('native storage schema range does not match artifact manifest');
215
- }
216
- if (handshake.target !== expected.target) {
217
- reasons.push(`target mismatch: expected ${expected.target}, got ${handshake.target}`);
218
- }
219
- if (handshake.target !== manifest.target) {
220
- reasons.push('native target does not match artifact manifest');
221
- }
222
- if (manifest.target !== [manifest.platform, manifest.arch, manifest.libc].filter(Boolean).join('-')) {
223
- reasons.push('native manifest target does not match platform/arch/libc dimensions');
224
- }
225
- if (manifest.artifactSha256 !== expected.artifactSha256) {
226
- reasons.push('native artifact SHA-256 mismatch');
227
- }
228
- return { compatible: reasons.length === 0, reasons, handshake };
229
- }
230
- //# sourceMappingURL=native-runtime-handshake.js.map