@atbash/sdk 0.9.1 → 0.9.2

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/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 when a chain-commit was dispatched (fire-and-forget). */
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
- * Chain commit is fire-and-forget: network errors are logged via
1150
- * `logger.warn` but never surfaced to the caller. This mirrors the
1151
- * existing plugin behaviour — chain is for audit/recovery, not gating.
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
- * Absent or false means the guard reached no decision — it was inside its cache
1289
- * window, chain was unreachable, the scan never ran, the file it can vouch for
1290
- * is not the file being read, or it is in observe mode. Those calls are
1291
- * unaudited: fall through to your own judge exactly as for a `null` return.
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` when it did not — either the event isn't memory-related, or it
1392
- * is but the guard could not check it. In both cases the host falls through to
1393
- * its own audit.
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 when a chain-commit was dispatched (fire-and-forget). */
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
- * Chain commit is fire-and-forget: network errors are logged via
1150
- * `logger.warn` but never surfaced to the caller. This mirrors the
1151
- * existing plugin behaviour — chain is for audit/recovery, not gating.
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
- * Absent or false means the guard reached no decision — it was inside its cache
1289
- * window, chain was unreachable, the scan never ran, the file it can vouch for
1290
- * is not the file being read, or it is in observe mode. Those calls are
1291
- * unaudited: fall through to your own judge exactly as for a `null` return.
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` when it did not — either the event isn't memory-related, or it
1392
- * is but the guard could not check it. In both cases the host falls through to
1393
- * its own audit.
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(config2.endpoint)) return;
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: config2.exportIntervalMs ?? 6e4
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
- try {
3638
- setupTelemetry({
3639
- enabled: true,
3640
- endpoint: this.endpoint,
3641
- getAuthHeaders: () => this.authHeaders()
3642
- });
3643
- } catch (err) {
3644
- this.logger.warn?.(
3645
- "[atbash] telemetry setup failed \u2014 continuing without metrics",
3646
- { error: String(err) }
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
- commitMemoryVersion(memEntry.value, auth, {
43369
- score: scanResult.score,
43370
- filePath,
43371
- orgName,
43372
- endpoint,
43373
- verifyPubKey
43374
- }).catch((err) => {
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 HOLD" : "[atbash] memory ALLOW",
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
- const chainOpts = { ...this.opts.chainOpts, network: resolved.network };
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 (falling back to SDK default): ${msg}`
43687
+ `[atbash] org \u2192 chain resolution failed (memory read will be blocked): ${msg}`
43614
43688
  );
43615
- return this.opts.chainOpts;
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 (serving whatever's on disk)", { error: msg });
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` when it did not — either the event isn't memory-related, or it
43669
- * is but the guard could not check it. In both cases the host falls through to
43670
- * its own audit.
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
- const chainOpts = await this.resolveChainOpts();
43878
+ chainOpts = await this.resolveChainOpts();
43788
43879
  result = await syncLocalMemory(this.opts.auth, pointer, {
43789
43880
  ttlMs: this.ttlMs,
43790
- chainOpts
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
- if (!this.enforce) return null;
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 (serving local copy)", { error: msg });
43801
- return null;
43892
+ this.logger.warn("[atbash] memory sync failed", { error: msg });
43893
+ return this.unverifiedRead(`Memory chain sync failed: ${msg}`);
43802
43894
  }
43803
- let onDiskIsCurrent = result.checked;
43804
- if (result.drifted) {
43805
- const fresh = result.current;
43806
- if (fresh) {
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
- if (onDiskIsCurrent) {
43830
- await this.pointerStore.set(this.agentPubkeyHex, result.pointer);
43831
- } else {
43832
- this.logger.warn(
43833
- "[atbash] not advancing memory pointer \u2014 local file is stale or revoked; reads stay unaudited until it is refreshed"
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
- if (!onDiskIsCurrent) return null;
43837
- if (!this.vouchesForTarget(event, ctx)) return null;
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`;