@atbash/sdk 0.16.4-dev.0 → 0.16.6-dev.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/dist/browser.d.mts +23 -13
- package/dist/browser.mjs +25 -11
- package/dist/index.d.mts +23 -13
- package/dist/index.d.ts +23 -13
- package/dist/index.js +173 -73
- package/dist/index.mjs +177 -74
- package/package.json +7 -6
package/dist/browser.d.mts
CHANGED
|
@@ -1111,6 +1111,8 @@ interface GuardMemoryWriteInput extends ClassifyMemoryWriteOptions {
|
|
|
1111
1111
|
verifyPubKey?: string;
|
|
1112
1112
|
/** Org name for chain resolution (public vs org-private chain). */
|
|
1113
1113
|
orgName?: string;
|
|
1114
|
+
/** Explicit chain target when the host already resolved and verified it. */
|
|
1115
|
+
chainOpts?: ChainOpts;
|
|
1114
1116
|
/** LLM confidence threshold for yellow escalation. Default 0.6. */
|
|
1115
1117
|
threshold?: number;
|
|
1116
1118
|
/** When false, red verdicts log but don't block. Default true. */
|
|
@@ -1133,7 +1135,7 @@ interface GuardMemoryWriteResult {
|
|
|
1133
1135
|
decision?: GuardMemoryDecision;
|
|
1134
1136
|
/** Present when handled and scan ran. Absent when scan threw. */
|
|
1135
1137
|
scanResult?: MemoryScanResult;
|
|
1136
|
-
/** Present when handled. True
|
|
1138
|
+
/** Present when handled. True only after the chain transaction confirms. */
|
|
1137
1139
|
committed?: boolean;
|
|
1138
1140
|
}
|
|
1139
1141
|
/**
|
|
@@ -1143,12 +1145,13 @@ interface GuardMemoryWriteResult {
|
|
|
1143
1145
|
*
|
|
1144
1146
|
* Verdict handling:
|
|
1145
1147
|
* - `red` → not persisted to chain; decision blocks (unless `enforce: false`)
|
|
1146
|
-
* - `yellow` → persisted with LLM score; decision allows
|
|
1147
|
-
* - `green` → persisted with LLM score; decision allows
|
|
1148
|
+
* - `yellow` → persisted with its LLM score; decision allows once the chain confirms
|
|
1149
|
+
* - `green` → persisted with its LLM score; decision allows once the chain confirms
|
|
1150
|
+
* - anything else → not persisted; decision blocks (unless `enforce: false`)
|
|
1148
1151
|
*
|
|
1149
|
-
*
|
|
1150
|
-
*
|
|
1151
|
-
*
|
|
1152
|
+
* A write is acknowledged only after the chain transaction confirms, for
|
|
1153
|
+
* yellow exactly as for green. An unconfirmed write blocks in enforce mode
|
|
1154
|
+
* and remains unaudited in observe mode.
|
|
1152
1155
|
*/
|
|
1153
1156
|
declare function guardMemoryWrite(input: GuardMemoryWriteInput): Promise<GuardMemoryWriteResult>;
|
|
1154
1157
|
|
|
@@ -1178,6 +1181,8 @@ interface SyncMemoryOptions {
|
|
|
1178
1181
|
/** Skip the TTL gate and force a chain check this call. Default false. */
|
|
1179
1182
|
force?: boolean;
|
|
1180
1183
|
chainOpts?: ChainOpts;
|
|
1184
|
+
/** Exact chain slot for the managed file; omission keeps the cross-file API behavior. */
|
|
1185
|
+
filePath?: string;
|
|
1181
1186
|
}
|
|
1182
1187
|
/**
|
|
1183
1188
|
* `drifted: false` — pointer is still valid; caller can keep serving the local copy.
|
|
@@ -1226,6 +1231,8 @@ declare function defaultPointerPath(workspaceDir?: string): string;
|
|
|
1226
1231
|
interface WrappedLogger {
|
|
1227
1232
|
info(message: string, meta?: Record<string, unknown>): void;
|
|
1228
1233
|
warn(message: string, meta?: Record<string, unknown>): void;
|
|
1234
|
+
/** Wait for queued writes and surface any lost log entry. */
|
|
1235
|
+
flush(): Promise<void>;
|
|
1229
1236
|
}
|
|
1230
1237
|
interface UpstreamLogger {
|
|
1231
1238
|
info: (...args: unknown[]) => void;
|
|
@@ -1285,10 +1292,10 @@ interface HookDecision {
|
|
|
1285
1292
|
* failure, a rolled-back version), and a host must never re-judge its way past
|
|
1286
1293
|
* one.
|
|
1287
1294
|
*
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1295
|
+
* Absent or false means the guard reached no decision — for example, the scan
|
|
1296
|
+
* never ran or this manager does not own the requested file. In enforce mode,
|
|
1297
|
+
* failed chain verification of the managed file is a block, never an unaudited
|
|
1298
|
+
* allow. In observe mode unverified calls remain unaudited.
|
|
1292
1299
|
*
|
|
1293
1300
|
* So the host rule is:
|
|
1294
1301
|
* `if (d.block) deny; else if (d.audited) allow; else judge it yourself;`
|
|
@@ -1388,9 +1395,9 @@ declare class MemoryGuardManager {
|
|
|
1388
1395
|
runBootProbe(): Promise<void>;
|
|
1389
1396
|
/**
|
|
1390
1397
|
* Returns a `HookDecision` when the guard reached a decision about this event.
|
|
1391
|
-
* Returns `null`
|
|
1392
|
-
*
|
|
1393
|
-
*
|
|
1398
|
+
* Returns `null` for non-memory events, other files this manager does not
|
|
1399
|
+
* own, or unverified observe-mode reads. An enforced read of the managed
|
|
1400
|
+
* file blocks when verification cannot complete.
|
|
1394
1401
|
*
|
|
1395
1402
|
* A returned decision carries `audited` (see `HookDecision`). Only
|
|
1396
1403
|
* `{ allow: true, audited: true }` means "checked and cleared"; anything else
|
|
@@ -1415,6 +1422,9 @@ declare class MemoryGuardManager {
|
|
|
1415
1422
|
*/
|
|
1416
1423
|
private vouchesForTarget;
|
|
1417
1424
|
private handleMemoryRead;
|
|
1425
|
+
/** Complete queued audit-log writes before a host shuts down or removes its workspace. */
|
|
1426
|
+
flushLogs(): Promise<void>;
|
|
1427
|
+
private unverifiedRead;
|
|
1418
1428
|
private writeMemoryAtomic;
|
|
1419
1429
|
}
|
|
1420
1430
|
declare function createMemoryGuardManager(opts: MemoryGuardManagerOptions): MemoryGuardManager;
|
package/dist/browser.mjs
CHANGED
|
@@ -44360,6 +44360,13 @@ function buildAllowedJudgeHosts() {
|
|
|
44360
44360
|
]);
|
|
44361
44361
|
}
|
|
44362
44362
|
var ALLOWED_JUDGE_HOSTS = buildAllowedJudgeHosts();
|
|
44363
|
+
function isAllowlistedJudgeHost(endpoint) {
|
|
44364
|
+
try {
|
|
44365
|
+
return ALLOWED_JUDGE_HOSTS.has(new URL(endpoint).hostname.toLowerCase());
|
|
44366
|
+
} catch {
|
|
44367
|
+
return false;
|
|
44368
|
+
}
|
|
44369
|
+
}
|
|
44363
44370
|
function validateJudgeEndpoint(judge) {
|
|
44364
44371
|
const policy = judge?.policy === "self-hosted" ? "self-hosted" : "default";
|
|
44365
44372
|
const candidate = judge?.endpoint?.trim() || DEFAULT_ENDPOINT;
|
|
@@ -44796,16 +44803,23 @@ var Atbash = class _Atbash {
|
|
|
44796
44803
|
verifying: this.verifyPubKey ? "with response-signature pubkey configured" : "without signature verification"
|
|
44797
44804
|
});
|
|
44798
44805
|
}
|
|
44799
|
-
|
|
44800
|
-
|
|
44801
|
-
|
|
44802
|
-
|
|
44803
|
-
|
|
44804
|
-
|
|
44805
|
-
|
|
44806
|
-
|
|
44807
|
-
|
|
44808
|
-
|
|
44806
|
+
if (isAllowlistedJudgeHost(this.endpoint)) {
|
|
44807
|
+
try {
|
|
44808
|
+
setupTelemetry({
|
|
44809
|
+
enabled: true,
|
|
44810
|
+
endpoint: this.endpoint,
|
|
44811
|
+
getAuthHeaders: () => this.authHeaders()
|
|
44812
|
+
});
|
|
44813
|
+
} catch (err) {
|
|
44814
|
+
this.logger.warn?.(
|
|
44815
|
+
"[atbash] telemetry setup failed \u2014 continuing without metrics",
|
|
44816
|
+
{ error: String(err) }
|
|
44817
|
+
);
|
|
44818
|
+
}
|
|
44819
|
+
} else {
|
|
44820
|
+
this.logger.info?.(
|
|
44821
|
+
"[atbash] telemetry disabled \u2014 the agent bearer is only sent to hosts that already receive it, and this judge endpoint is not one",
|
|
44822
|
+
{ endpoint: this.endpoint }
|
|
44809
44823
|
);
|
|
44810
44824
|
}
|
|
44811
44825
|
}
|
|
@@ -46124,7 +46138,7 @@ async function syncLocalMemory(auth, pointer, opts = {}) {
|
|
|
46124
46138
|
if (withinTtl) {
|
|
46125
46139
|
return { drifted: false, checked: false, pointer };
|
|
46126
46140
|
}
|
|
46127
|
-
const currentId = await getActiveMemoryId(auth, opts.chainOpts);
|
|
46141
|
+
const currentId = await getActiveMemoryId(auth, opts.chainOpts, opts.filePath);
|
|
46128
46142
|
const nextPointer = { activeId: currentId, checkedAt: now };
|
|
46129
46143
|
if (currentId === pointer.activeId) {
|
|
46130
46144
|
return { drifted: false, checked: true, pointer: nextPointer };
|
package/dist/index.d.mts
CHANGED
|
@@ -1111,6 +1111,8 @@ interface GuardMemoryWriteInput extends ClassifyMemoryWriteOptions {
|
|
|
1111
1111
|
verifyPubKey?: string;
|
|
1112
1112
|
/** Org name for chain resolution (public vs org-private chain). */
|
|
1113
1113
|
orgName?: string;
|
|
1114
|
+
/** Explicit chain target when the host already resolved and verified it. */
|
|
1115
|
+
chainOpts?: ChainOpts;
|
|
1114
1116
|
/** LLM confidence threshold for yellow escalation. Default 0.6. */
|
|
1115
1117
|
threshold?: number;
|
|
1116
1118
|
/** When false, red verdicts log but don't block. Default true. */
|
|
@@ -1133,7 +1135,7 @@ interface GuardMemoryWriteResult {
|
|
|
1133
1135
|
decision?: GuardMemoryDecision;
|
|
1134
1136
|
/** Present when handled and scan ran. Absent when scan threw. */
|
|
1135
1137
|
scanResult?: MemoryScanResult;
|
|
1136
|
-
/** Present when handled. True
|
|
1138
|
+
/** Present when handled. True only after the chain transaction confirms. */
|
|
1137
1139
|
committed?: boolean;
|
|
1138
1140
|
}
|
|
1139
1141
|
/**
|
|
@@ -1143,12 +1145,13 @@ interface GuardMemoryWriteResult {
|
|
|
1143
1145
|
*
|
|
1144
1146
|
* Verdict handling:
|
|
1145
1147
|
* - `red` → not persisted to chain; decision blocks (unless `enforce: false`)
|
|
1146
|
-
* - `yellow` → persisted with LLM score; decision allows
|
|
1147
|
-
* - `green` → persisted with LLM score; decision allows
|
|
1148
|
+
* - `yellow` → persisted with its LLM score; decision allows once the chain confirms
|
|
1149
|
+
* - `green` → persisted with its LLM score; decision allows once the chain confirms
|
|
1150
|
+
* - anything else → not persisted; decision blocks (unless `enforce: false`)
|
|
1148
1151
|
*
|
|
1149
|
-
*
|
|
1150
|
-
*
|
|
1151
|
-
*
|
|
1152
|
+
* A write is acknowledged only after the chain transaction confirms, for
|
|
1153
|
+
* yellow exactly as for green. An unconfirmed write blocks in enforce mode
|
|
1154
|
+
* and remains unaudited in observe mode.
|
|
1152
1155
|
*/
|
|
1153
1156
|
declare function guardMemoryWrite(input: GuardMemoryWriteInput): Promise<GuardMemoryWriteResult>;
|
|
1154
1157
|
|
|
@@ -1178,6 +1181,8 @@ interface SyncMemoryOptions {
|
|
|
1178
1181
|
/** Skip the TTL gate and force a chain check this call. Default false. */
|
|
1179
1182
|
force?: boolean;
|
|
1180
1183
|
chainOpts?: ChainOpts;
|
|
1184
|
+
/** Exact chain slot for the managed file; omission keeps the cross-file API behavior. */
|
|
1185
|
+
filePath?: string;
|
|
1181
1186
|
}
|
|
1182
1187
|
/**
|
|
1183
1188
|
* `drifted: false` — pointer is still valid; caller can keep serving the local copy.
|
|
@@ -1226,6 +1231,8 @@ declare function defaultPointerPath(workspaceDir?: string): string;
|
|
|
1226
1231
|
interface WrappedLogger {
|
|
1227
1232
|
info(message: string, meta?: Record<string, unknown>): void;
|
|
1228
1233
|
warn(message: string, meta?: Record<string, unknown>): void;
|
|
1234
|
+
/** Wait for queued writes and surface any lost log entry. */
|
|
1235
|
+
flush(): Promise<void>;
|
|
1229
1236
|
}
|
|
1230
1237
|
interface UpstreamLogger {
|
|
1231
1238
|
info: (...args: unknown[]) => void;
|
|
@@ -1285,10 +1292,10 @@ interface HookDecision {
|
|
|
1285
1292
|
* failure, a rolled-back version), and a host must never re-judge its way past
|
|
1286
1293
|
* one.
|
|
1287
1294
|
*
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1295
|
+
* Absent or false means the guard reached no decision — for example, the scan
|
|
1296
|
+
* never ran or this manager does not own the requested file. In enforce mode,
|
|
1297
|
+
* failed chain verification of the managed file is a block, never an unaudited
|
|
1298
|
+
* allow. In observe mode unverified calls remain unaudited.
|
|
1292
1299
|
*
|
|
1293
1300
|
* So the host rule is:
|
|
1294
1301
|
* `if (d.block) deny; else if (d.audited) allow; else judge it yourself;`
|
|
@@ -1388,9 +1395,9 @@ declare class MemoryGuardManager {
|
|
|
1388
1395
|
runBootProbe(): Promise<void>;
|
|
1389
1396
|
/**
|
|
1390
1397
|
* Returns a `HookDecision` when the guard reached a decision about this event.
|
|
1391
|
-
* Returns `null`
|
|
1392
|
-
*
|
|
1393
|
-
*
|
|
1398
|
+
* Returns `null` for non-memory events, other files this manager does not
|
|
1399
|
+
* own, or unverified observe-mode reads. An enforced read of the managed
|
|
1400
|
+
* file blocks when verification cannot complete.
|
|
1394
1401
|
*
|
|
1395
1402
|
* A returned decision carries `audited` (see `HookDecision`). Only
|
|
1396
1403
|
* `{ allow: true, audited: true }` means "checked and cleared"; anything else
|
|
@@ -1415,6 +1422,9 @@ declare class MemoryGuardManager {
|
|
|
1415
1422
|
*/
|
|
1416
1423
|
private vouchesForTarget;
|
|
1417
1424
|
private handleMemoryRead;
|
|
1425
|
+
/** Complete queued audit-log writes before a host shuts down or removes its workspace. */
|
|
1426
|
+
flushLogs(): Promise<void>;
|
|
1427
|
+
private unverifiedRead;
|
|
1418
1428
|
private writeMemoryAtomic;
|
|
1419
1429
|
}
|
|
1420
1430
|
declare function createMemoryGuardManager(opts: MemoryGuardManagerOptions): MemoryGuardManager;
|
package/dist/index.d.ts
CHANGED
|
@@ -1111,6 +1111,8 @@ interface GuardMemoryWriteInput extends ClassifyMemoryWriteOptions {
|
|
|
1111
1111
|
verifyPubKey?: string;
|
|
1112
1112
|
/** Org name for chain resolution (public vs org-private chain). */
|
|
1113
1113
|
orgName?: string;
|
|
1114
|
+
/** Explicit chain target when the host already resolved and verified it. */
|
|
1115
|
+
chainOpts?: ChainOpts;
|
|
1114
1116
|
/** LLM confidence threshold for yellow escalation. Default 0.6. */
|
|
1115
1117
|
threshold?: number;
|
|
1116
1118
|
/** When false, red verdicts log but don't block. Default true. */
|
|
@@ -1133,7 +1135,7 @@ interface GuardMemoryWriteResult {
|
|
|
1133
1135
|
decision?: GuardMemoryDecision;
|
|
1134
1136
|
/** Present when handled and scan ran. Absent when scan threw. */
|
|
1135
1137
|
scanResult?: MemoryScanResult;
|
|
1136
|
-
/** Present when handled. True
|
|
1138
|
+
/** Present when handled. True only after the chain transaction confirms. */
|
|
1137
1139
|
committed?: boolean;
|
|
1138
1140
|
}
|
|
1139
1141
|
/**
|
|
@@ -1143,12 +1145,13 @@ interface GuardMemoryWriteResult {
|
|
|
1143
1145
|
*
|
|
1144
1146
|
* Verdict handling:
|
|
1145
1147
|
* - `red` → not persisted to chain; decision blocks (unless `enforce: false`)
|
|
1146
|
-
* - `yellow` → persisted with LLM score; decision allows
|
|
1147
|
-
* - `green` → persisted with LLM score; decision allows
|
|
1148
|
+
* - `yellow` → persisted with its LLM score; decision allows once the chain confirms
|
|
1149
|
+
* - `green` → persisted with its LLM score; decision allows once the chain confirms
|
|
1150
|
+
* - anything else → not persisted; decision blocks (unless `enforce: false`)
|
|
1148
1151
|
*
|
|
1149
|
-
*
|
|
1150
|
-
*
|
|
1151
|
-
*
|
|
1152
|
+
* A write is acknowledged only after the chain transaction confirms, for
|
|
1153
|
+
* yellow exactly as for green. An unconfirmed write blocks in enforce mode
|
|
1154
|
+
* and remains unaudited in observe mode.
|
|
1152
1155
|
*/
|
|
1153
1156
|
declare function guardMemoryWrite(input: GuardMemoryWriteInput): Promise<GuardMemoryWriteResult>;
|
|
1154
1157
|
|
|
@@ -1178,6 +1181,8 @@ interface SyncMemoryOptions {
|
|
|
1178
1181
|
/** Skip the TTL gate and force a chain check this call. Default false. */
|
|
1179
1182
|
force?: boolean;
|
|
1180
1183
|
chainOpts?: ChainOpts;
|
|
1184
|
+
/** Exact chain slot for the managed file; omission keeps the cross-file API behavior. */
|
|
1185
|
+
filePath?: string;
|
|
1181
1186
|
}
|
|
1182
1187
|
/**
|
|
1183
1188
|
* `drifted: false` — pointer is still valid; caller can keep serving the local copy.
|
|
@@ -1226,6 +1231,8 @@ declare function defaultPointerPath(workspaceDir?: string): string;
|
|
|
1226
1231
|
interface WrappedLogger {
|
|
1227
1232
|
info(message: string, meta?: Record<string, unknown>): void;
|
|
1228
1233
|
warn(message: string, meta?: Record<string, unknown>): void;
|
|
1234
|
+
/** Wait for queued writes and surface any lost log entry. */
|
|
1235
|
+
flush(): Promise<void>;
|
|
1229
1236
|
}
|
|
1230
1237
|
interface UpstreamLogger {
|
|
1231
1238
|
info: (...args: unknown[]) => void;
|
|
@@ -1285,10 +1292,10 @@ interface HookDecision {
|
|
|
1285
1292
|
* failure, a rolled-back version), and a host must never re-judge its way past
|
|
1286
1293
|
* one.
|
|
1287
1294
|
*
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1295
|
+
* Absent or false means the guard reached no decision — for example, the scan
|
|
1296
|
+
* never ran or this manager does not own the requested file. In enforce mode,
|
|
1297
|
+
* failed chain verification of the managed file is a block, never an unaudited
|
|
1298
|
+
* allow. In observe mode unverified calls remain unaudited.
|
|
1292
1299
|
*
|
|
1293
1300
|
* So the host rule is:
|
|
1294
1301
|
* `if (d.block) deny; else if (d.audited) allow; else judge it yourself;`
|
|
@@ -1388,9 +1395,9 @@ declare class MemoryGuardManager {
|
|
|
1388
1395
|
runBootProbe(): Promise<void>;
|
|
1389
1396
|
/**
|
|
1390
1397
|
* Returns a `HookDecision` when the guard reached a decision about this event.
|
|
1391
|
-
* Returns `null`
|
|
1392
|
-
*
|
|
1393
|
-
*
|
|
1398
|
+
* Returns `null` for non-memory events, other files this manager does not
|
|
1399
|
+
* own, or unverified observe-mode reads. An enforced read of the managed
|
|
1400
|
+
* file blocks when verification cannot complete.
|
|
1394
1401
|
*
|
|
1395
1402
|
* A returned decision carries `audited` (see `HookDecision`). Only
|
|
1396
1403
|
* `{ allow: true, audited: true }` means "checked and cleared"; anything else
|
|
@@ -1415,6 +1422,9 @@ declare class MemoryGuardManager {
|
|
|
1415
1422
|
*/
|
|
1416
1423
|
private vouchesForTarget;
|
|
1417
1424
|
private handleMemoryRead;
|
|
1425
|
+
/** Complete queued audit-log writes before a host shuts down or removes its workspace. */
|
|
1426
|
+
flushLogs(): Promise<void>;
|
|
1427
|
+
private unverifiedRead;
|
|
1418
1428
|
private writeMemoryAtomic;
|
|
1419
1429
|
}
|
|
1420
1430
|
declare function createMemoryGuardManager(opts: MemoryGuardManagerOptions): MemoryGuardManager;
|
package/dist/index.js
CHANGED
|
@@ -3026,6 +3026,13 @@ function buildAllowedJudgeHosts() {
|
|
|
3026
3026
|
]);
|
|
3027
3027
|
}
|
|
3028
3028
|
var ALLOWED_JUDGE_HOSTS = buildAllowedJudgeHosts();
|
|
3029
|
+
function isAllowlistedJudgeHost(endpoint) {
|
|
3030
|
+
try {
|
|
3031
|
+
return ALLOWED_JUDGE_HOSTS.has(new URL(endpoint).hostname.toLowerCase());
|
|
3032
|
+
} catch {
|
|
3033
|
+
return false;
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3029
3036
|
function validateJudgeEndpoint(judge) {
|
|
3030
3037
|
const policy = judge?.policy === "self-hosted" ? "self-hosted" : "default";
|
|
3031
3038
|
const candidate = judge?.endpoint?.trim() || DEFAULT_ENDPOINT;
|
|
@@ -3362,6 +3369,7 @@ var callCounter = null;
|
|
|
3362
3369
|
var durationHistogram = null;
|
|
3363
3370
|
var defaultSource = "sdk";
|
|
3364
3371
|
var sourceExplicitlySet = false;
|
|
3372
|
+
var requestedExportIntervalMs;
|
|
3365
3373
|
function isTelemetryOptedOut() {
|
|
3366
3374
|
const disabled = process.env.ATBASH_TELEMETRY_DISABLED?.trim().toLowerCase();
|
|
3367
3375
|
if (disabled && ["1", "true", "yes", "on"].includes(disabled)) {
|
|
@@ -3390,18 +3398,36 @@ function setupTelemetry(config2) {
|
|
|
3390
3398
|
defaultSource = config2.source;
|
|
3391
3399
|
sourceExplicitlySet = true;
|
|
3392
3400
|
}
|
|
3401
|
+
if (config2.exportIntervalMs !== void 0 && requestedExportIntervalMs === void 0) {
|
|
3402
|
+
requestedExportIntervalMs = config2.exportIntervalMs;
|
|
3403
|
+
}
|
|
3393
3404
|
if (meterProvider) return;
|
|
3394
3405
|
if (!config2.endpoint || !config2.getAuthHeaders) return;
|
|
3395
|
-
if (/^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)(:|\/|$)/i.test(
|
|
3406
|
+
if (/^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)(:|\/|$)/i.test(
|
|
3407
|
+
config2.endpoint
|
|
3408
|
+
))
|
|
3409
|
+
return;
|
|
3396
3410
|
const proxyUrl = `${config2.endpoint.replace(/\/+$/, "")}/api/telemetry`;
|
|
3397
3411
|
const getAuthHeaders = config2.getAuthHeaders;
|
|
3398
3412
|
const exporter = new import_exporter_metrics_otlp_http.OTLPMetricExporter({
|
|
3399
3413
|
url: proxyUrl,
|
|
3400
|
-
headers: async () => getAuthHeaders()
|
|
3414
|
+
headers: async () => getAuthHeaders(),
|
|
3415
|
+
// DELTA, not the OTel default of CUMULATIVE.
|
|
3416
|
+
//
|
|
3417
|
+
// A cumulative counter reports a running total, and the backend recovers
|
|
3418
|
+
// the increment by differencing consecutive points of a series. That needs
|
|
3419
|
+
// at least two points. Most Atbash callers are short-lived — a CLI run
|
|
3420
|
+
// exports once and exits — so their series has exactly one point, nothing
|
|
3421
|
+
// to difference against, and the usage board reads them as zero: the calls
|
|
3422
|
+
// arrive, carry the right `source`, and still count for nothing.
|
|
3423
|
+
//
|
|
3424
|
+
// A delta counter reports the increment itself, so a single export is a
|
|
3425
|
+
// whole truthful datapoint and SUM over any window is the real call count.
|
|
3426
|
+
temporalityPreference: import_exporter_metrics_otlp_http.AggregationTemporalityPreference.DELTA
|
|
3401
3427
|
});
|
|
3402
3428
|
const reader = new import_sdk_metrics.PeriodicExportingMetricReader({
|
|
3403
3429
|
exporter,
|
|
3404
|
-
exportIntervalMillis:
|
|
3430
|
+
exportIntervalMillis: requestedExportIntervalMs ?? 6e4
|
|
3405
3431
|
});
|
|
3406
3432
|
meterProvider = new import_sdk_metrics.MeterProvider({
|
|
3407
3433
|
resource: (0, import_resources.resourceFromAttributes)({
|
|
@@ -3447,6 +3473,7 @@ async function shutdownTelemetry() {
|
|
|
3447
3473
|
durationHistogram = null;
|
|
3448
3474
|
defaultSource = "sdk";
|
|
3449
3475
|
sourceExplicitlySet = false;
|
|
3476
|
+
requestedExportIntervalMs = void 0;
|
|
3450
3477
|
}
|
|
3451
3478
|
|
|
3452
3479
|
// src-ts/userConfig.ts
|
|
@@ -3634,16 +3661,23 @@ var Atbash = class _Atbash {
|
|
|
3634
3661
|
verifying: this.verifyPubKey ? "with response-signature pubkey configured" : "without signature verification"
|
|
3635
3662
|
});
|
|
3636
3663
|
}
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3664
|
+
if (isAllowlistedJudgeHost(this.endpoint)) {
|
|
3665
|
+
try {
|
|
3666
|
+
setupTelemetry({
|
|
3667
|
+
enabled: true,
|
|
3668
|
+
endpoint: this.endpoint,
|
|
3669
|
+
getAuthHeaders: () => this.authHeaders()
|
|
3670
|
+
});
|
|
3671
|
+
} catch (err) {
|
|
3672
|
+
this.logger.warn?.(
|
|
3673
|
+
"[atbash] telemetry setup failed \u2014 continuing without metrics",
|
|
3674
|
+
{ error: String(err) }
|
|
3675
|
+
);
|
|
3676
|
+
}
|
|
3677
|
+
} else {
|
|
3678
|
+
this.logger.info?.(
|
|
3679
|
+
"[atbash] telemetry disabled \u2014 the agent bearer is only sent to hosts that already receive it, and this judge endpoint is not one",
|
|
3680
|
+
{ endpoint: this.endpoint }
|
|
3647
3681
|
);
|
|
3648
3682
|
}
|
|
3649
3683
|
}
|
|
@@ -43041,7 +43075,7 @@ async function commitMemoryVersion(plaintext, auth, opts) {
|
|
|
43041
43075
|
const chainOpts = await resolveChainOptsForOrg(opts, auth);
|
|
43042
43076
|
const client = await buildChainClient(chainOpts);
|
|
43043
43077
|
const { sigProvider } = buildSigner(auth);
|
|
43044
|
-
await client.signAndSendUniqueTransaction(
|
|
43078
|
+
const receipt = await client.signAndSendUniqueTransaction(
|
|
43045
43079
|
{
|
|
43046
43080
|
name: native.OP_ADD_AGENT_MEMORY,
|
|
43047
43081
|
args: [
|
|
@@ -43054,6 +43088,9 @@ async function commitMemoryVersion(plaintext, auth, opts) {
|
|
|
43054
43088
|
},
|
|
43055
43089
|
sigProvider
|
|
43056
43090
|
);
|
|
43091
|
+
if (receipt.status !== index.ResponseStatus.Confirmed) {
|
|
43092
|
+
throw new Error(`Memory transaction was not confirmed (status=${receipt.status})`);
|
|
43093
|
+
}
|
|
43057
43094
|
}
|
|
43058
43095
|
async function decryptRow(row, key3) {
|
|
43059
43096
|
const parsed = native.parseMemoryRow(gtvToFfiSafe(row));
|
|
@@ -43163,13 +43200,16 @@ async function rollbackMemory(toId, reason, auth, opts) {
|
|
|
43163
43200
|
const chainOpts = await resolveChainOptsForOrg(opts, auth);
|
|
43164
43201
|
const client = await buildChainClient(chainOpts);
|
|
43165
43202
|
const { sigProvider } = buildSigner(auth);
|
|
43166
|
-
await client.signAndSendUniqueTransaction(
|
|
43203
|
+
const receipt = await client.signAndSendUniqueTransaction(
|
|
43167
43204
|
{
|
|
43168
43205
|
name: native.OP_ROLLBACK_AGENT_MEMORY,
|
|
43169
43206
|
args: [toGtxBytes(args.agent_pubkey), args.to_id, args.reason]
|
|
43170
43207
|
},
|
|
43171
43208
|
sigProvider
|
|
43172
43209
|
);
|
|
43210
|
+
if (receipt.status !== index.ResponseStatus.Confirmed) {
|
|
43211
|
+
throw new Error(`Memory rollback was not confirmed (status=${receipt.status})`);
|
|
43212
|
+
}
|
|
43173
43213
|
}
|
|
43174
43214
|
|
|
43175
43215
|
// src-ts/memory/_json_safe.ts
|
|
@@ -43308,6 +43348,7 @@ async function guardMemoryWrite(input) {
|
|
|
43308
43348
|
endpoint,
|
|
43309
43349
|
verifyPubKey,
|
|
43310
43350
|
orgName,
|
|
43351
|
+
chainOpts,
|
|
43311
43352
|
threshold,
|
|
43312
43353
|
patterns,
|
|
43313
43354
|
toolNames,
|
|
@@ -43364,23 +43405,41 @@ async function guardMemoryWrite(input) {
|
|
|
43364
43405
|
committed: false
|
|
43365
43406
|
};
|
|
43366
43407
|
}
|
|
43408
|
+
if (scanResult.verdict !== "green" && scanResult.verdict !== "yellow") {
|
|
43409
|
+
const reason = "Memory scan returned an unrecognized verdict.";
|
|
43410
|
+
return {
|
|
43411
|
+
handled: true,
|
|
43412
|
+
decision: enforce ? { allow: false, block: true, reason } : { allow: true, reason },
|
|
43413
|
+
scanResult,
|
|
43414
|
+
committed: false
|
|
43415
|
+
};
|
|
43416
|
+
}
|
|
43367
43417
|
const filePath = import_node_path4.default.basename(memEntry.key);
|
|
43368
|
-
|
|
43369
|
-
|
|
43370
|
-
|
|
43371
|
-
|
|
43372
|
-
|
|
43373
|
-
|
|
43374
|
-
|
|
43418
|
+
try {
|
|
43419
|
+
await commitMemoryVersion(memEntry.value, auth, {
|
|
43420
|
+
score: scanResult.score,
|
|
43421
|
+
filePath,
|
|
43422
|
+
orgName,
|
|
43423
|
+
endpoint,
|
|
43424
|
+
verifyPubKey,
|
|
43425
|
+
chainOpts
|
|
43426
|
+
});
|
|
43427
|
+
} catch (err) {
|
|
43375
43428
|
const reason = err instanceof Error ? err.message : String(err);
|
|
43376
43429
|
logger2?.warn?.("[atbash] memory commit to chain failed", {
|
|
43377
43430
|
path: memEntry.key,
|
|
43378
43431
|
filePath,
|
|
43379
43432
|
reason
|
|
43380
43433
|
});
|
|
43381
|
-
|
|
43434
|
+
return {
|
|
43435
|
+
handled: true,
|
|
43436
|
+
decision: enforce ? { allow: false, block: true, reason: `Memory commit failed: ${reason}` } : { allow: true, reason: `Memory commit failed: ${reason}` },
|
|
43437
|
+
scanResult,
|
|
43438
|
+
committed: false
|
|
43439
|
+
};
|
|
43440
|
+
}
|
|
43382
43441
|
logger2?.info?.(
|
|
43383
|
-
scanResult.verdict === "yellow" ? "[atbash] memory
|
|
43442
|
+
scanResult.verdict === "yellow" ? "[atbash] memory ALLOW (yellow, low score)" : "[atbash] memory ALLOW",
|
|
43384
43443
|
{
|
|
43385
43444
|
path: memEntry.key,
|
|
43386
43445
|
filePath,
|
|
@@ -43413,7 +43472,7 @@ async function syncLocalMemory(auth, pointer, opts = {}) {
|
|
|
43413
43472
|
if (withinTtl) {
|
|
43414
43473
|
return { drifted: false, checked: false, pointer };
|
|
43415
43474
|
}
|
|
43416
|
-
const currentId = await getActiveMemoryId(auth, opts.chainOpts);
|
|
43475
|
+
const currentId = await getActiveMemoryId(auth, opts.chainOpts, opts.filePath);
|
|
43417
43476
|
const nextPointer = { activeId: currentId, checkedAt: now };
|
|
43418
43477
|
if (currentId === pointer.activeId) {
|
|
43419
43478
|
return { drifted: false, checked: true, pointer: nextPointer };
|
|
@@ -43495,13 +43554,16 @@ function formatMeta(meta) {
|
|
|
43495
43554
|
}
|
|
43496
43555
|
function createFileLogger(filePath, upstream) {
|
|
43497
43556
|
let queue = Promise.resolve();
|
|
43557
|
+
let writeError;
|
|
43498
43558
|
async function ensureDir() {
|
|
43499
43559
|
await import_node_fs5.promises.mkdir(import_node_path6.default.dirname(filePath), { recursive: true });
|
|
43500
43560
|
}
|
|
43501
43561
|
function append(level, message, meta) {
|
|
43502
43562
|
const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${message}${formatMeta(meta)}
|
|
43503
43563
|
`;
|
|
43504
|
-
queue = queue.then(ensureDir).then(() => import_node_fs5.promises.appendFile(filePath, line, "utf8")).catch(() => {
|
|
43564
|
+
queue = queue.then(ensureDir).then(() => import_node_fs5.promises.appendFile(filePath, line, "utf8")).catch((error4) => {
|
|
43565
|
+
writeError = error4 instanceof Error ? error4 : new Error(String(error4));
|
|
43566
|
+
upstream?.warn("[atbash] plugin log write failed", { error: writeError.message });
|
|
43505
43567
|
});
|
|
43506
43568
|
}
|
|
43507
43569
|
return {
|
|
@@ -43512,6 +43574,10 @@ function createFileLogger(filePath, upstream) {
|
|
|
43512
43574
|
warn(message, meta) {
|
|
43513
43575
|
upstream?.warn(message, meta ?? {});
|
|
43514
43576
|
append("warn", message, meta);
|
|
43577
|
+
},
|
|
43578
|
+
async flush() {
|
|
43579
|
+
await queue;
|
|
43580
|
+
if (writeError) throw writeError;
|
|
43515
43581
|
}
|
|
43516
43582
|
};
|
|
43517
43583
|
}
|
|
@@ -43601,7 +43667,15 @@ var MemoryGuardManager = class {
|
|
|
43601
43667
|
orgName: this.opts.orgName
|
|
43602
43668
|
});
|
|
43603
43669
|
const resolved = await atbash.resolveChainForOrg(this.opts.orgName);
|
|
43604
|
-
|
|
43670
|
+
if (!await atbash.checkAgentExists(this.agentPubkeyHex, { network: resolved.network })) {
|
|
43671
|
+
throw new Error(`agent is not registered on the resolved ${resolved.network} chain`);
|
|
43672
|
+
}
|
|
43673
|
+
const chainOpts = {
|
|
43674
|
+
...this.opts.chainOpts,
|
|
43675
|
+
network: resolved.network,
|
|
43676
|
+
blockchainRid: resolved.blockchainRid,
|
|
43677
|
+
nodeUrls: this.opts.chainOpts?.nodeUrls ?? resolved.nodeUrls
|
|
43678
|
+
};
|
|
43605
43679
|
this._resolvedChainOpts = chainOpts;
|
|
43606
43680
|
this.logger.info(
|
|
43607
43681
|
`[atbash] resolved org \u2192 chain \u2014 org=${this.opts.orgName} network=${resolved.network}`
|
|
@@ -43610,9 +43684,9 @@ var MemoryGuardManager = class {
|
|
|
43610
43684
|
} catch (err) {
|
|
43611
43685
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43612
43686
|
this.logger.warn(
|
|
43613
|
-
`[atbash] org \u2192 chain resolution failed (
|
|
43687
|
+
`[atbash] org \u2192 chain resolution failed (memory read will be blocked): ${msg}`
|
|
43614
43688
|
);
|
|
43615
|
-
|
|
43689
|
+
throw err;
|
|
43616
43690
|
} finally {
|
|
43617
43691
|
this._resolveChainInflight = void 0;
|
|
43618
43692
|
}
|
|
@@ -43631,13 +43705,18 @@ var MemoryGuardManager = class {
|
|
|
43631
43705
|
const result = await syncLocalMemory(this.opts.auth, seed, {
|
|
43632
43706
|
ttlMs: 0,
|
|
43633
43707
|
force: true,
|
|
43634
|
-
chainOpts
|
|
43708
|
+
chainOpts,
|
|
43709
|
+
filePath: import_node_path7.default.basename(this.memoryFilePath)
|
|
43635
43710
|
});
|
|
43636
43711
|
if (!result.drifted && result.pointer.activeId == null) {
|
|
43637
43712
|
this.logger.info(
|
|
43638
43713
|
`[atbash] no active memory on chain for agent=${this.agentPubkeyHex.slice(0, 16)}\u2026 org=${this.opts.orgName ?? "(none)"} \u2014 either the agent isn't registered on this chain or hasn't written any memory. Sync will remain a no-op until a write lands.`
|
|
43639
43714
|
);
|
|
43640
43715
|
} else if (result.drifted && result.current) {
|
|
43716
|
+
if (result.current.filePath !== import_node_path7.default.basename(this.memoryFilePath) || result.current.isActive !== true || result.current.decryptError) {
|
|
43717
|
+
this.logger.warn("[atbash] boot sync REFUSED mismatched memory version");
|
|
43718
|
+
return;
|
|
43719
|
+
}
|
|
43641
43720
|
if (result.current.score < this.rollbackMinScore) {
|
|
43642
43721
|
this.logger.warn(
|
|
43643
43722
|
`[atbash] boot sync REFUSED refresh \u2014 id=${result.current.id} score=${result.current.score} below threshold ${this.rollbackMinScore}. Leaving MEMORY.md and pointer untouched; next memory read will be blocked.`
|
|
@@ -43651,7 +43730,8 @@ var MemoryGuardManager = class {
|
|
|
43651
43730
|
await this.writeMemoryAtomic(result.current.content);
|
|
43652
43731
|
} catch (err) {
|
|
43653
43732
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43654
|
-
this.logger.warn("[atbash] boot sync write failed
|
|
43733
|
+
this.logger.warn("[atbash] boot sync write failed; pointer remains unchanged", { error: msg });
|
|
43734
|
+
return;
|
|
43655
43735
|
}
|
|
43656
43736
|
}
|
|
43657
43737
|
await this.pointerStore.set(this.agentPubkeyHex, result.pointer);
|
|
@@ -43665,9 +43745,9 @@ var MemoryGuardManager = class {
|
|
|
43665
43745
|
}
|
|
43666
43746
|
/**
|
|
43667
43747
|
* Returns a `HookDecision` when the guard reached a decision about this event.
|
|
43668
|
-
* Returns `null`
|
|
43669
|
-
*
|
|
43670
|
-
*
|
|
43748
|
+
* Returns `null` for non-memory events, other files this manager does not
|
|
43749
|
+
* own, or unverified observe-mode reads. An enforced read of the managed
|
|
43750
|
+
* file blocks when verification cannot complete.
|
|
43671
43751
|
*
|
|
43672
43752
|
* A returned decision carries `audited` (see `HookDecision`). Only
|
|
43673
43753
|
* `{ allow: true, audited: true }` means "checked and cleared"; anything else
|
|
@@ -43703,6 +43783,7 @@ var MemoryGuardManager = class {
|
|
|
43703
43783
|
endpoint: this.opts.judgeEndpoint,
|
|
43704
43784
|
verifyPubKey: this.opts.judgeVerifyPubKey,
|
|
43705
43785
|
orgName: this.opts.orgName,
|
|
43786
|
+
chainOpts: this.opts.chainOpts,
|
|
43706
43787
|
patterns: this.opts.memoryPathPatterns,
|
|
43707
43788
|
toolNames: this.opts.memoryWriteToolNames,
|
|
43708
43789
|
enforce: this.enforce,
|
|
@@ -43747,6 +43828,14 @@ var MemoryGuardManager = class {
|
|
|
43747
43828
|
reason: `memory scan returned ${sr2.verdict} but this guard is not enforcing it`
|
|
43748
43829
|
};
|
|
43749
43830
|
}
|
|
43831
|
+
if (guard.committed !== true) {
|
|
43832
|
+
return {
|
|
43833
|
+
allow: true,
|
|
43834
|
+
audited: false,
|
|
43835
|
+
verdict: sr2.verdict,
|
|
43836
|
+
reason: "memory scan passed but chain commit did not confirm"
|
|
43837
|
+
};
|
|
43838
|
+
}
|
|
43750
43839
|
return { allow: true, audited: true, verdict: sr2.verdict };
|
|
43751
43840
|
}
|
|
43752
43841
|
/**
|
|
@@ -43781,62 +43870,73 @@ var MemoryGuardManager = class {
|
|
|
43781
43870
|
return found.every((p) => import_node_path7.default.resolve(p) === managed);
|
|
43782
43871
|
}
|
|
43783
43872
|
async handleMemoryRead(event, ctx) {
|
|
43873
|
+
if (!this.vouchesForTarget(event, ctx)) return null;
|
|
43784
43874
|
const pointer = await this.pointerStore.get(this.agentPubkeyHex);
|
|
43785
43875
|
let result;
|
|
43876
|
+
let chainOpts;
|
|
43786
43877
|
try {
|
|
43787
|
-
|
|
43878
|
+
chainOpts = await this.resolveChainOpts();
|
|
43788
43879
|
result = await syncLocalMemory(this.opts.auth, pointer, {
|
|
43789
43880
|
ttlMs: this.ttlMs,
|
|
43790
|
-
|
|
43881
|
+
force: this.enforce,
|
|
43882
|
+
chainOpts,
|
|
43883
|
+
filePath: import_node_path7.default.basename(this.memoryFilePath)
|
|
43791
43884
|
});
|
|
43792
43885
|
} catch (err) {
|
|
43793
43886
|
if (err instanceof MemoryIntegrityError) {
|
|
43794
43887
|
const reason = `Memory integrity check failed on id ${err.id} \u2014 read blocked.`;
|
|
43795
43888
|
this.logger.warn("[atbash] MEMORY INTEGRITY FAILURE", { id: err.id, error: err.message });
|
|
43796
|
-
|
|
43797
|
-
return { block: true, blockReason: reason, allow: false, reason, audited: true };
|
|
43889
|
+
return this.unverifiedRead(reason);
|
|
43798
43890
|
}
|
|
43799
43891
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43800
|
-
this.logger.warn("[atbash] memory sync failed
|
|
43801
|
-
return
|
|
43892
|
+
this.logger.warn("[atbash] memory sync failed", { error: msg });
|
|
43893
|
+
return this.unverifiedRead(`Memory chain sync failed: ${msg}`);
|
|
43802
43894
|
}
|
|
43803
|
-
|
|
43804
|
-
|
|
43805
|
-
|
|
43806
|
-
|
|
43807
|
-
if (fresh.score < this.rollbackMinScore) {
|
|
43808
|
-
const reason = `Rolled-back memory version #${fresh.id} scored ${fresh.score} (below threshold ${this.rollbackMinScore}) \u2014 read blocked.`;
|
|
43809
|
-
this.logger.warn("[atbash] blocking read on low-score rollback", { id: fresh.id, score: fresh.score });
|
|
43810
|
-
if (!this.enforce) return null;
|
|
43811
|
-
return { block: true, blockReason: reason, allow: false, reason, audited: true };
|
|
43812
|
-
}
|
|
43813
|
-
this.logger.info("[atbash] memory drift detected \u2014 refreshing local file", {
|
|
43814
|
-
id: fresh.id,
|
|
43815
|
-
score: fresh.score
|
|
43816
|
-
});
|
|
43817
|
-
try {
|
|
43818
|
-
await this.writeMemoryAtomic(fresh.content);
|
|
43819
|
-
} catch (err) {
|
|
43820
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
43821
|
-
this.logger.warn("[atbash] failed to write refreshed memory (serving old)", { error: msg });
|
|
43822
|
-
onDiskIsCurrent = false;
|
|
43823
|
-
}
|
|
43824
|
-
} else {
|
|
43825
|
-
this.logger.info("[atbash] active memory removed on chain", { pubkey: this.agentPubkeyHex });
|
|
43826
|
-
onDiskIsCurrent = false;
|
|
43827
|
-
}
|
|
43895
|
+
if (!result.checked) return this.unverifiedRead("Memory chain state was not checked.");
|
|
43896
|
+
const activeId = result.pointer.activeId;
|
|
43897
|
+
if (activeId === null) {
|
|
43898
|
+
return this.unverifiedRead("No active chain version exists for the managed memory file.");
|
|
43828
43899
|
}
|
|
43829
|
-
|
|
43830
|
-
|
|
43831
|
-
|
|
43832
|
-
|
|
43833
|
-
|
|
43834
|
-
);
|
|
43900
|
+
let active;
|
|
43901
|
+
try {
|
|
43902
|
+
active = result.drifted ? result.current : await getMemoryById(activeId, this.opts.auth, chainOpts);
|
|
43903
|
+
} catch (err) {
|
|
43904
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
43905
|
+
this.logger.warn("[atbash] active memory row could not be verified", { error: msg });
|
|
43906
|
+
return this.unverifiedRead(`Memory version could not be verified: ${msg}`);
|
|
43835
43907
|
}
|
|
43836
|
-
|
|
43837
|
-
if (!
|
|
43908
|
+
const filePath = import_node_path7.default.basename(this.memoryFilePath);
|
|
43909
|
+
if (!active || active.id !== activeId || active.filePath !== filePath || active.isActive !== true || active.decryptError) {
|
|
43910
|
+
return this.unverifiedRead("Active memory version does not match the managed file.");
|
|
43911
|
+
}
|
|
43912
|
+
if (active.score < this.rollbackMinScore) {
|
|
43913
|
+
return this.unverifiedRead(`Memory version #${active.id} is below the minimum safety score.`);
|
|
43914
|
+
}
|
|
43915
|
+
try {
|
|
43916
|
+
if (result.drifted) {
|
|
43917
|
+
this.logger.info("[atbash] memory drift detected \u2014 refreshing local file", { id: active.id });
|
|
43918
|
+
await this.writeMemoryAtomic(active.content);
|
|
43919
|
+
}
|
|
43920
|
+
const onDisk = await import_node_fs6.promises.readFile(this.memoryFilePath, "utf8");
|
|
43921
|
+
if (onDisk !== active.content) {
|
|
43922
|
+
return this.unverifiedRead("Local memory bytes do not match the active chain version.");
|
|
43923
|
+
}
|
|
43924
|
+
} catch (err) {
|
|
43925
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
43926
|
+
this.logger.warn("[atbash] managed memory file could not be verified", { error: msg });
|
|
43927
|
+
return this.unverifiedRead(`Managed memory file could not be verified: ${msg}`);
|
|
43928
|
+
}
|
|
43929
|
+
await this.pointerStore.set(this.agentPubkeyHex, result.pointer);
|
|
43838
43930
|
return { allow: true, audited: true };
|
|
43839
43931
|
}
|
|
43932
|
+
/** Complete queued audit-log writes before a host shuts down or removes its workspace. */
|
|
43933
|
+
async flushLogs() {
|
|
43934
|
+
await this.logger.flush();
|
|
43935
|
+
}
|
|
43936
|
+
unverifiedRead(reason) {
|
|
43937
|
+
if (!this.enforce) return null;
|
|
43938
|
+
return { block: true, blockReason: reason, allow: false, reason, audited: true };
|
|
43939
|
+
}
|
|
43840
43940
|
async writeMemoryAtomic(content) {
|
|
43841
43941
|
await import_node_fs6.promises.mkdir(import_node_path7.default.dirname(this.memoryFilePath), { recursive: true });
|
|
43842
43942
|
const tmp = `${this.memoryFilePath}.${process.pid}.tmp`;
|
package/dist/index.mjs
CHANGED
|
@@ -2930,6 +2930,13 @@ function buildAllowedJudgeHosts() {
|
|
|
2930
2930
|
]);
|
|
2931
2931
|
}
|
|
2932
2932
|
var ALLOWED_JUDGE_HOSTS = buildAllowedJudgeHosts();
|
|
2933
|
+
function isAllowlistedJudgeHost(endpoint) {
|
|
2934
|
+
try {
|
|
2935
|
+
return ALLOWED_JUDGE_HOSTS.has(new URL(endpoint).hostname.toLowerCase());
|
|
2936
|
+
} catch {
|
|
2937
|
+
return false;
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2933
2940
|
function validateJudgeEndpoint(judge) {
|
|
2934
2941
|
const policy = judge?.policy === "self-hosted" ? "self-hosted" : "default";
|
|
2935
2942
|
const candidate = judge?.endpoint?.trim() || DEFAULT_ENDPOINT;
|
|
@@ -3258,7 +3265,10 @@ function canonicalAllow(data) {
|
|
|
3258
3265
|
import { readFileSync as readFileSync2 } from "fs";
|
|
3259
3266
|
import { homedir as homedir2 } from "os";
|
|
3260
3267
|
import { join as join2 } from "path";
|
|
3261
|
-
import {
|
|
3268
|
+
import {
|
|
3269
|
+
AggregationTemporalityPreference,
|
|
3270
|
+
OTLPMetricExporter
|
|
3271
|
+
} from "@opentelemetry/exporter-metrics-otlp-http";
|
|
3262
3272
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
3263
3273
|
import {
|
|
3264
3274
|
MeterProvider,
|
|
@@ -3269,6 +3279,7 @@ var callCounter = null;
|
|
|
3269
3279
|
var durationHistogram = null;
|
|
3270
3280
|
var defaultSource = "sdk";
|
|
3271
3281
|
var sourceExplicitlySet = false;
|
|
3282
|
+
var requestedExportIntervalMs;
|
|
3272
3283
|
function isTelemetryOptedOut() {
|
|
3273
3284
|
const disabled = process.env.ATBASH_TELEMETRY_DISABLED?.trim().toLowerCase();
|
|
3274
3285
|
if (disabled && ["1", "true", "yes", "on"].includes(disabled)) {
|
|
@@ -3297,18 +3308,36 @@ function setupTelemetry(config2) {
|
|
|
3297
3308
|
defaultSource = config2.source;
|
|
3298
3309
|
sourceExplicitlySet = true;
|
|
3299
3310
|
}
|
|
3311
|
+
if (config2.exportIntervalMs !== void 0 && requestedExportIntervalMs === void 0) {
|
|
3312
|
+
requestedExportIntervalMs = config2.exportIntervalMs;
|
|
3313
|
+
}
|
|
3300
3314
|
if (meterProvider) return;
|
|
3301
3315
|
if (!config2.endpoint || !config2.getAuthHeaders) return;
|
|
3302
|
-
if (/^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)(:|\/|$)/i.test(
|
|
3316
|
+
if (/^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)(:|\/|$)/i.test(
|
|
3317
|
+
config2.endpoint
|
|
3318
|
+
))
|
|
3319
|
+
return;
|
|
3303
3320
|
const proxyUrl = `${config2.endpoint.replace(/\/+$/, "")}/api/telemetry`;
|
|
3304
3321
|
const getAuthHeaders = config2.getAuthHeaders;
|
|
3305
3322
|
const exporter = new OTLPMetricExporter({
|
|
3306
3323
|
url: proxyUrl,
|
|
3307
|
-
headers: async () => getAuthHeaders()
|
|
3324
|
+
headers: async () => getAuthHeaders(),
|
|
3325
|
+
// DELTA, not the OTel default of CUMULATIVE.
|
|
3326
|
+
//
|
|
3327
|
+
// A cumulative counter reports a running total, and the backend recovers
|
|
3328
|
+
// the increment by differencing consecutive points of a series. That needs
|
|
3329
|
+
// at least two points. Most Atbash callers are short-lived — a CLI run
|
|
3330
|
+
// exports once and exits — so their series has exactly one point, nothing
|
|
3331
|
+
// to difference against, and the usage board reads them as zero: the calls
|
|
3332
|
+
// arrive, carry the right `source`, and still count for nothing.
|
|
3333
|
+
//
|
|
3334
|
+
// A delta counter reports the increment itself, so a single export is a
|
|
3335
|
+
// whole truthful datapoint and SUM over any window is the real call count.
|
|
3336
|
+
temporalityPreference: AggregationTemporalityPreference.DELTA
|
|
3308
3337
|
});
|
|
3309
3338
|
const reader = new PeriodicExportingMetricReader({
|
|
3310
3339
|
exporter,
|
|
3311
|
-
exportIntervalMillis:
|
|
3340
|
+
exportIntervalMillis: requestedExportIntervalMs ?? 6e4
|
|
3312
3341
|
});
|
|
3313
3342
|
meterProvider = new MeterProvider({
|
|
3314
3343
|
resource: resourceFromAttributes({
|
|
@@ -3354,6 +3383,7 @@ async function shutdownTelemetry() {
|
|
|
3354
3383
|
durationHistogram = null;
|
|
3355
3384
|
defaultSource = "sdk";
|
|
3356
3385
|
sourceExplicitlySet = false;
|
|
3386
|
+
requestedExportIntervalMs = void 0;
|
|
3357
3387
|
}
|
|
3358
3388
|
|
|
3359
3389
|
// src-ts/userConfig.ts
|
|
@@ -3547,16 +3577,23 @@ var Atbash = class _Atbash {
|
|
|
3547
3577
|
verifying: this.verifyPubKey ? "with response-signature pubkey configured" : "without signature verification"
|
|
3548
3578
|
});
|
|
3549
3579
|
}
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3580
|
+
if (isAllowlistedJudgeHost(this.endpoint)) {
|
|
3581
|
+
try {
|
|
3582
|
+
setupTelemetry({
|
|
3583
|
+
enabled: true,
|
|
3584
|
+
endpoint: this.endpoint,
|
|
3585
|
+
getAuthHeaders: () => this.authHeaders()
|
|
3586
|
+
});
|
|
3587
|
+
} catch (err) {
|
|
3588
|
+
this.logger.warn?.(
|
|
3589
|
+
"[atbash] telemetry setup failed \u2014 continuing without metrics",
|
|
3590
|
+
{ error: String(err) }
|
|
3591
|
+
);
|
|
3592
|
+
}
|
|
3593
|
+
} else {
|
|
3594
|
+
this.logger.info?.(
|
|
3595
|
+
"[atbash] telemetry disabled \u2014 the agent bearer is only sent to hosts that already receive it, and this judge endpoint is not one",
|
|
3596
|
+
{ endpoint: this.endpoint }
|
|
3560
3597
|
);
|
|
3561
3598
|
}
|
|
3562
3599
|
}
|
|
@@ -42954,7 +42991,7 @@ async function commitMemoryVersion(plaintext, auth, opts) {
|
|
|
42954
42991
|
const chainOpts = await resolveChainOptsForOrg(opts, auth);
|
|
42955
42992
|
const client = await buildChainClient(chainOpts);
|
|
42956
42993
|
const { sigProvider } = buildSigner(auth);
|
|
42957
|
-
await client.signAndSendUniqueTransaction(
|
|
42994
|
+
const receipt = await client.signAndSendUniqueTransaction(
|
|
42958
42995
|
{
|
|
42959
42996
|
name: native.OP_ADD_AGENT_MEMORY,
|
|
42960
42997
|
args: [
|
|
@@ -42967,6 +43004,9 @@ async function commitMemoryVersion(plaintext, auth, opts) {
|
|
|
42967
43004
|
},
|
|
42968
43005
|
sigProvider
|
|
42969
43006
|
);
|
|
43007
|
+
if (receipt.status !== index.ResponseStatus.Confirmed) {
|
|
43008
|
+
throw new Error(`Memory transaction was not confirmed (status=${receipt.status})`);
|
|
43009
|
+
}
|
|
42970
43010
|
}
|
|
42971
43011
|
async function decryptRow(row, key3) {
|
|
42972
43012
|
const parsed = native.parseMemoryRow(gtvToFfiSafe(row));
|
|
@@ -43076,13 +43116,16 @@ async function rollbackMemory(toId, reason, auth, opts) {
|
|
|
43076
43116
|
const chainOpts = await resolveChainOptsForOrg(opts, auth);
|
|
43077
43117
|
const client = await buildChainClient(chainOpts);
|
|
43078
43118
|
const { sigProvider } = buildSigner(auth);
|
|
43079
|
-
await client.signAndSendUniqueTransaction(
|
|
43119
|
+
const receipt = await client.signAndSendUniqueTransaction(
|
|
43080
43120
|
{
|
|
43081
43121
|
name: native.OP_ROLLBACK_AGENT_MEMORY,
|
|
43082
43122
|
args: [toGtxBytes(args.agent_pubkey), args.to_id, args.reason]
|
|
43083
43123
|
},
|
|
43084
43124
|
sigProvider
|
|
43085
43125
|
);
|
|
43126
|
+
if (receipt.status !== index.ResponseStatus.Confirmed) {
|
|
43127
|
+
throw new Error(`Memory rollback was not confirmed (status=${receipt.status})`);
|
|
43128
|
+
}
|
|
43086
43129
|
}
|
|
43087
43130
|
|
|
43088
43131
|
// src-ts/memory/_json_safe.ts
|
|
@@ -43221,6 +43264,7 @@ async function guardMemoryWrite(input) {
|
|
|
43221
43264
|
endpoint,
|
|
43222
43265
|
verifyPubKey,
|
|
43223
43266
|
orgName,
|
|
43267
|
+
chainOpts,
|
|
43224
43268
|
threshold,
|
|
43225
43269
|
patterns,
|
|
43226
43270
|
toolNames,
|
|
@@ -43277,23 +43321,41 @@ async function guardMemoryWrite(input) {
|
|
|
43277
43321
|
committed: false
|
|
43278
43322
|
};
|
|
43279
43323
|
}
|
|
43324
|
+
if (scanResult.verdict !== "green" && scanResult.verdict !== "yellow") {
|
|
43325
|
+
const reason = "Memory scan returned an unrecognized verdict.";
|
|
43326
|
+
return {
|
|
43327
|
+
handled: true,
|
|
43328
|
+
decision: enforce ? { allow: false, block: true, reason } : { allow: true, reason },
|
|
43329
|
+
scanResult,
|
|
43330
|
+
committed: false
|
|
43331
|
+
};
|
|
43332
|
+
}
|
|
43280
43333
|
const filePath = path3.basename(memEntry.key);
|
|
43281
|
-
|
|
43282
|
-
|
|
43283
|
-
|
|
43284
|
-
|
|
43285
|
-
|
|
43286
|
-
|
|
43287
|
-
|
|
43334
|
+
try {
|
|
43335
|
+
await commitMemoryVersion(memEntry.value, auth, {
|
|
43336
|
+
score: scanResult.score,
|
|
43337
|
+
filePath,
|
|
43338
|
+
orgName,
|
|
43339
|
+
endpoint,
|
|
43340
|
+
verifyPubKey,
|
|
43341
|
+
chainOpts
|
|
43342
|
+
});
|
|
43343
|
+
} catch (err) {
|
|
43288
43344
|
const reason = err instanceof Error ? err.message : String(err);
|
|
43289
43345
|
logger2?.warn?.("[atbash] memory commit to chain failed", {
|
|
43290
43346
|
path: memEntry.key,
|
|
43291
43347
|
filePath,
|
|
43292
43348
|
reason
|
|
43293
43349
|
});
|
|
43294
|
-
|
|
43350
|
+
return {
|
|
43351
|
+
handled: true,
|
|
43352
|
+
decision: enforce ? { allow: false, block: true, reason: `Memory commit failed: ${reason}` } : { allow: true, reason: `Memory commit failed: ${reason}` },
|
|
43353
|
+
scanResult,
|
|
43354
|
+
committed: false
|
|
43355
|
+
};
|
|
43356
|
+
}
|
|
43295
43357
|
logger2?.info?.(
|
|
43296
|
-
scanResult.verdict === "yellow" ? "[atbash] memory
|
|
43358
|
+
scanResult.verdict === "yellow" ? "[atbash] memory ALLOW (yellow, low score)" : "[atbash] memory ALLOW",
|
|
43297
43359
|
{
|
|
43298
43360
|
path: memEntry.key,
|
|
43299
43361
|
filePath,
|
|
@@ -43326,7 +43388,7 @@ async function syncLocalMemory(auth, pointer, opts = {}) {
|
|
|
43326
43388
|
if (withinTtl) {
|
|
43327
43389
|
return { drifted: false, checked: false, pointer };
|
|
43328
43390
|
}
|
|
43329
|
-
const currentId = await getActiveMemoryId(auth, opts.chainOpts);
|
|
43391
|
+
const currentId = await getActiveMemoryId(auth, opts.chainOpts, opts.filePath);
|
|
43330
43392
|
const nextPointer = { activeId: currentId, checkedAt: now };
|
|
43331
43393
|
if (currentId === pointer.activeId) {
|
|
43332
43394
|
return { drifted: false, checked: true, pointer: nextPointer };
|
|
@@ -43408,13 +43470,16 @@ function formatMeta(meta) {
|
|
|
43408
43470
|
}
|
|
43409
43471
|
function createFileLogger(filePath, upstream) {
|
|
43410
43472
|
let queue = Promise.resolve();
|
|
43473
|
+
let writeError;
|
|
43411
43474
|
async function ensureDir() {
|
|
43412
43475
|
await fs2.mkdir(path5.dirname(filePath), { recursive: true });
|
|
43413
43476
|
}
|
|
43414
43477
|
function append(level, message, meta) {
|
|
43415
43478
|
const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${message}${formatMeta(meta)}
|
|
43416
43479
|
`;
|
|
43417
|
-
queue = queue.then(ensureDir).then(() => fs2.appendFile(filePath, line, "utf8")).catch(() => {
|
|
43480
|
+
queue = queue.then(ensureDir).then(() => fs2.appendFile(filePath, line, "utf8")).catch((error4) => {
|
|
43481
|
+
writeError = error4 instanceof Error ? error4 : new Error(String(error4));
|
|
43482
|
+
upstream?.warn("[atbash] plugin log write failed", { error: writeError.message });
|
|
43418
43483
|
});
|
|
43419
43484
|
}
|
|
43420
43485
|
return {
|
|
@@ -43425,6 +43490,10 @@ function createFileLogger(filePath, upstream) {
|
|
|
43425
43490
|
warn(message, meta) {
|
|
43426
43491
|
upstream?.warn(message, meta ?? {});
|
|
43427
43492
|
append("warn", message, meta);
|
|
43493
|
+
},
|
|
43494
|
+
async flush() {
|
|
43495
|
+
await queue;
|
|
43496
|
+
if (writeError) throw writeError;
|
|
43428
43497
|
}
|
|
43429
43498
|
};
|
|
43430
43499
|
}
|
|
@@ -43514,7 +43583,15 @@ var MemoryGuardManager = class {
|
|
|
43514
43583
|
orgName: this.opts.orgName
|
|
43515
43584
|
});
|
|
43516
43585
|
const resolved = await atbash.resolveChainForOrg(this.opts.orgName);
|
|
43517
|
-
|
|
43586
|
+
if (!await atbash.checkAgentExists(this.agentPubkeyHex, { network: resolved.network })) {
|
|
43587
|
+
throw new Error(`agent is not registered on the resolved ${resolved.network} chain`);
|
|
43588
|
+
}
|
|
43589
|
+
const chainOpts = {
|
|
43590
|
+
...this.opts.chainOpts,
|
|
43591
|
+
network: resolved.network,
|
|
43592
|
+
blockchainRid: resolved.blockchainRid,
|
|
43593
|
+
nodeUrls: this.opts.chainOpts?.nodeUrls ?? resolved.nodeUrls
|
|
43594
|
+
};
|
|
43518
43595
|
this._resolvedChainOpts = chainOpts;
|
|
43519
43596
|
this.logger.info(
|
|
43520
43597
|
`[atbash] resolved org \u2192 chain \u2014 org=${this.opts.orgName} network=${resolved.network}`
|
|
@@ -43523,9 +43600,9 @@ var MemoryGuardManager = class {
|
|
|
43523
43600
|
} catch (err) {
|
|
43524
43601
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43525
43602
|
this.logger.warn(
|
|
43526
|
-
`[atbash] org \u2192 chain resolution failed (
|
|
43603
|
+
`[atbash] org \u2192 chain resolution failed (memory read will be blocked): ${msg}`
|
|
43527
43604
|
);
|
|
43528
|
-
|
|
43605
|
+
throw err;
|
|
43529
43606
|
} finally {
|
|
43530
43607
|
this._resolveChainInflight = void 0;
|
|
43531
43608
|
}
|
|
@@ -43544,13 +43621,18 @@ var MemoryGuardManager = class {
|
|
|
43544
43621
|
const result = await syncLocalMemory(this.opts.auth, seed, {
|
|
43545
43622
|
ttlMs: 0,
|
|
43546
43623
|
force: true,
|
|
43547
|
-
chainOpts
|
|
43624
|
+
chainOpts,
|
|
43625
|
+
filePath: path6.basename(this.memoryFilePath)
|
|
43548
43626
|
});
|
|
43549
43627
|
if (!result.drifted && result.pointer.activeId == null) {
|
|
43550
43628
|
this.logger.info(
|
|
43551
43629
|
`[atbash] no active memory on chain for agent=${this.agentPubkeyHex.slice(0, 16)}\u2026 org=${this.opts.orgName ?? "(none)"} \u2014 either the agent isn't registered on this chain or hasn't written any memory. Sync will remain a no-op until a write lands.`
|
|
43552
43630
|
);
|
|
43553
43631
|
} else if (result.drifted && result.current) {
|
|
43632
|
+
if (result.current.filePath !== path6.basename(this.memoryFilePath) || result.current.isActive !== true || result.current.decryptError) {
|
|
43633
|
+
this.logger.warn("[atbash] boot sync REFUSED mismatched memory version");
|
|
43634
|
+
return;
|
|
43635
|
+
}
|
|
43554
43636
|
if (result.current.score < this.rollbackMinScore) {
|
|
43555
43637
|
this.logger.warn(
|
|
43556
43638
|
`[atbash] boot sync REFUSED refresh \u2014 id=${result.current.id} score=${result.current.score} below threshold ${this.rollbackMinScore}. Leaving MEMORY.md and pointer untouched; next memory read will be blocked.`
|
|
@@ -43564,7 +43646,8 @@ var MemoryGuardManager = class {
|
|
|
43564
43646
|
await this.writeMemoryAtomic(result.current.content);
|
|
43565
43647
|
} catch (err) {
|
|
43566
43648
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43567
|
-
this.logger.warn("[atbash] boot sync write failed
|
|
43649
|
+
this.logger.warn("[atbash] boot sync write failed; pointer remains unchanged", { error: msg });
|
|
43650
|
+
return;
|
|
43568
43651
|
}
|
|
43569
43652
|
}
|
|
43570
43653
|
await this.pointerStore.set(this.agentPubkeyHex, result.pointer);
|
|
@@ -43578,9 +43661,9 @@ var MemoryGuardManager = class {
|
|
|
43578
43661
|
}
|
|
43579
43662
|
/**
|
|
43580
43663
|
* Returns a `HookDecision` when the guard reached a decision about this event.
|
|
43581
|
-
* Returns `null`
|
|
43582
|
-
*
|
|
43583
|
-
*
|
|
43664
|
+
* Returns `null` for non-memory events, other files this manager does not
|
|
43665
|
+
* own, or unverified observe-mode reads. An enforced read of the managed
|
|
43666
|
+
* file blocks when verification cannot complete.
|
|
43584
43667
|
*
|
|
43585
43668
|
* A returned decision carries `audited` (see `HookDecision`). Only
|
|
43586
43669
|
* `{ allow: true, audited: true }` means "checked and cleared"; anything else
|
|
@@ -43616,6 +43699,7 @@ var MemoryGuardManager = class {
|
|
|
43616
43699
|
endpoint: this.opts.judgeEndpoint,
|
|
43617
43700
|
verifyPubKey: this.opts.judgeVerifyPubKey,
|
|
43618
43701
|
orgName: this.opts.orgName,
|
|
43702
|
+
chainOpts: this.opts.chainOpts,
|
|
43619
43703
|
patterns: this.opts.memoryPathPatterns,
|
|
43620
43704
|
toolNames: this.opts.memoryWriteToolNames,
|
|
43621
43705
|
enforce: this.enforce,
|
|
@@ -43660,6 +43744,14 @@ var MemoryGuardManager = class {
|
|
|
43660
43744
|
reason: `memory scan returned ${sr2.verdict} but this guard is not enforcing it`
|
|
43661
43745
|
};
|
|
43662
43746
|
}
|
|
43747
|
+
if (guard.committed !== true) {
|
|
43748
|
+
return {
|
|
43749
|
+
allow: true,
|
|
43750
|
+
audited: false,
|
|
43751
|
+
verdict: sr2.verdict,
|
|
43752
|
+
reason: "memory scan passed but chain commit did not confirm"
|
|
43753
|
+
};
|
|
43754
|
+
}
|
|
43663
43755
|
return { allow: true, audited: true, verdict: sr2.verdict };
|
|
43664
43756
|
}
|
|
43665
43757
|
/**
|
|
@@ -43694,62 +43786,73 @@ var MemoryGuardManager = class {
|
|
|
43694
43786
|
return found.every((p) => path6.resolve(p) === managed);
|
|
43695
43787
|
}
|
|
43696
43788
|
async handleMemoryRead(event, ctx) {
|
|
43789
|
+
if (!this.vouchesForTarget(event, ctx)) return null;
|
|
43697
43790
|
const pointer = await this.pointerStore.get(this.agentPubkeyHex);
|
|
43698
43791
|
let result;
|
|
43792
|
+
let chainOpts;
|
|
43699
43793
|
try {
|
|
43700
|
-
|
|
43794
|
+
chainOpts = await this.resolveChainOpts();
|
|
43701
43795
|
result = await syncLocalMemory(this.opts.auth, pointer, {
|
|
43702
43796
|
ttlMs: this.ttlMs,
|
|
43703
|
-
|
|
43797
|
+
force: this.enforce,
|
|
43798
|
+
chainOpts,
|
|
43799
|
+
filePath: path6.basename(this.memoryFilePath)
|
|
43704
43800
|
});
|
|
43705
43801
|
} catch (err) {
|
|
43706
43802
|
if (err instanceof MemoryIntegrityError) {
|
|
43707
43803
|
const reason = `Memory integrity check failed on id ${err.id} \u2014 read blocked.`;
|
|
43708
43804
|
this.logger.warn("[atbash] MEMORY INTEGRITY FAILURE", { id: err.id, error: err.message });
|
|
43709
|
-
|
|
43710
|
-
return { block: true, blockReason: reason, allow: false, reason, audited: true };
|
|
43805
|
+
return this.unverifiedRead(reason);
|
|
43711
43806
|
}
|
|
43712
43807
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43713
|
-
this.logger.warn("[atbash] memory sync failed
|
|
43714
|
-
return
|
|
43808
|
+
this.logger.warn("[atbash] memory sync failed", { error: msg });
|
|
43809
|
+
return this.unverifiedRead(`Memory chain sync failed: ${msg}`);
|
|
43715
43810
|
}
|
|
43716
|
-
|
|
43717
|
-
|
|
43718
|
-
|
|
43719
|
-
|
|
43720
|
-
if (fresh.score < this.rollbackMinScore) {
|
|
43721
|
-
const reason = `Rolled-back memory version #${fresh.id} scored ${fresh.score} (below threshold ${this.rollbackMinScore}) \u2014 read blocked.`;
|
|
43722
|
-
this.logger.warn("[atbash] blocking read on low-score rollback", { id: fresh.id, score: fresh.score });
|
|
43723
|
-
if (!this.enforce) return null;
|
|
43724
|
-
return { block: true, blockReason: reason, allow: false, reason, audited: true };
|
|
43725
|
-
}
|
|
43726
|
-
this.logger.info("[atbash] memory drift detected \u2014 refreshing local file", {
|
|
43727
|
-
id: fresh.id,
|
|
43728
|
-
score: fresh.score
|
|
43729
|
-
});
|
|
43730
|
-
try {
|
|
43731
|
-
await this.writeMemoryAtomic(fresh.content);
|
|
43732
|
-
} catch (err) {
|
|
43733
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
43734
|
-
this.logger.warn("[atbash] failed to write refreshed memory (serving old)", { error: msg });
|
|
43735
|
-
onDiskIsCurrent = false;
|
|
43736
|
-
}
|
|
43737
|
-
} else {
|
|
43738
|
-
this.logger.info("[atbash] active memory removed on chain", { pubkey: this.agentPubkeyHex });
|
|
43739
|
-
onDiskIsCurrent = false;
|
|
43740
|
-
}
|
|
43811
|
+
if (!result.checked) return this.unverifiedRead("Memory chain state was not checked.");
|
|
43812
|
+
const activeId = result.pointer.activeId;
|
|
43813
|
+
if (activeId === null) {
|
|
43814
|
+
return this.unverifiedRead("No active chain version exists for the managed memory file.");
|
|
43741
43815
|
}
|
|
43742
|
-
|
|
43743
|
-
|
|
43744
|
-
|
|
43745
|
-
|
|
43746
|
-
|
|
43747
|
-
);
|
|
43816
|
+
let active;
|
|
43817
|
+
try {
|
|
43818
|
+
active = result.drifted ? result.current : await getMemoryById(activeId, this.opts.auth, chainOpts);
|
|
43819
|
+
} catch (err) {
|
|
43820
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
43821
|
+
this.logger.warn("[atbash] active memory row could not be verified", { error: msg });
|
|
43822
|
+
return this.unverifiedRead(`Memory version could not be verified: ${msg}`);
|
|
43748
43823
|
}
|
|
43749
|
-
|
|
43750
|
-
if (!
|
|
43824
|
+
const filePath = path6.basename(this.memoryFilePath);
|
|
43825
|
+
if (!active || active.id !== activeId || active.filePath !== filePath || active.isActive !== true || active.decryptError) {
|
|
43826
|
+
return this.unverifiedRead("Active memory version does not match the managed file.");
|
|
43827
|
+
}
|
|
43828
|
+
if (active.score < this.rollbackMinScore) {
|
|
43829
|
+
return this.unverifiedRead(`Memory version #${active.id} is below the minimum safety score.`);
|
|
43830
|
+
}
|
|
43831
|
+
try {
|
|
43832
|
+
if (result.drifted) {
|
|
43833
|
+
this.logger.info("[atbash] memory drift detected \u2014 refreshing local file", { id: active.id });
|
|
43834
|
+
await this.writeMemoryAtomic(active.content);
|
|
43835
|
+
}
|
|
43836
|
+
const onDisk = await fs3.readFile(this.memoryFilePath, "utf8");
|
|
43837
|
+
if (onDisk !== active.content) {
|
|
43838
|
+
return this.unverifiedRead("Local memory bytes do not match the active chain version.");
|
|
43839
|
+
}
|
|
43840
|
+
} catch (err) {
|
|
43841
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
43842
|
+
this.logger.warn("[atbash] managed memory file could not be verified", { error: msg });
|
|
43843
|
+
return this.unverifiedRead(`Managed memory file could not be verified: ${msg}`);
|
|
43844
|
+
}
|
|
43845
|
+
await this.pointerStore.set(this.agentPubkeyHex, result.pointer);
|
|
43751
43846
|
return { allow: true, audited: true };
|
|
43752
43847
|
}
|
|
43848
|
+
/** Complete queued audit-log writes before a host shuts down or removes its workspace. */
|
|
43849
|
+
async flushLogs() {
|
|
43850
|
+
await this.logger.flush();
|
|
43851
|
+
}
|
|
43852
|
+
unverifiedRead(reason) {
|
|
43853
|
+
if (!this.enforce) return null;
|
|
43854
|
+
return { block: true, blockReason: reason, allow: false, reason, audited: true };
|
|
43855
|
+
}
|
|
43753
43856
|
async writeMemoryAtomic(content) {
|
|
43754
43857
|
await fs3.mkdir(path6.dirname(this.memoryFilePath), { recursive: true });
|
|
43755
43858
|
const tmp = `${this.memoryFilePath}.${process.pid}.tmp`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atbash/sdk",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.6-dev.0",
|
|
4
4
|
"description": "TypeScript SDK for Atbash — the safety layer that evaluates AI agent actions against operator-defined policies before execution.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atbash",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"test:native": "node --test __test__/vector_parity.test.mjs",
|
|
60
60
|
"test:sdk": "node --test __test__/*.sdk.test.mjs",
|
|
61
61
|
"test:live": "node --test __test__/live_technical_health.test.mjs",
|
|
62
|
+
"test:memory:live": "node --test __test__/memory_chain_live.test.mjs",
|
|
62
63
|
"test": "node --test __test__/*.test.mjs",
|
|
63
64
|
"lint": "eslint .",
|
|
64
65
|
"lint:fix": "eslint . --fix",
|
|
@@ -89,10 +90,10 @@
|
|
|
89
90
|
"typescript-eslint": "^8.62.1"
|
|
90
91
|
},
|
|
91
92
|
"optionalDependencies": {
|
|
92
|
-
"@atbash/sdk-linux-x64-gnu": "0.16.
|
|
93
|
-
"@atbash/sdk-linux-arm64-gnu": "0.16.
|
|
94
|
-
"@atbash/sdk-linux-x64-musl": "0.16.
|
|
95
|
-
"@atbash/sdk-darwin-arm64": "0.16.
|
|
96
|
-
"@atbash/sdk-win32-x64-msvc": "0.16.
|
|
93
|
+
"@atbash/sdk-linux-x64-gnu": "0.16.6-dev.0",
|
|
94
|
+
"@atbash/sdk-linux-arm64-gnu": "0.16.6-dev.0",
|
|
95
|
+
"@atbash/sdk-linux-x64-musl": "0.16.6-dev.0",
|
|
96
|
+
"@atbash/sdk-darwin-arm64": "0.16.6-dev.0",
|
|
97
|
+
"@atbash/sdk-win32-x64-msvc": "0.16.6-dev.0"
|
|
97
98
|
}
|
|
98
99
|
}
|