@synctek/forgeos 2.0.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/LICENSE +21 -0
- package/README.md +386 -0
- package/dist/cli/commands/analyze.d.ts +14 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli/commands/analyze.js +94 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/build.d.ts +11 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +86 -0
- package/dist/cli/commands/build.js.map +1 -0
- package/dist/cli/commands/changeset.d.ts +13 -0
- package/dist/cli/commands/changeset.d.ts.map +1 -0
- package/dist/cli/commands/changeset.js +174 -0
- package/dist/cli/commands/changeset.js.map +1 -0
- package/dist/cli/commands/evidence.d.ts +12 -0
- package/dist/cli/commands/evidence.d.ts.map +1 -0
- package/dist/cli/commands/evidence.js +94 -0
- package/dist/cli/commands/evidence.js.map +1 -0
- package/dist/cli/commands/federation.d.ts +13 -0
- package/dist/cli/commands/federation.d.ts.map +1 -0
- package/dist/cli/commands/federation.js +127 -0
- package/dist/cli/commands/federation.js.map +1 -0
- package/dist/cli/commands/gate.d.ts +15 -0
- package/dist/cli/commands/gate.d.ts.map +1 -0
- package/dist/cli/commands/gate.js +178 -0
- package/dist/cli/commands/gate.js.map +1 -0
- package/dist/cli/commands/initiative.d.ts +13 -0
- package/dist/cli/commands/initiative.d.ts.map +1 -0
- package/dist/cli/commands/initiative.js +130 -0
- package/dist/cli/commands/initiative.js.map +1 -0
- package/dist/cli/commands/mind.d.ts +16 -0
- package/dist/cli/commands/mind.d.ts.map +1 -0
- package/dist/cli/commands/mind.js +139 -0
- package/dist/cli/commands/mind.js.map +1 -0
- package/dist/cli/commands/outcome.d.ts +12 -0
- package/dist/cli/commands/outcome.d.ts.map +1 -0
- package/dist/cli/commands/outcome.js +85 -0
- package/dist/cli/commands/outcome.js.map +1 -0
- package/dist/cli/commands/project.d.ts +13 -0
- package/dist/cli/commands/project.d.ts.map +1 -0
- package/dist/cli/commands/project.js +128 -0
- package/dist/cli/commands/project.js.map +1 -0
- package/dist/cli/commands/review.d.ts +15 -0
- package/dist/cli/commands/review.d.ts.map +1 -0
- package/dist/cli/commands/review.js +167 -0
- package/dist/cli/commands/review.js.map +1 -0
- package/dist/cli/commands/score.d.ts +17 -0
- package/dist/cli/commands/score.d.ts.map +1 -0
- package/dist/cli/commands/score.js +168 -0
- package/dist/cli/commands/score.js.map +1 -0
- package/dist/cli/commands/session.d.ts +13 -0
- package/dist/cli/commands/session.d.ts.map +1 -0
- package/dist/cli/commands/session.js +133 -0
- package/dist/cli/commands/session.js.map +1 -0
- package/dist/cli/commands/trust.d.ts +16 -0
- package/dist/cli/commands/trust.d.ts.map +1 -0
- package/dist/cli/commands/trust.js +261 -0
- package/dist/cli/commands/trust.js.map +1 -0
- package/dist/cli/index.d.ts +19 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +99 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/output.d.ts +48 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +139 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/client.d.ts +46 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +146 -0
- package/dist/client.js.map +1 -0
- package/dist/handlers.d.ts +11 -0
- package/dist/handlers.d.ts.map +1 -0
- package/dist/handlers.js +424 -0
- package/dist/handlers.js.map +1 -0
- package/dist/http-server.d.ts +25 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +246 -0
- package/dist/http-server.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/handlers.d.ts +11 -0
- package/dist/mcp/handlers.d.ts.map +1 -0
- package/dist/mcp/handlers.js +553 -0
- package/dist/mcp/handlers.js.map +1 -0
- package/dist/mcp/http-server.d.ts +25 -0
- package/dist/mcp/http-server.d.ts.map +1 -0
- package/dist/mcp/http-server.js +246 -0
- package/dist/mcp/http-server.js.map +1 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +40 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/tools.d.ts +944 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +531 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/shared/client.d.ts +59 -0
- package/dist/shared/client.d.ts.map +1 -0
- package/dist/shared/client.js +171 -0
- package/dist/shared/client.js.map +1 -0
- package/dist/shared/errors.d.ts +25 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/errors.js +44 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/types.d.ts +111 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +10 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/tools.d.ts +944 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +513 -0
- package/dist/tools.js.map +1 -0
- package/dist/trust/chain.d.ts +86 -0
- package/dist/trust/chain.d.ts.map +1 -0
- package/dist/trust/chain.js +176 -0
- package/dist/trust/chain.js.map +1 -0
- package/dist/trust/git-binding.d.ts +61 -0
- package/dist/trust/git-binding.d.ts.map +1 -0
- package/dist/trust/git-binding.js +133 -0
- package/dist/trust/git-binding.js.map +1 -0
- package/dist/trust/index.d.ts +20 -0
- package/dist/trust/index.d.ts.map +1 -0
- package/dist/trust/index.js +17 -0
- package/dist/trust/index.js.map +1 -0
- package/dist/trust/ledger.d.ts +144 -0
- package/dist/trust/ledger.d.ts.map +1 -0
- package/dist/trust/ledger.js +351 -0
- package/dist/trust/ledger.js.map +1 -0
- package/dist/trust/signing.d.ts +134 -0
- package/dist/trust/signing.d.ts.map +1 -0
- package/dist/trust/signing.js +249 -0
- package/dist/trust/signing.js.map +1 -0
- package/dist/trust/transmission.d.ts +129 -0
- package/dist/trust/transmission.d.ts.map +1 -0
- package/dist/trust/transmission.js +390 -0
- package/dist/trust/transmission.js.map +1 -0
- package/dist/types.d.ts +183 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Trust Ledger — hash-chained JSONL audit trail for code intelligence.
|
|
3
|
+
*
|
|
4
|
+
* Binary-compatible with the Python ledger.py implementation.
|
|
5
|
+
*
|
|
6
|
+
* Each ForgeOS project maintains a local append-only ledger at
|
|
7
|
+
* `.forgeos/ledger.jsonl`. Entries are hash-chained using SHA-256 over
|
|
8
|
+
* the canonical fields "seq:prev_hash:payload_hash:timestamp", producing
|
|
9
|
+
* a tamper-evident record of every analysis run, review, and metric change.
|
|
10
|
+
*
|
|
11
|
+
* Chain hash formula:
|
|
12
|
+
* chain_hash = SHA-256("{seq}:{prev_hash}:{payload_hash}:{timestamp}")
|
|
13
|
+
*
|
|
14
|
+
* JSONL format: one JSON object per line, keys sorted (sort_keys=True),
|
|
15
|
+
* written with appendFileSync. Matches Python's json.dumps(..., sort_keys=True).
|
|
16
|
+
*
|
|
17
|
+
* File locking: Node.js single-threaded event loop provides safe sequential
|
|
18
|
+
* writes within one process. For cross-process safety, a .ledger.lock file
|
|
19
|
+
* is created (advisory lock, best-effort) — same approach as Python's
|
|
20
|
+
* fcntl.LOCK_EX. Full POSIX flock is not available in pure Node stdlib;
|
|
21
|
+
* the lock file is written atomically and removed after use.
|
|
22
|
+
*
|
|
23
|
+
* Entry field order in JSONL (matches Python's sort_keys=True output):
|
|
24
|
+
* chain_hash, entry_type, payload, payload_hash, prev_hash, schema_version,
|
|
25
|
+
* seq, signature, source_tree_hash, timestamp
|
|
26
|
+
*/
|
|
27
|
+
import { readFileSync, appendFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync, } from 'fs';
|
|
28
|
+
import { join, dirname } from 'path';
|
|
29
|
+
import { GENESIS_SENTINEL, SCHEMA_VERSION, ENTRY_TYPES, computeChainHash, computePayloadHash, pythonJsonDumps, } from './chain.js';
|
|
30
|
+
import { KeyManager } from './signing.js';
|
|
31
|
+
import { computeSourceTreeHash } from './git-binding.js';
|
|
32
|
+
export class LedgerError extends Error {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = 'LedgerError';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export class ChainIntegrityError extends LedgerError {
|
|
39
|
+
seq;
|
|
40
|
+
constructor(seq, message) {
|
|
41
|
+
super(`Chain break at seq ${seq}: ${message}`);
|
|
42
|
+
this.name = 'ChainIntegrityError';
|
|
43
|
+
this.seq = seq;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// ------------------------------------------------------------------
|
|
47
|
+
// TrustLedger
|
|
48
|
+
// ------------------------------------------------------------------
|
|
49
|
+
/**
|
|
50
|
+
* Append-only, hash-chained ledger for local code intelligence data.
|
|
51
|
+
*
|
|
52
|
+
* The ledger lives at {projectPath}/.forgeos/ledger.jsonl.
|
|
53
|
+
*
|
|
54
|
+
* Example:
|
|
55
|
+
* const ledger = new TrustLedger('/my/project');
|
|
56
|
+
* const entry = ledger.append('analysis_snapshot', { quality_score: 82.4 });
|
|
57
|
+
* const result = ledger.verifyChain();
|
|
58
|
+
* const stats = ledger.getChainStats();
|
|
59
|
+
*/
|
|
60
|
+
export class TrustLedger {
|
|
61
|
+
ledgerPath;
|
|
62
|
+
lockPath;
|
|
63
|
+
keyManager;
|
|
64
|
+
constructor(projectPath) {
|
|
65
|
+
this.ledgerPath = join(projectPath, '.forgeos', 'ledger.jsonl');
|
|
66
|
+
this.lockPath = join(projectPath, '.forgeos', '.ledger.lock');
|
|
67
|
+
// Attempt to initialize KeyManager; fall back to null if keys don't exist
|
|
68
|
+
this.keyManager = new KeyManager(projectPath);
|
|
69
|
+
}
|
|
70
|
+
// ------------------------------------------------------------------
|
|
71
|
+
// Public API
|
|
72
|
+
// ------------------------------------------------------------------
|
|
73
|
+
/**
|
|
74
|
+
* Append a new entry to the ledger and return the complete record.
|
|
75
|
+
*
|
|
76
|
+
* Optionally binds to git state (sourceTreeHash) and signs the entry
|
|
77
|
+
* (signature) if keys are available.
|
|
78
|
+
*
|
|
79
|
+
* @param entryType - One of the 7 canonical entry types.
|
|
80
|
+
* @param payload - Arbitrary JSON-serializable payload dict.
|
|
81
|
+
* @param repoPath - Optional repo path for git binding.
|
|
82
|
+
* @param signature - Optional pre-computed Ed25519 signature (base64).
|
|
83
|
+
* If null, the ledger will attempt to self-sign if keys exist.
|
|
84
|
+
* @returns The complete ledger entry.
|
|
85
|
+
* @throws LedgerError if the ledger directory does not exist.
|
|
86
|
+
* @throws Error if entryType is not in ENTRY_TYPES.
|
|
87
|
+
*/
|
|
88
|
+
append(entryType, payload, repoPath, signature) {
|
|
89
|
+
if (!ENTRY_TYPES.has(entryType)) {
|
|
90
|
+
throw new Error(`Unknown entry_type ${JSON.stringify(entryType)}. Valid types: ${[...ENTRY_TYPES].sort().join(', ')}`);
|
|
91
|
+
}
|
|
92
|
+
const ledgerDir = dirname(this.ledgerPath);
|
|
93
|
+
if (!existsSync(ledgerDir)) {
|
|
94
|
+
throw new LedgerError(`Ledger directory ${ledgerDir} does not exist. Run 'forgeos analyze --init' to initialise the project.`);
|
|
95
|
+
}
|
|
96
|
+
// Ensure parent for lock file exists
|
|
97
|
+
mkdirSync(dirname(this.lockPath), { recursive: true });
|
|
98
|
+
return this.appendLocked(entryType, payload, repoPath ?? null, signature ?? null);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Verify the integrity of the entire hash chain.
|
|
102
|
+
*
|
|
103
|
+
* Walks every entry in order, recomputing each chain_hash and checking
|
|
104
|
+
* that it matches the stored value, and that prev_hash matches the
|
|
105
|
+
* chain_hash of the preceding entry.
|
|
106
|
+
*
|
|
107
|
+
* @returns VerifyResult with validity, entry count, and first break location.
|
|
108
|
+
*/
|
|
109
|
+
verifyChain() {
|
|
110
|
+
if (!existsSync(this.ledgerPath)) {
|
|
111
|
+
return {
|
|
112
|
+
valid: true,
|
|
113
|
+
total_entries: 0,
|
|
114
|
+
first_break_at: null,
|
|
115
|
+
message: 'No ledger file — chain trivially valid.',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const rawLines = this.readRawLines();
|
|
119
|
+
if (rawLines.length === 0) {
|
|
120
|
+
return {
|
|
121
|
+
valid: true,
|
|
122
|
+
total_entries: 0,
|
|
123
|
+
first_break_at: null,
|
|
124
|
+
message: 'Empty ledger — chain trivially valid.',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
let prevChainHash = GENESIS_SENTINEL;
|
|
128
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
129
|
+
let entry;
|
|
130
|
+
try {
|
|
131
|
+
entry = JSON.parse(rawLines[i]);
|
|
132
|
+
}
|
|
133
|
+
catch (exc) {
|
|
134
|
+
return {
|
|
135
|
+
valid: false,
|
|
136
|
+
total_entries: rawLines.length,
|
|
137
|
+
first_break_at: i,
|
|
138
|
+
message: `Malformed JSON at seq ${i}: ${exc}`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (entry.seq !== i) {
|
|
142
|
+
return {
|
|
143
|
+
valid: false,
|
|
144
|
+
total_entries: rawLines.length,
|
|
145
|
+
first_break_at: i,
|
|
146
|
+
message: `seq mismatch at position ${i}: stored ${entry.seq}`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (entry.prev_hash !== prevChainHash) {
|
|
150
|
+
return {
|
|
151
|
+
valid: false,
|
|
152
|
+
total_entries: rawLines.length,
|
|
153
|
+
first_break_at: i,
|
|
154
|
+
message: `prev_hash mismatch at seq ${i}: expected ${JSON.stringify(prevChainHash)}, got ${JSON.stringify(entry.prev_hash)}`,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const payloadHash = computePayloadHash(entry.payload);
|
|
158
|
+
const expectedChainHash = computeChainHash(i, prevChainHash, payloadHash, entry.timestamp);
|
|
159
|
+
if (entry.chain_hash !== expectedChainHash) {
|
|
160
|
+
return {
|
|
161
|
+
valid: false,
|
|
162
|
+
total_entries: rawLines.length,
|
|
163
|
+
first_break_at: i,
|
|
164
|
+
message: `chain_hash mismatch at seq ${i}: expected ${JSON.stringify(expectedChainHash)}, got ${JSON.stringify(entry.chain_hash)}`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
prevChainHash = entry.chain_hash;
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
valid: true,
|
|
171
|
+
total_entries: rawLines.length,
|
|
172
|
+
first_break_at: null,
|
|
173
|
+
message: `Chain valid — ${rawLines.length} entries verified.`,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Read entries from the ledger with optional filtering.
|
|
178
|
+
*
|
|
179
|
+
* @param entryType - If set, only return entries of this type.
|
|
180
|
+
* @param since - If set, only return entries with timestamp >= this ISO string.
|
|
181
|
+
* @param limit - Maximum number of entries to return.
|
|
182
|
+
* @returns Array of entry dicts in chronological order.
|
|
183
|
+
*/
|
|
184
|
+
readEntries(entryType, since, limit) {
|
|
185
|
+
if (!existsSync(this.ledgerPath)) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
const results = [];
|
|
189
|
+
for (const raw of this.readRawLines()) {
|
|
190
|
+
if (!raw)
|
|
191
|
+
continue;
|
|
192
|
+
const entry = JSON.parse(raw);
|
|
193
|
+
if (entryType !== undefined && entry.entry_type !== entryType) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (since !== undefined && entry.timestamp < since) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
results.push(entry);
|
|
200
|
+
if (limit !== undefined && results.length >= limit) {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return results;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Return the most recent ledger entry, or null if empty.
|
|
208
|
+
*/
|
|
209
|
+
getLatest() {
|
|
210
|
+
if (!existsSync(this.ledgerPath)) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
const rawLines = this.readRawLines();
|
|
214
|
+
if (rawLines.length === 0) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
return JSON.parse(rawLines[rawLines.length - 1]);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Return aggregate statistics about the ledger chain.
|
|
221
|
+
*
|
|
222
|
+
* Matches Python's get_chain_stats() return structure exactly.
|
|
223
|
+
*/
|
|
224
|
+
getChainStats() {
|
|
225
|
+
if (!existsSync(this.ledgerPath)) {
|
|
226
|
+
return {
|
|
227
|
+
entry_count: 0,
|
|
228
|
+
first_timestamp: null,
|
|
229
|
+
last_timestamp: null,
|
|
230
|
+
chain_valid: true,
|
|
231
|
+
by_type: {},
|
|
232
|
+
shareable_chain_hash: null,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const rawLines = this.readRawLines();
|
|
236
|
+
if (rawLines.length === 0) {
|
|
237
|
+
return {
|
|
238
|
+
entry_count: 0,
|
|
239
|
+
first_timestamp: null,
|
|
240
|
+
last_timestamp: null,
|
|
241
|
+
chain_valid: true,
|
|
242
|
+
by_type: {},
|
|
243
|
+
shareable_chain_hash: null,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const first = JSON.parse(rawLines[0]);
|
|
247
|
+
const last = JSON.parse(rawLines[rawLines.length - 1]);
|
|
248
|
+
const byType = {};
|
|
249
|
+
for (const raw of rawLines) {
|
|
250
|
+
const e = JSON.parse(raw);
|
|
251
|
+
const t = e.entry_type ?? 'unknown';
|
|
252
|
+
byType[t] = (byType[t] ?? 0) + 1;
|
|
253
|
+
}
|
|
254
|
+
const verifyResult = this.verifyChain();
|
|
255
|
+
return {
|
|
256
|
+
entry_count: rawLines.length,
|
|
257
|
+
first_timestamp: first.timestamp ?? null,
|
|
258
|
+
last_timestamp: last.timestamp ?? null,
|
|
259
|
+
chain_valid: verifyResult.valid,
|
|
260
|
+
by_type: byType,
|
|
261
|
+
shareable_chain_hash: last.chain_hash ?? null,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
// ------------------------------------------------------------------
|
|
265
|
+
// Internal helpers
|
|
266
|
+
// ------------------------------------------------------------------
|
|
267
|
+
/**
|
|
268
|
+
* Write a new entry.
|
|
269
|
+
*
|
|
270
|
+
* Advisory file locking via .ledger.lock is used for cross-process safety.
|
|
271
|
+
* Node.js is single-threaded so within one process this is sequential.
|
|
272
|
+
*/
|
|
273
|
+
appendLocked(entryType, payload, repoPath, providedSignature) {
|
|
274
|
+
// Acquire advisory lock (best-effort)
|
|
275
|
+
const lockAcquired = this.acquireLock();
|
|
276
|
+
try {
|
|
277
|
+
const latest = this.getLatest();
|
|
278
|
+
const seq = latest !== null ? latest.seq + 1 : 0;
|
|
279
|
+
const prevHash = latest !== null ? latest.chain_hash : GENESIS_SENTINEL;
|
|
280
|
+
const timestamp = new Date().toISOString();
|
|
281
|
+
const payloadHash = computePayloadHash(payload);
|
|
282
|
+
const chainHash = computeChainHash(seq, prevHash, payloadHash, timestamp);
|
|
283
|
+
// Compute git binding if repo path provided
|
|
284
|
+
const sourceTreeHash = repoPath !== null ? computeSourceTreeHash(repoPath) : null;
|
|
285
|
+
// Build entry without signature first (for signing)
|
|
286
|
+
const entry = {
|
|
287
|
+
schema_version: SCHEMA_VERSION,
|
|
288
|
+
seq,
|
|
289
|
+
timestamp,
|
|
290
|
+
entry_type: entryType,
|
|
291
|
+
payload,
|
|
292
|
+
payload_hash: payloadHash,
|
|
293
|
+
prev_hash: prevHash,
|
|
294
|
+
chain_hash: chainHash,
|
|
295
|
+
source_tree_hash: sourceTreeHash,
|
|
296
|
+
signature: null,
|
|
297
|
+
};
|
|
298
|
+
// Sign if keys available and no pre-computed signature provided
|
|
299
|
+
let finalSignature = providedSignature;
|
|
300
|
+
if (finalSignature === null && this.keyManager !== null) {
|
|
301
|
+
try {
|
|
302
|
+
finalSignature = this.keyManager.signEntry(entry);
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
// Keys unavailable — proceed unsigned
|
|
306
|
+
finalSignature = null;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
entry.signature = finalSignature;
|
|
310
|
+
// Serialize: matches Python's json.dumps(entry, ensure_ascii=False, sort_keys=True)
|
|
311
|
+
// Python sorts keys alphabetically, so: chain_hash, entry_type, payload,
|
|
312
|
+
// payload_hash, prev_hash, schema_version, seq, signature, source_tree_hash, timestamp
|
|
313
|
+
const line = pythonJsonDumps(entry);
|
|
314
|
+
appendFileSync(this.ledgerPath, line + '\n', 'utf8');
|
|
315
|
+
return entry;
|
|
316
|
+
}
|
|
317
|
+
finally {
|
|
318
|
+
if (lockAcquired) {
|
|
319
|
+
this.releaseLock();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/** Read all non-empty lines from the ledger file. */
|
|
324
|
+
readRawLines() {
|
|
325
|
+
const content = readFileSync(this.ledgerPath, 'utf8');
|
|
326
|
+
return content.split('\n').map(l => l.trim()).filter(l => l.length > 0);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Acquire advisory lock by creating .ledger.lock.
|
|
330
|
+
* Returns true if lock was created (should be released), false if lock
|
|
331
|
+
* already existed (proceeding anyway — this is advisory only).
|
|
332
|
+
*/
|
|
333
|
+
acquireLock() {
|
|
334
|
+
try {
|
|
335
|
+
writeFileSync(this.lockPath, String(process.pid), { flag: 'wx' });
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
releaseLock() {
|
|
343
|
+
try {
|
|
344
|
+
unlinkSync(this.lockPath);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// Ignore errors on lock release
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
//# sourceMappingURL=ledger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger.js","sourceRoot":"","sources":["../../src/trust/ledger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ed25519 key management for Trust Ledger entry signing.
|
|
3
|
+
*
|
|
4
|
+
* Binary-compatible with the Python signing.py implementation.
|
|
5
|
+
*
|
|
6
|
+
* Key files:
|
|
7
|
+
* .forgeos/keys/private.pem (mode 0o600 — owner read/write only)
|
|
8
|
+
* .forgeos/keys/public.pem (world-readable)
|
|
9
|
+
*
|
|
10
|
+
* Key format: PKCS8 PEM for private, SPKI PEM for public — matching
|
|
11
|
+
* Python's serialization.PrivateFormat.PKCS8 and
|
|
12
|
+
* serialization.PublicFormat.SubjectPublicKeyInfo.
|
|
13
|
+
*
|
|
14
|
+
* Fingerprint: first 16 hex chars of SHA-256(raw_32_byte_public_key).
|
|
15
|
+
* This matches Python's get_fingerprint() which calls:
|
|
16
|
+
* public_key.public_bytes(Encoding.Raw, PublicFormat.Raw) → 32 bytes
|
|
17
|
+
* SHA-256(raw_bytes).hexdigest()[:16]
|
|
18
|
+
*
|
|
19
|
+
* Signatures: Ed25519 raw signatures, base64-encoded (standard alphabet),
|
|
20
|
+
* produced over the canonical UTF-8 bytes of the entry dict. This matches
|
|
21
|
+
* Python's base64.b64encode(raw_sig).decode("ascii").
|
|
22
|
+
*
|
|
23
|
+
* Security invariants (matching Python):
|
|
24
|
+
* - load_private_key raises if file mode is broader than 0o600
|
|
25
|
+
* - Key rotation archives old_public.pem before overwriting
|
|
26
|
+
*/
|
|
27
|
+
import { type KeyObject } from 'crypto';
|
|
28
|
+
export declare class SigningError extends Error {
|
|
29
|
+
constructor(message: string);
|
|
30
|
+
}
|
|
31
|
+
export declare class KeyNotFoundError extends SigningError {
|
|
32
|
+
constructor(message: string);
|
|
33
|
+
}
|
|
34
|
+
export declare class KeyPermissionError extends SigningError {
|
|
35
|
+
constructor(message: string);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Manages Ed25519 keypairs for a ForgeOS project.
|
|
39
|
+
*
|
|
40
|
+
* All key files are stored under {projectPath}/.forgeos/keys/.
|
|
41
|
+
*
|
|
42
|
+
* Example:
|
|
43
|
+
* const km = new KeyManager('/my/project');
|
|
44
|
+
* km.generateKeypair();
|
|
45
|
+
* const sig = km.signEntry({ seq: 0, ... });
|
|
46
|
+
* const ok = km.verifySignature({ seq: 0, ... }, sig);
|
|
47
|
+
*/
|
|
48
|
+
export declare class KeyManager {
|
|
49
|
+
private keysDir;
|
|
50
|
+
constructor(projectPath: string);
|
|
51
|
+
get privateKeyPath(): string;
|
|
52
|
+
get publicKeyPath(): string;
|
|
53
|
+
/**
|
|
54
|
+
* Generate a new Ed25519 keypair and persist it to disk.
|
|
55
|
+
*
|
|
56
|
+
* Creates the key directory (mode 0o700) if it does not exist.
|
|
57
|
+
* Overwrites any existing keypair — rotate deliberately via rotateKey().
|
|
58
|
+
*
|
|
59
|
+
* @returns The key fingerprint (first 16 hex chars of SHA-256 of raw public key bytes).
|
|
60
|
+
*/
|
|
61
|
+
generateKeypair(): string;
|
|
62
|
+
/**
|
|
63
|
+
* Rotate the keypair, returning [oldFingerprint, newFingerprint].
|
|
64
|
+
*
|
|
65
|
+
* The old public key is preserved at old_public.pem during the transition
|
|
66
|
+
* so callers can construct a capability_declared ledger entry referencing
|
|
67
|
+
* both keys. After the transition entry is written, the caller may delete
|
|
68
|
+
* old_public.pem.
|
|
69
|
+
*
|
|
70
|
+
* @returns Tuple of [oldFingerprint, newFingerprint].
|
|
71
|
+
* @throws KeyNotFoundError if no existing keypair is found.
|
|
72
|
+
*/
|
|
73
|
+
rotateKey(): [string, string];
|
|
74
|
+
/**
|
|
75
|
+
* Load the private key from disk, enforcing strict file permissions.
|
|
76
|
+
*
|
|
77
|
+
* @throws KeyNotFoundError if the private key file does not exist.
|
|
78
|
+
* @throws KeyPermissionError if the file mode is broader than 0o600.
|
|
79
|
+
*/
|
|
80
|
+
loadPrivateKey(): KeyObject;
|
|
81
|
+
/**
|
|
82
|
+
* Load the public key from disk.
|
|
83
|
+
*
|
|
84
|
+
* @throws KeyNotFoundError if the public key file does not exist.
|
|
85
|
+
*/
|
|
86
|
+
loadPublicKey(): KeyObject;
|
|
87
|
+
/**
|
|
88
|
+
* Return the raw 32-byte Ed25519 public key material.
|
|
89
|
+
*
|
|
90
|
+
* This replicates Python's:
|
|
91
|
+
* public_key.public_bytes(Encoding.Raw, PublicFormat.Raw)
|
|
92
|
+
* which returns the 32-byte raw Ed25519 public key (not PEM, not DER SPKI).
|
|
93
|
+
*/
|
|
94
|
+
getPublicKeyBytes(): Buffer;
|
|
95
|
+
/**
|
|
96
|
+
* Return the key fingerprint as 16 hex characters.
|
|
97
|
+
*
|
|
98
|
+
* Fingerprint = first 16 hex chars of SHA-256(raw_public_key_bytes).
|
|
99
|
+
*
|
|
100
|
+
* This matches Python's:
|
|
101
|
+
* raw_bytes = public_key.public_bytes(Encoding.Raw, PublicFormat.Raw)
|
|
102
|
+
* digest = hashlib.sha256(raw_bytes).hexdigest()
|
|
103
|
+
* return digest[:16]
|
|
104
|
+
*/
|
|
105
|
+
getFingerprint(): string;
|
|
106
|
+
/**
|
|
107
|
+
* Sign an entry dict with the project's private key.
|
|
108
|
+
*
|
|
109
|
+
* The entry is serialized to canonical JSON (sorted keys, UTF-8) before
|
|
110
|
+
* signing, matching Python's:
|
|
111
|
+
* entry_bytes = json.dumps(entry, sort_keys=True, ensure_ascii=False).encode("utf-8")
|
|
112
|
+
* raw_sig = private_key.sign(entry_bytes)
|
|
113
|
+
* return base64.b64encode(raw_sig).decode("ascii")
|
|
114
|
+
*
|
|
115
|
+
* @param entry - The entry dict to sign (before signature field is set).
|
|
116
|
+
* @returns Base64-encoded (standard alphabet) signature string.
|
|
117
|
+
*/
|
|
118
|
+
signEntry(entry: Record<string, unknown>): string;
|
|
119
|
+
/**
|
|
120
|
+
* Verify an Ed25519 signature against an entry dict.
|
|
121
|
+
*
|
|
122
|
+
* Returns false on any verification failure — never throws.
|
|
123
|
+
*
|
|
124
|
+
* Matches Python's verify_signature which catches all exceptions and
|
|
125
|
+
* returns False rather than propagating.
|
|
126
|
+
*
|
|
127
|
+
* @param entry - The entry dict that was signed.
|
|
128
|
+
* @param signature - Base64-encoded signature string.
|
|
129
|
+
* @returns true if the signature is valid, false otherwise.
|
|
130
|
+
*/
|
|
131
|
+
verifySignature(entry: Record<string, unknown>, signature: string): boolean;
|
|
132
|
+
private assertPrivateKeyPermissions;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=signing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../../src/trust/signing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,QAAQ,CAAC;AAmBhB,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,gBAAiB,SAAQ,YAAY;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,kBAAmB,SAAQ,YAAY;gBACtC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;GAUG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAS;gBAEZ,WAAW,EAAE,MAAM;IAI/B,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,IAAI,aAAa,IAAI,MAAM,CAE1B;IAMD;;;;;;;OAOG;IACH,eAAe,IAAI,MAAM;IAezB;;;;;;;;;;OAUG;IACH,SAAS,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;IAqB7B;;;;;OAKG;IACH,cAAc,IAAI,SAAS;IAa3B;;;;OAIG;IACH,aAAa,IAAI,SAAS;IAW1B;;;;;;OAMG;IACH,iBAAiB,IAAI,MAAM;IAS3B;;;;;;;;;OASG;IACH,cAAc,IAAI,MAAM;IAUxB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IAcjD;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAkB3E,OAAO,CAAC,2BAA2B;CAWpC"}
|