@letterblack/lbe-core 1.3.3 → 1.3.4

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 (75) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +130 -442
  3. package/assets/runtime-boundary.svg +36 -36
  4. package/dist/cli.js +141 -0
  5. package/dist/index.js +52 -0
  6. package/{release-exec/dist → dist}/wasm.lock.json +5 -4
  7. package/package.json +23 -54
  8. package/types.d.ts +2 -175
  9. package/.githooks/pre-commit +0 -2
  10. package/.githooks/pre-push +0 -2
  11. package/CHANGELOG.md +0 -75
  12. package/Release-README.md +0 -65
  13. package/WORKSPACE.md +0 -422
  14. package/_proof.mjs +0 -246
  15. package/bin/lbe.js +0 -12
  16. package/config/identity.config.json +0 -3
  17. package/config/policy.default.json +0 -24
  18. package/dist/cli/lbe.js +0 -4274
  19. package/dist/hooks/register.cjs +0 -505
  20. package/dist/state/appendCentral.cjs +0 -87
  21. package/dist/state/index.cjs +0 -101
  22. package/exec/cli.js +0 -472
  23. package/exec/index.js +0 -2
  24. package/index.js +0 -24
  25. package/lbe.audit.jsonl +0 -46
  26. package/release/README.md +0 -216
  27. package/release/TRUST.md +0 -90
  28. package/release/exec-README.md +0 -215
  29. package/release/exec-types.d.ts +0 -50
  30. package/release-exec/LICENSE +0 -1
  31. package/release-exec/README.md +0 -215
  32. package/release-exec/assets/lbe-gates.jpg +0 -0
  33. package/release-exec/assets/lbe-gates.png +0 -0
  34. package/release-exec/assets/runtime-boundary.svg +0 -36
  35. package/release-exec/assets/story-allow.jpg +0 -0
  36. package/release-exec/assets/story-allow.png +0 -0
  37. package/release-exec/assets/story-deny.jpg +0 -0
  38. package/release-exec/assets/story-deny.png +0 -0
  39. package/release-exec/dist/cli.js +0 -2841
  40. package/release-exec/dist/index.js +0 -1835
  41. package/release-exec/hooks/register.cjs +0 -473
  42. package/release-exec/package.json +0 -35
  43. package/release-exec/types.d.ts +0 -50
  44. package/runtime/engine.js +0 -322
  45. package/runtime/lbe_engine.wasm +0 -0
  46. package/src/cli/commands/auditVerify.js +0 -36
  47. package/src/cli/commands/dryrun.js +0 -175
  48. package/src/cli/commands/health.js +0 -153
  49. package/src/cli/commands/init.js +0 -306
  50. package/src/cli/commands/integrityCheck.js +0 -57
  51. package/src/cli/commands/logs.js +0 -53
  52. package/src/cli/commands/openState.js +0 -44
  53. package/src/cli/commands/policyAdd.js +0 -8
  54. package/src/cli/commands/policyMode.js +0 -7
  55. package/src/cli/commands/policySign.js +0 -72
  56. package/src/cli/commands/proof.js +0 -122
  57. package/src/cli/commands/run.js +0 -342
  58. package/src/cli/commands/status.js +0 -73
  59. package/src/cli/commands/verify.js +0 -144
  60. package/src/cli/main.js +0 -176
  61. package/src/cli/parseArgs.js +0 -114
  62. package/src/exec/localExecutor.js +0 -289
  63. package/src/hooks/register.cjs +0 -505
  64. package/src/state/appendCentral.cjs +0 -87
  65. package/src/state/fileIndex.js +0 -140
  66. package/src/state/index.cjs +0 -101
  67. package/src/state/index.js +0 -65
  68. package/src/state/intentRegistry.js +0 -83
  69. package/src/state/migration.js +0 -112
  70. package/src/state/proofRunner.js +0 -246
  71. package/src/state/stateRoot.js +0 -40
  72. package/src/state/targetRegistry.js +0 -108
  73. package/src/state/workspaceId.js +0 -40
  74. package/src/state/workspaceRegistry.js +0 -65
  75. /package/{release-exec/dist → dist}/lbe_engine.wasm +0 -0
package/runtime/engine.js DELETED
@@ -1,322 +0,0 @@
1
- // Runtime boundary for the compiled governance engine.
2
- // All governance decisions route through here — the WASM module owns the logic.
3
-
4
- import fs from 'fs';
5
- import path from 'path';
6
- import { fileURLToPath } from 'url';
7
-
8
- const runtimeDir = path.dirname(fileURLToPath(import.meta.url));
9
- const wasmPath = path.join(runtimeDir, 'lbe_engine.wasm');
10
-
11
- // ── Result tables (error code → message) ─────────────────────────────────────
12
- // These map WASM integer codes to human-readable reasons for the JS surface.
13
-
14
- const POLICY_MESSAGES = {
15
- 0: { allowed: true, reason: null, message: 'Policy check passed' },
16
- 1: { allowed: false, reason: 'POLICY_NOT_CONFIGURED', message: 'No policy configured' },
17
- 2: { allowed: false, reason: 'REQUESTER_NOT_ALLOWED', message: 'Requester not in policy' },
18
- 3: { allowed: false, reason: 'COMMAND_NOT_ALLOWED', message: 'Command not allowed for requester' },
19
- 4: { allowed: false, reason: 'ADAPTER_NOT_ALLOWED', message: 'Adapter not allowed' },
20
- 5: { allowed: false, reason: 'NO_FILESYSTEM_ROOTS_DEFINED', message: 'No filesystem roots defined for requester' },
21
- 6: { allowed: false, reason: 'CWD_OUTSIDE_ALLOWED_ROOT', message: 'Path not under allowed roots' },
22
- 7: { allowed: false, reason: 'PATH_DENIED_BY_PATTERN', message: 'Path matches deny pattern' },
23
- 8: { allowed: false, reason: 'SHELL_CMD_DENIED', message: 'Shell command not allowed' },
24
- };
25
-
26
- const SCHEMA_MESSAGES = {
27
- 0: { valid: true, error: null },
28
- 1: { valid: false, error: 'Missing required field: id' },
29
- 2: { valid: false, error: 'Missing required field: commandId' },
30
- 3: { valid: false, error: 'Missing required field: requesterId' },
31
- 4: { valid: false, error: 'Missing required field: sessionId' },
32
- 5: { valid: false, error: 'Missing required field: timestamp' },
33
- 6: { valid: false, error: 'Missing required field: nonce' },
34
- 7: { valid: false, error: 'Missing required field: requires' },
35
- 8: { valid: false, error: 'Missing required field: payload' },
36
- 9: { valid: false, error: 'Missing required field: signature' },
37
- 10: { valid: false, error: "Field 'id' is invalid" },
38
- 11: { valid: false, error: "Field 'commandId' is invalid" },
39
- 12: { valid: false, error: "Field 'requesterId' is invalid" },
40
- 13: { valid: false, error: "Field 'sessionId' is invalid" },
41
- 14: { valid: false, error: "Field 'timestamp' is invalid" },
42
- 15: { valid: false, error: "Field 'nonce' is invalid" },
43
- 16: { valid: false, error: "Field 'requires' is invalid" },
44
- 17: { valid: false, error: 'payload: missing required field: adapter' },
45
- 18: { valid: false, error: "payload: field 'adapter' is invalid" },
46
- 19: { valid: false, error: 'signature: missing required field: alg' },
47
- 20: { valid: false, error: 'signature: missing required field: keyId' },
48
- 21: { valid: false, error: 'signature: missing required field: sig' },
49
- 22: { valid: false, error: "signature: field 'alg' must be ed25519" },
50
- 23: { valid: false, error: "signature: field 'sig' is invalid" },
51
- 24: { valid: false, error: "Field 'risk' is invalid" },
52
- };
53
-
54
- const KEY_REASONS = {
55
- 1: 'KEY_ID_INVALID',
56
- 2: 'KEY_NOT_TRUSTED',
57
- 3: 'KEY_DEPRECATED',
58
- 4: 'KEY_REQUESTER_MISMATCH',
59
- 5: 'KEY_LIFECYCLE_INVALID',
60
- 6: 'KEY_NOT_YET_VALID',
61
- 7: 'KEY_EXPIRED',
62
- };
63
-
64
- const PIPELINE_STAGES = {
65
- 0: 'schema',
66
- 1: 'timestamp',
67
- 2: 'key',
68
- 3: 'signature',
69
- 4: 'rate_limit',
70
- 5: 'nonce',
71
- 6: 'policy',
72
- 255: 'ok',
73
- };
74
-
75
- const RISK_LABELS = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];
76
- const COMMAND_TYPE = { ECHO: 0, READ_FILE: 1, WRITE_FILE: 2, PATCH_FILE: 3, DELETE_FILE: 4, RUN_SHELL: 5 };
77
-
78
- // ── WASM instance (lazy singleton) ────────────────────────────────────────────
79
-
80
- let _instance = null;
81
-
82
- function wasm() {
83
- if (_instance) return _instance;
84
- if (!fs.existsSync(wasmPath)) throw new Error(`LBE engine missing: ${wasmPath}`);
85
- const bytes = fs.readFileSync(wasmPath);
86
- _instance = new WebAssembly.Instance(new WebAssembly.Module(bytes), {});
87
- return _instance;
88
- }
89
-
90
- function memory() {
91
- return new Uint8Array(wasm().exports.memory.buffer);
92
- }
93
-
94
- function inPtr() { return wasm().exports.lbe_in_ptr(); }
95
- function outPtr() { return wasm().exports.lbe_out_ptr(); }
96
- function bufSize(){ return wasm().exports.lbe_buf_size(); }
97
-
98
- // Write a UTF-8 string + null terminator into the WASM input buffer.
99
- function writeIn(str) {
100
- const enc = new TextEncoder().encode(str);
101
- const mem = memory();
102
- const ptr = inPtr();
103
- mem.set(enc, ptr);
104
- mem[ptr + enc.length] = 0;
105
- }
106
-
107
- // Read a null-terminated UTF-8 string from the WASM output buffer.
108
- function readOut() {
109
- const mem = memory();
110
- const ptr = outPtr();
111
- let end = ptr;
112
- while (mem[end] !== 0 && end - ptr < bufSize()) end++;
113
- return new TextDecoder().decode(mem.slice(ptr, end));
114
- }
115
-
116
- // Write raw bytes + null to input buffer (for audit hash — two segments).
117
- function writeInBinary(a, b) {
118
- const mem = memory();
119
- const ptr = inPtr();
120
- let pos = ptr;
121
- for (let i = 0; i < a.length; i++) mem[pos++] = a[i];
122
- mem[pos++] = 0;
123
- for (let i = 0; i < b.length; i++) mem[pos++] = b[i];
124
- mem[pos] = 0;
125
- }
126
-
127
- // Write 48 u32 values (pipeline input struct) to input buffer.
128
- function writePipelineInput(fields) {
129
- const mem = memory();
130
- const ptr = inPtr();
131
- const view = new DataView(mem.buffer, ptr);
132
- fields.forEach((v, i) => view.setUint32(i * 4, v >>> 0, true));
133
- }
134
-
135
- // Read 2 u32 values (pipeline output struct) from output buffer.
136
- function readPipelineOutput() {
137
- const mem = memory();
138
- const ptr = outPtr();
139
- const view = new DataView(mem.buffer, ptr);
140
- return { stage: view.getUint32(0, true), code: view.getUint32(4, true) };
141
- }
142
-
143
- // ── Public engine API ─────────────────────────────────────────────────────────
144
-
145
- export function getRuntimeInfo() {
146
- return { mode: 'wasm', available: fs.existsSync(wasmPath), wasmPath, localFirst: true };
147
- }
148
-
149
- export async function loadWasmEngine() {
150
- return { ok: true, mode: 'wasm', version: wasm().exports.lbe_engine_version() };
151
- }
152
-
153
- /**
154
- * runValidationPipeline — full 4-gate validation in WASM.
155
- *
156
- * @param {object} flags All pre-extracted flags for each gate.
157
- * @returns {{ stage, stageLabel, code, ok, policyResult, keyReason, schemaError }}
158
- */
159
- export function runValidationPipeline(flags) {
160
- // Pack the 49-field input struct into IN_BUF as little-endian u32 values.
161
- // Order must match lib.rs lbe_validate_pipeline documentation.
162
- writePipelineInput([
163
- // Schema flags [0..24]
164
- flags.hasId ? 1 : 0, flags.idValid ? 1 : 0,
165
- flags.hasCommandId ? 1 : 0, flags.commandIdValid ? 1 : 0,
166
- flags.hasRequesterId ? 1 : 0, flags.requesterIdValid ? 1 : 0,
167
- flags.hasSessionId ? 1 : 0, flags.sessionIdValid ? 1 : 0,
168
- flags.hasTimestamp ? 1 : 0, flags.timestampValid ? 1 : 0,
169
- flags.hasNonce ? 1 : 0, flags.nonceValid ? 1 : 0,
170
- flags.hasRequires ? 1 : 0, flags.requiresValid ? 1 : 0,
171
- flags.hasPayload ? 1 : 0,
172
- flags.hasPayloadAdapter ? 1 : 0, flags.payloadAdapterValid ? 1 : 0,
173
- flags.hasSignature ? 1 : 0,
174
- flags.hasSignatureAlg ? 1 : 0, flags.signatureAlgValid ? 1 : 0,
175
- flags.hasSignatureKeyId ? 1 : 0,
176
- flags.hasSignatureSig ? 1 : 0, flags.signatureSigValid ? 1 : 0,
177
- flags.hasRisk ? 1 : 0, flags.riskValid ? 1 : 0,
178
- // Timestamp [25..27]
179
- flags.cmdTimestamp >>> 0, flags.nowSec >>> 0, flags.maxClockSkewSec >>> 0,
180
- // Key lifecycle [28..34]
181
- flags.keyIdFormatValid ? 1 : 0,
182
- flags.keyFound ? 1 : 0,
183
- flags.keyNotDeprecated ? 1 : 0,
184
- flags.keyRequesterMatches ? 1 : 0,
185
- flags.keyNotBeforeOk ? 1 : 0,
186
- flags.keyNotExpired ? 1 : 0,
187
- flags.keyLifecycleFieldsPresent ? 1 : 0,
188
- // Signature [35]
189
- flags.signatureValid ? 1 : 0,
190
- // Rate limit [36..37]
191
- flags.rateLimitOk ? 1 : 0, flags.rateLimitRetryAfterSec >>> 0,
192
- // Nonce [38]
193
- flags.nonceOk ? 1 : 0,
194
- // Policy [39..48]
195
- flags.policyConfigured ? 1 : 0,
196
- flags.requesterConfigured ? 1 : 0,
197
- flags.commandAllowed ? 1 : 0,
198
- flags.adapterAllowed ? 1 : 0,
199
- flags.filesystemRequired ? 1 : 0,
200
- flags.filesystemRootsDefined ? 1 : 0,
201
- flags.filesystemOk ? 1 : 0,
202
- flags.pathDenied ? 1 : 0,
203
- flags.shellRequired ? 1 : 0,
204
- flags.shellCommandOk ? 1 : 0,
205
- ]);
206
-
207
- wasm().exports.lbe_validate_pipeline();
208
- const { stage, code } = readPipelineOutput();
209
- const ok = stage === 255;
210
-
211
- return {
212
- ok,
213
- stage,
214
- stageLabel: PIPELINE_STAGES[stage] || 'unknown',
215
- code,
216
- schemaError: stage === 0 ? (SCHEMA_MESSAGES[code]?.error || 'Schema invalid') : null,
217
- keyReason: stage === 2 ? (KEY_REASONS[code] || 'KEY_ERROR') : null,
218
- policyResult:stage === 6 ? { ...(POLICY_MESSAGES[code] || POLICY_MESSAGES[1]), code } : null,
219
- retryAfterSec: stage === 4 ? code : 0,
220
- skewSec: stage === 1 ? code : 0,
221
- };
222
- }
223
-
224
- /**
225
- * checkNonce — delegates nonce deduplication to WASM.
226
- * Returns { ok, updatedEntriesText } where updatedEntriesText is the new serialised
227
- * nonce DB to persist (null when replay detected).
228
- */
229
- export function checkNonce({ ttlSec, nowSec, newKey, existingEntries }) {
230
- const lines = [`${ttlSec}:${nowSec}`, newKey, ...existingEntries].join('\n') + '\n';
231
- writeIn(lines);
232
- const isReplay = wasm().exports.lbe_nonce_check() !== 0;
233
- if (isReplay) return { ok: false, updatedEntriesText: null };
234
- const out = readOut();
235
- // Strip leading "OK\n"
236
- return { ok: true, updatedEntriesText: out.startsWith('OK\n') ? out.slice(3) : out };
237
- }
238
-
239
- /**
240
- * checkRateLimit — delegates rate-limit sliding-window logic to WASM.
241
- * Returns { ok, retryAfterSec, updatedEntriesText }.
242
- */
243
- export function checkRateLimit({ windowSec, maxRequests, nowSec, requesterId, existingEntries }) {
244
- const lines = [
245
- `${windowSec}:${maxRequests}:${nowSec}`,
246
- requesterId,
247
- ...existingEntries,
248
- ].join('\n') + '\n';
249
- writeIn(lines);
250
- const exceeded = wasm().exports.lbe_rate_check() !== 0;
251
- const out = readOut();
252
- if (exceeded) {
253
- const retryAfterSec = parseInt(out.match(/^EXCEEDED:(\d+)/)?.[1] ?? '1', 10);
254
- const entriesText = out.replace(/^EXCEEDED:\d+\n/, '');
255
- return { ok: false, retryAfterSec, updatedEntriesText: entriesText };
256
- }
257
- return { ok: true, retryAfterSec: 0, updatedEntriesText: out.startsWith('OK\n') ? out.slice(3) : out };
258
- }
259
-
260
- /**
261
- * computeAuditHash — SHA-256 of (prevHash || entryJson) computed in WASM.
262
- */
263
- export function computeAuditHash(prevHash, entryJson) {
264
- const enc = new TextEncoder();
265
- writeInBinary(enc.encode(prevHash), enc.encode(entryJson));
266
- wasm().exports.lbe_audit_hash();
267
- return readOut().slice(0, 64); // 64-char hex string
268
- }
269
-
270
- /**
271
- * classifyRisk — risk level via WASM.
272
- * commandId: one of ECHO, READ_FILE, WRITE_FILE, PATCH_FILE, DELETE_FILE, RUN_SHELL
273
- */
274
- export function classifyRisk(commandId, shellCmdIsRm = false) {
275
- const typeCode = COMMAND_TYPE[commandId] ?? 0;
276
- const code = wasm().exports.lbe_classify_risk(typeCode, shellCmdIsRm ? 1 : 0);
277
- return RISK_LABELS[code] ?? 'LOW';
278
- }
279
-
280
- /**
281
- * shouldRollback — rollback decision via WASM.
282
- */
283
- export function shouldRollback({ execFailed, postCheckFailed, backupExists, rollbackEnabled }) {
284
- return wasm().exports.lbe_rollback_decision(
285
- execFailed ? 1 : 0,
286
- postCheckFailed ? 1 : 0,
287
- backupExists ? 1 : 0,
288
- rollbackEnabled ? 1 : 0
289
- ) === 1;
290
- }
291
-
292
- // ── Legacy scalar exports (back-compat) ───────────────────────────────────────
293
-
294
- export function evaluatePolicyDecision(input) {
295
- const code = wasm().exports.lbe_policy_decision(
296
- input.policyConfigured ? 1 : 0, input.requesterConfigured ? 1 : 0,
297
- input.commandAllowed ? 1 : 0, input.adapterAllowed ? 1 : 0,
298
- input.filesystemRequired ? 1 : 0, input.filesystemRootsDefined ? 1 : 0,
299
- input.filesystemOk ? 1 : 0, input.pathDenied ? 1 : 0,
300
- input.shellRequired ? 1 : 0, input.shellCommandOk ? 1 : 0
301
- );
302
- return { ...(POLICY_MESSAGES[code] || POLICY_MESSAGES[1]), code };
303
- }
304
-
305
- export function evaluateSchemaDecision(input) {
306
- const code = wasm().exports.lbe_schema_decision(
307
- input.hasId ? 1 : 0, input.idValid ? 1 : 0,
308
- input.hasCommandId ? 1 : 0, input.commandIdValid ? 1 : 0,
309
- input.hasRequesterId ? 1 : 0, input.requesterIdValid ? 1 : 0,
310
- input.hasSessionId ? 1 : 0, input.sessionIdValid ? 1 : 0,
311
- input.hasTimestamp ? 1 : 0, input.timestampValid ? 1 : 0,
312
- input.hasNonce ? 1 : 0, input.nonceValid ? 1 : 0,
313
- input.hasRequires ? 1 : 0, input.requiresValid ? 1 : 0,
314
- input.hasPayload ? 1 : 0,
315
- input.hasPayloadAdapter ? 1 : 0, input.payloadAdapterValid ? 1 : 0,
316
- input.hasSignature ? 1 : 0, input.hasSignatureAlg ? 1 : 0,
317
- input.signatureAlgValid ? 1 : 0, input.hasSignatureKeyId ? 1 : 0,
318
- input.hasSignatureSig ? 1 : 0, input.signatureSigValid ? 1 : 0,
319
- input.hasRisk ? 1 : 0, input.riskValid ? 1 : 0
320
- );
321
- return { ...(SCHEMA_MESSAGES[code] || SCHEMA_MESSAGES[10]), code };
322
- }
Binary file
@@ -1,36 +0,0 @@
1
- // src/cli/commands/auditVerify.js
2
- // Verify audit log hash-chain integrity
3
-
4
- import path from 'path';
5
- import { verifyAuditLogIntegrity } from '../../core/auditLog.js';
6
-
7
- function toBoolean(value, defaultValue) {
8
- if (value === undefined) return defaultValue;
9
- if (value === true || value === false) return value;
10
- const normalized = String(value).trim().toLowerCase();
11
- if (normalized === 'true' || normalized === '1' || normalized === 'yes') return true;
12
- if (normalized === 'false' || normalized === '0' || normalized === 'no') return false;
13
- return defaultValue;
14
- }
15
-
16
- export async function auditVerifyCommand(opts) {
17
- const auditPath = opts.audit ? path.resolve(opts.audit) : path.resolve('.lbe/data/audit.log.jsonl');
18
- const failFast = toBoolean(opts['fail-fast'], true);
19
- const jsonOutput = toBoolean(opts.json, true);
20
- const maxEntries = Number.isFinite(Number(opts.max)) ? Number(opts.max) : undefined;
21
-
22
- const result = verifyAuditLogIntegrity(auditPath, { failFast, maxEntries });
23
-
24
- if (jsonOutput) {
25
- console.log(JSON.stringify(result, null, 2));
26
- } else if (result.valid) {
27
- console.log(`OK: ${result.file}`);
28
- console.log(`Entries: ${result.entries}`);
29
- } else {
30
- console.log(`FAIL: ${result.file}`);
31
- console.log(`First invalid index: ${result.firstInvalidIndex}`);
32
- console.log(`Reason: ${result.reason}`);
33
- }
34
-
35
- process.exit(result.valid ? 0 : 8);
36
- }
@@ -1,175 +0,0 @@
1
- // src/cli/commands/dryrun.js
2
- // Validate proposal and simulate execution
3
-
4
- import fs from 'fs';
5
- import path from 'path';
6
- import { validateCommand } from '../../core/validator.js';
7
- import { NonceStore } from '../../core/nonceStore.js';
8
- import { executeAdapter } from '../../adapters/index.js';
9
- import { loadKeysStore } from '../../core/trustedKeys.js';
10
- import { verifyPolicySignature } from '../../core/policySignature.js';
11
- import { validateAndUpdatePolicyVersionState } from '../../core/policyVersionGuard.js';
12
-
13
- export async function dryrunCommand(opts) {
14
- const { in: inFile } = opts;
15
- const config = opts.config || opts.policy;
16
- const pubKey = opts['pub-key'];
17
- const keysStorePath = opts['keys-store'] || path.resolve('.lbe/config/keys.json');
18
- const policySigPath = opts['policy-sig'] || path.resolve('.lbe/config/policy.sig.json');
19
- const policyStatePath = opts['policy-state'] || path.resolve('.lbe/data/policy.state.json');
20
- const allowUnsignedPolicy = opts['policy-unsigned-ok'] === true || String(opts['policy-unsigned-ok']).toLowerCase() === 'true';
21
- // Validate required arguments
22
- if (!inFile) {
23
- console.error('Error: --in <file> is required');
24
- process.exit(1);
25
- }
26
-
27
- // Read proposal file
28
- let proposal;
29
- try {
30
- const filePath = path.resolve(inFile);
31
- const content = fs.readFileSync(filePath, 'utf-8');
32
- proposal = JSON.parse(content);
33
- } catch (error) {
34
- console.error(JSON.stringify({
35
- status: 'error',
36
- error: 'INVALID_PROPOSAL_FILE',
37
- message: error.message
38
- }));
39
- process.exit(5);
40
- }
41
-
42
- // Load policy
43
- let policy;
44
- try {
45
- const policyPath = config || path.resolve('.lbe/config/policy.default.json');
46
- if (!fs.existsSync(policyPath)) {
47
- console.error(JSON.stringify({
48
- status: 'error',
49
- error: 'MISSING_POLICY',
50
- message: `Policy file not found: ${policyPath}`
51
- }));
52
- process.exit(1);
53
- }
54
- const policyContent = fs.readFileSync(policyPath, 'utf-8');
55
- policy = JSON.parse(policyContent);
56
- } catch (error) {
57
- console.error(JSON.stringify({
58
- status: 'error',
59
- error: 'INVALID_POLICY',
60
- message: error.message
61
- }));
62
- process.exit(1);
63
- }
64
-
65
- // Load key store (preferred) with legacy pub-key fallback
66
- const keyStoreResult = loadKeysStore(keysStorePath);
67
- const keyStore = keyStoreResult.ok ? keyStoreResult.store : null;
68
-
69
- // Preflight: policy signature verification (strict by default)
70
- const policySigCheck = verifyPolicySignature({
71
- policyObj: policy,
72
- keyStore,
73
- policySigPath,
74
- allowUnsigned: allowUnsignedPolicy
75
- });
76
- if (!policySigCheck.ok) {
77
- console.error(JSON.stringify({
78
- status: 'error',
79
- error: policySigCheck.reason,
80
- message: policySigCheck.message
81
- }, null, 2));
82
- process.exit(8);
83
- }
84
-
85
- const versionCheck = validateAndUpdatePolicyVersionState({
86
- policyObj: policy,
87
- statePath: policyStatePath,
88
- maxCreatedAtSkewSec: policy?.security?.maxPolicyCreatedAtSkewSec
89
- });
90
- if (!versionCheck.ok) {
91
- console.error(JSON.stringify({
92
- status: 'error',
93
- error: versionCheck.reason,
94
- message: versionCheck.message
95
- }, null, 2));
96
- process.exit(8);
97
- }
98
-
99
- // Load nonce store
100
- const nonceDb = new NonceStore(path.resolve('.lbe/data/nonce.db.json'));
101
- await nonceDb.load();
102
-
103
- if (!keyStore && !pubKey) {
104
- console.error(JSON.stringify({
105
- status: 'error',
106
- error: 'MISSING_KEY_MATERIAL',
107
- message: `${keyStoreResult.message}. Provide --pub-key/--pub-key-file or create .lbe/config/keys.json`
108
- }));
109
- process.exit(1);
110
- }
111
-
112
- // Validate command
113
- const validateResult = validateCommand({
114
- commandObj: proposal,
115
- pubKeyB64: pubKey,
116
- keyStore,
117
- nonceDb,
118
- policy
119
- });
120
-
121
- if (!validateResult.valid) {
122
- const output = {
123
- status: 'invalid',
124
- commandId: proposal.commandId || 'N/A',
125
- checks: validateResult.checks,
126
- errors: validateResult.errors || [],
127
- executionResult: null
128
- };
129
- console.log(JSON.stringify(output, null, 2));
130
-
131
- if (validateResult.checks.schema === false) process.exit(5);
132
- if (validateResult.checks.signature === false) process.exit(3);
133
- if (validateResult.checks.nonce === false) process.exit(4);
134
- if (validateResult.checks.timestamp === false) process.exit(6);
135
- if (validateResult.checks.rateLimit === false) process.exit(7);
136
- if (validateResult.checks.policy === false) process.exit(2);
137
- process.exit(9);
138
- }
139
-
140
- // Simulate execution with noop adapter
141
- let executionResult;
142
- try {
143
- const requesterPolicy = policy.requesters?.[proposal.requesterId];
144
- executionResult = await executeAdapter(
145
- 'noop',
146
- proposal,
147
- policy,
148
- requesterPolicy
149
- );
150
- } catch (error) {
151
- executionResult = {
152
- adapter: 'noop',
153
- status: 'error',
154
- error: error.message
155
- };
156
- }
157
-
158
- // Output structured result
159
- const output = {
160
- status: 'valid_simulated',
161
- commandId: proposal.commandId || 'N/A',
162
- checks: validateResult.checks,
163
- risk: validateResult.risk || 'UNKNOWN',
164
- executionResult: {
165
- adapter: executionResult.adapter,
166
- status: executionResult.status,
167
- output: executionResult.output || executionResult.error || '',
168
- exitCode: executionResult.exitCode || 0,
169
- note: 'This is a simulation using noop adapter. No actual execution occurred.'
170
- }
171
- };
172
-
173
- console.log(JSON.stringify(output, null, 2));
174
- process.exit(0);
175
- }
@@ -1,153 +0,0 @@
1
- // src/cli/commands/health.js
2
- // Deployment health check command
3
-
4
- import fs from 'fs';
5
- import path from 'path';
6
- import { performIntegrityCheck } from '../../core/integrity.js';
7
-
8
- function toBoolean(value, defaultValue) {
9
- if (value === undefined) return defaultValue;
10
- if (value === true || value === false) return value;
11
- const normalized = String(value).trim().toLowerCase();
12
- if (normalized === 'true' || normalized === '1' || normalized === 'yes') return true;
13
- if (normalized === 'false' || normalized === '0' || normalized === 'no') return false;
14
- return defaultValue;
15
- }
16
-
17
- function addCheck(checks, name, ok, message) {
18
- checks[name] = { ok, message };
19
- }
20
-
21
- function canReadFile(filePath) {
22
- try {
23
- fs.accessSync(filePath, fs.constants.R_OK);
24
- return true;
25
- } catch {
26
- return false;
27
- }
28
- }
29
-
30
- function checkDataWritable(dataDir) {
31
- const marker = path.join(dataDir, `.healthcheck-${Date.now()}`);
32
- try {
33
- fs.mkdirSync(dataDir, { recursive: true });
34
- fs.writeFileSync(marker, 'ok', 'utf8');
35
- fs.unlinkSync(marker);
36
- return { ok: true, message: 'Data directory writable' };
37
- } catch (error) {
38
- return { ok: false, message: `Data directory not writable: ${error.message}` };
39
- }
40
- }
41
-
42
- export async function healthCommand(opts) {
43
- const jsonOutput = toBoolean(opts.json, true);
44
- const policyPath = path.resolve(opts.config || opts.policy || '.lbe/config/policy.default.json');
45
- const policySigPath = path.resolve(opts['policy-sig'] || '.lbe/config/policy.sig.json');
46
- const keysStorePath = path.resolve(opts['keys-store'] || '.lbe/config/keys.json');
47
- const dataDir = path.resolve(opts['data-dir'] || '.lbe/data');
48
- const auditPath = path.resolve(opts.audit || path.join(dataDir, 'audit.log.jsonl'));
49
- const noncePath = path.resolve(opts['nonce-db'] || path.join(dataDir, 'nonce.db.json'));
50
- const ratePath = path.resolve(opts['rate-db'] || path.join(dataDir, 'rate-limit.db.json'));
51
- const policyStatePath = path.resolve(opts['policy-state'] || path.join(dataDir, 'policy.state.json'));
52
- const integrityStrict = toBoolean(opts['integrity-strict'], false);
53
- const integrityManifestPath = path.resolve(opts['integrity-manifest'] || '.lbe/config/integrity.manifest.json');
54
-
55
- const checks = {};
56
-
57
- addCheck(
58
- checks,
59
- 'policy',
60
- fs.existsSync(policyPath) && canReadFile(policyPath),
61
- fs.existsSync(policyPath)
62
- ? `Policy file readable: ${policyPath}`
63
- : `Policy file missing: ${policyPath}`
64
- );
65
-
66
- addCheck(
67
- checks,
68
- 'policySignature',
69
- fs.existsSync(policySigPath) && canReadFile(policySigPath),
70
- fs.existsSync(policySigPath)
71
- ? `Policy signature readable: ${policySigPath}`
72
- : `Policy signature missing: ${policySigPath}`
73
- );
74
-
75
- addCheck(
76
- checks,
77
- 'trustedKeys',
78
- fs.existsSync(keysStorePath) && canReadFile(keysStorePath),
79
- fs.existsSync(keysStorePath)
80
- ? `Trusted keys readable: ${keysStorePath}`
81
- : `Trusted keys missing: ${keysStorePath}`
82
- );
83
-
84
- addCheck(
85
- checks,
86
- 'auditLog',
87
- fs.existsSync(auditPath) && canReadFile(auditPath),
88
- fs.existsSync(auditPath)
89
- ? `Audit log readable: ${auditPath}`
90
- : `Audit log missing: ${auditPath}`
91
- );
92
-
93
- addCheck(
94
- checks,
95
- 'nonceDb',
96
- fs.existsSync(noncePath) && canReadFile(noncePath),
97
- fs.existsSync(noncePath)
98
- ? `Nonce DB readable: ${noncePath}`
99
- : `Nonce DB missing: ${noncePath}`
100
- );
101
-
102
- addCheck(
103
- checks,
104
- 'rateLimitDb',
105
- fs.existsSync(ratePath) && canReadFile(ratePath),
106
- fs.existsSync(ratePath)
107
- ? `Rate-limit DB readable: ${ratePath}`
108
- : `Rate-limit DB missing: ${ratePath}`
109
- );
110
-
111
- addCheck(
112
- checks,
113
- 'policyState',
114
- fs.existsSync(policyStatePath) && canReadFile(policyStatePath),
115
- fs.existsSync(policyStatePath)
116
- ? `Policy state readable: ${policyStatePath}`
117
- : `Policy state missing: ${policyStatePath}`
118
- );
119
-
120
- const writable = checkDataWritable(dataDir);
121
- addCheck(checks, 'dataWritable', writable.ok, writable.message);
122
-
123
- if (integrityStrict) {
124
- const integrity = await performIntegrityCheck({
125
- strict: true,
126
- manifestPath: integrityManifestPath
127
- });
128
- addCheck(
129
- checks,
130
- 'integrity',
131
- integrity.valid,
132
- integrity.valid
133
- ? integrity.message
134
- : `${integrity.reason}: ${integrity.message}`
135
- );
136
- }
137
-
138
- const allOk = Object.values(checks).every((c) => c.ok === true);
139
- const output = {
140
- ok: allOk,
141
- status: allOk ? 'healthy' : 'unhealthy',
142
- timestamp: new Date().toISOString(),
143
- checks
144
- };
145
-
146
- if (jsonOutput) {
147
- console.log(JSON.stringify(output, null, 2));
148
- } else {
149
- console.log(`${output.status.toUpperCase()}: ${Object.keys(checks).length} checks`);
150
- }
151
-
152
- process.exit(allOk ? 0 : 8);
153
- }