@atbash/sdk 0.16.5-dev.0 → 0.17.0-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 +76 -13
- package/dist/browser.mjs +277 -106
- package/dist/index.d.mts +76 -13
- package/dist/index.d.ts +76 -13
- package/dist/index.js +408 -166
- package/dist/index.mjs +408 -166
- package/package.json +7 -6
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;
|
|
@@ -3294,7 +3301,7 @@ function loadAgentFromFile(keyPath) {
|
|
|
3294
3301
|
// src-ts/normalize.ts
|
|
3295
3302
|
function normalizeVerdict(raw2) {
|
|
3296
3303
|
if (raw2 === null || raw2 === void 0) return "No verdict";
|
|
3297
|
-
const v = String(raw2).toUpperCase();
|
|
3304
|
+
const v = String(raw2).trim().toUpperCase();
|
|
3298
3305
|
if (v === "ALLOW" || v === "GREEN") return "ALLOW";
|
|
3299
3306
|
if (v === "HOLD" || v === "YELLOW") return "HOLD";
|
|
3300
3307
|
if (v === "BLOCK" || v === "RED") return "BLOCK";
|
|
@@ -3331,17 +3338,58 @@ function allowSignalsPermit(data) {
|
|
|
3331
3338
|
}
|
|
3332
3339
|
return signals.length === 0 || signals.every((value) => value === true);
|
|
3333
3340
|
}
|
|
3341
|
+
function explicitRefusal(data) {
|
|
3342
|
+
let hold = false;
|
|
3343
|
+
for (const source of decisionSources(data)) {
|
|
3344
|
+
const verdict = verdictOf(source);
|
|
3345
|
+
if (verdict === "BLOCK" || verdict === "RED") return "BLOCK";
|
|
3346
|
+
if (verdict === "HOLD" || verdict === "YELLOW") hold = true;
|
|
3347
|
+
const actionType = actionTypeOf(source);
|
|
3348
|
+
if (actionType === "block") return "BLOCK";
|
|
3349
|
+
if (actionType === "hold_for_user_confirm") hold = true;
|
|
3350
|
+
}
|
|
3351
|
+
if (!allowSignalsPermit(data)) return "BLOCK";
|
|
3352
|
+
return hold ? "HOLD" : "";
|
|
3353
|
+
}
|
|
3354
|
+
function decisionSources(data) {
|
|
3355
|
+
return [data, isRecord(data.decision) ? data.decision : {}];
|
|
3356
|
+
}
|
|
3357
|
+
function verdictOf(source) {
|
|
3358
|
+
return typeof source.verdict === "string" ? source.verdict.trim().toUpperCase() : "";
|
|
3359
|
+
}
|
|
3360
|
+
function actionTypeRank(actionType) {
|
|
3361
|
+
if (actionType === "block") return 4;
|
|
3362
|
+
if (actionType === "hold_for_user_confirm") return 3;
|
|
3363
|
+
if (actionType === "") return 1;
|
|
3364
|
+
if (actionType === "allow") return 0;
|
|
3365
|
+
return 2;
|
|
3366
|
+
}
|
|
3367
|
+
function actionTypeOf(source) {
|
|
3368
|
+
let winner = "";
|
|
3369
|
+
for (const key3 of ["action_type", "actionType"]) {
|
|
3370
|
+
const raw2 = source[key3];
|
|
3371
|
+
if (raw2 === void 0 || raw2 === null) continue;
|
|
3372
|
+
const actionType = normalizeActionType(raw2);
|
|
3373
|
+
if (actionTypeRank(actionType) > actionTypeRank(winner)) winner = actionType;
|
|
3374
|
+
}
|
|
3375
|
+
return winner;
|
|
3376
|
+
}
|
|
3334
3377
|
function wireActionType(data) {
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3378
|
+
for (const source of decisionSources(data)) {
|
|
3379
|
+
const actionType = actionTypeOf(source);
|
|
3380
|
+
if (actionType !== "") return actionType;
|
|
3381
|
+
}
|
|
3382
|
+
return "";
|
|
3338
3383
|
}
|
|
3339
3384
|
function isAuditTier(data) {
|
|
3340
3385
|
const absentVerdict = data.verdict === null || data.verdict === void 0;
|
|
3341
3386
|
return absentVerdict && data.status === "logged";
|
|
3342
3387
|
}
|
|
3388
|
+
function isAuditTierBody(data) {
|
|
3389
|
+
return isAuditTier(data);
|
|
3390
|
+
}
|
|
3343
3391
|
function canonicalAllow(data) {
|
|
3344
|
-
if (
|
|
3392
|
+
if (explicitRefusal(data) !== "") return false;
|
|
3345
3393
|
const actionType = wireActionType(data);
|
|
3346
3394
|
if (actionType && actionType !== "allow") return false;
|
|
3347
3395
|
if (isAuditTier(data)) return true;
|
|
@@ -3571,6 +3619,10 @@ var Atbash = class _Atbash {
|
|
|
3571
3619
|
orgEncryptionPubKey;
|
|
3572
3620
|
/** When true (default), `auditToolCall` denies on any error. */
|
|
3573
3621
|
failClosed;
|
|
3622
|
+
/** When true (default), an unenforced (audit-tier) answer is refused. */
|
|
3623
|
+
requireEnforcement;
|
|
3624
|
+
/** Overall deadline for one auditToolCall decision, in ms. */
|
|
3625
|
+
decisionTimeoutMs;
|
|
3574
3626
|
/** Org key learned from the last agent-exists check, for this agent only. */
|
|
3575
3627
|
_orgKeyFromChain = null;
|
|
3576
3628
|
debug;
|
|
@@ -3644,6 +3696,8 @@ var Atbash = class _Atbash {
|
|
|
3644
3696
|
this.verifyPubKey = validated.verifyPubKey ?? void 0;
|
|
3645
3697
|
this.orgEncryptionPubKey = options.orgEncryptionPubKey;
|
|
3646
3698
|
this.failClosed = options.failClosed !== false;
|
|
3699
|
+
this.requireEnforcement = options.requireEnforcement !== false;
|
|
3700
|
+
this.decisionTimeoutMs = options.decisionTimeoutMs ?? DEFAULT_DECISION_TIMEOUT_MS;
|
|
3647
3701
|
this.debug = options.debug === true;
|
|
3648
3702
|
this.logger = options.logger ?? {};
|
|
3649
3703
|
this.http = new HttpClient(this.endpoint, options.timeoutMs ?? 12e4);
|
|
@@ -3654,16 +3708,23 @@ var Atbash = class _Atbash {
|
|
|
3654
3708
|
verifying: this.verifyPubKey ? "with response-signature pubkey configured" : "without signature verification"
|
|
3655
3709
|
});
|
|
3656
3710
|
}
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3711
|
+
if (isAllowlistedJudgeHost(this.endpoint)) {
|
|
3712
|
+
try {
|
|
3713
|
+
setupTelemetry({
|
|
3714
|
+
enabled: true,
|
|
3715
|
+
endpoint: this.endpoint,
|
|
3716
|
+
getAuthHeaders: () => this.authHeaders()
|
|
3717
|
+
});
|
|
3718
|
+
} catch (err) {
|
|
3719
|
+
this.logger.warn?.(
|
|
3720
|
+
"[atbash] telemetry setup failed \u2014 continuing without metrics",
|
|
3721
|
+
{ error: String(err) }
|
|
3722
|
+
);
|
|
3723
|
+
}
|
|
3724
|
+
} else {
|
|
3725
|
+
this.logger.info?.(
|
|
3726
|
+
"[atbash] telemetry disabled \u2014 the agent bearer is only sent to hosts that already receive it, and this judge endpoint is not one",
|
|
3727
|
+
{ endpoint: this.endpoint }
|
|
3667
3728
|
);
|
|
3668
3729
|
}
|
|
3669
3730
|
}
|
|
@@ -3727,6 +3788,8 @@ var Atbash = class _Atbash {
|
|
|
3727
3788
|
orgName: options.orgName,
|
|
3728
3789
|
verifyPubKey: validated.verifyPubKey ?? void 0,
|
|
3729
3790
|
failClosed: options.failClosed,
|
|
3791
|
+
requireEnforcement: options.requireEnforcement,
|
|
3792
|
+
decisionTimeoutMs: options.decisionTimeoutMs,
|
|
3730
3793
|
// ATBASH_DEBUG lets an operator turn diagnostics on without editing a
|
|
3731
3794
|
// host's plugin config, which is usually the harder half.
|
|
3732
3795
|
debug: options.debug ?? /^(1|true|yes)$/i.test(resolve("debug")),
|
|
@@ -3925,34 +3988,16 @@ var Atbash = class _Atbash {
|
|
|
3925
3988
|
}
|
|
3926
3989
|
if (!resp.ok) throw await this.httpError(resp);
|
|
3927
3990
|
const bodyBytes = Buffer.from(await resp.arrayBuffer());
|
|
3928
|
-
const verifyPubKey = options.verifyPubKey
|
|
3991
|
+
const verifyPubKey = this.effectiveVerifyKey(options.verifyPubKey);
|
|
3929
3992
|
if (verifyPubKey !== void 0) {
|
|
3930
|
-
|
|
3931
|
-
if (!sig) {
|
|
3932
|
-
throw new SignatureVerificationError(
|
|
3933
|
-
"missing X-Atbash-Signature header"
|
|
3934
|
-
);
|
|
3935
|
-
}
|
|
3936
|
-
let ok;
|
|
3937
|
-
try {
|
|
3938
|
-
ok = native.verifySignature(bodyBytes, sig, verifyPubKey);
|
|
3939
|
-
} catch (err) {
|
|
3940
|
-
throw new SignatureVerificationError(
|
|
3941
|
-
`signature verification threw: ${errorMessage(err)}`
|
|
3942
|
-
);
|
|
3943
|
-
}
|
|
3944
|
-
if (!ok) {
|
|
3945
|
-
throw new SignatureVerificationError(
|
|
3946
|
-
"signature does not verify against configured verifyPubKey"
|
|
3947
|
-
);
|
|
3948
|
-
}
|
|
3993
|
+
verifyResponseSignature(bodyBytes, resp, verifyPubKey);
|
|
3949
3994
|
}
|
|
3950
3995
|
const data = parseJson(bodyBytes);
|
|
3951
3996
|
const rawScore = data.score;
|
|
3952
3997
|
const score = typeof rawScore === "number" && Number.isInteger(rawScore) && rawScore >= 1 && rawScore <= 10 ? rawScore : void 0;
|
|
3953
3998
|
return {
|
|
3954
3999
|
verdict: normalizeVerdict(data.verdict),
|
|
3955
|
-
allow:
|
|
4000
|
+
allow: this.executable(data),
|
|
3956
4001
|
actionType: normalizeActionType(data.action_type),
|
|
3957
4002
|
reason: String(data.reason ?? ""),
|
|
3958
4003
|
confidence: Number(data.confidence ?? 0),
|
|
@@ -3963,7 +4008,8 @@ var Atbash = class _Atbash {
|
|
|
3963
4008
|
enforced: Boolean(data.enforced),
|
|
3964
4009
|
enforcementMode: String(data.enforcement_mode ?? ""),
|
|
3965
4010
|
status: String(data.status ?? ""),
|
|
3966
|
-
score
|
|
4011
|
+
score,
|
|
4012
|
+
refusal: explicitRefusal(data)
|
|
3967
4013
|
};
|
|
3968
4014
|
}
|
|
3969
4015
|
/* ── audit_tool_call (redact → judge → decision) ───────────────────────── */
|
|
@@ -3996,77 +4042,125 @@ var Atbash = class _Atbash {
|
|
|
3996
4042
|
}
|
|
3997
4043
|
try {
|
|
3998
4044
|
this.logger.info?.("[atbash] judge API called", { tool: toolName });
|
|
3999
|
-
const result = await
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
}
|
|
4014
|
-
return {
|
|
4015
|
-
allow: true,
|
|
4016
|
-
verdict: "ALLOW",
|
|
4017
|
-
reason: result.reason || "audit tier \u2014 request logged on-chain, no AI enforcement",
|
|
4018
|
-
toolCallId: result.toolCallId
|
|
4019
|
-
};
|
|
4020
|
-
}
|
|
4021
|
-
const action = result.actionType;
|
|
4022
|
-
if (action === "block") {
|
|
4045
|
+
const result = await withDeadline(
|
|
4046
|
+
this.judgeAction(actionText, contextText, {
|
|
4047
|
+
toolName,
|
|
4048
|
+
toolArgsJson: argsJson,
|
|
4049
|
+
orgName: this.orgName,
|
|
4050
|
+
// Passed through unredacted — caller vouches for the contents.
|
|
4051
|
+
resolved: input.resolved
|
|
4052
|
+
}),
|
|
4053
|
+
this.decisionTimeoutMs
|
|
4054
|
+
);
|
|
4055
|
+
return this.decide(result);
|
|
4056
|
+
} catch (err) {
|
|
4057
|
+
if (err instanceof SignatureVerificationError) {
|
|
4058
|
+
this.failJudge(errorMessage(err), err);
|
|
4023
4059
|
return {
|
|
4024
4060
|
allow: false,
|
|
4025
|
-
verdict: "
|
|
4026
|
-
reason:
|
|
4027
|
-
toolCallId: result.toolCallId
|
|
4061
|
+
verdict: "ERROR",
|
|
4062
|
+
reason: `The judge's reply failed its signature check (${errorMessage(err)}), so it was not trusted.`
|
|
4028
4063
|
};
|
|
4029
4064
|
}
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4065
|
+
return this.failJudge(errorMessage(err), err);
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
/**
|
|
4069
|
+
* The verify key for one call (R4, governor tier-2 review). A per-call key
|
|
4070
|
+
* may only narrow what the client accepts: on a client pinned to a judge
|
|
4071
|
+
* key it must be that same key, never a replacement; on an unpinned client
|
|
4072
|
+
* it adds the check.
|
|
4073
|
+
*/
|
|
4074
|
+
effectiveVerifyKey(perCall) {
|
|
4075
|
+
if (perCall === void 0) return this.verifyPubKey;
|
|
4076
|
+
const key3 = perCall.trim().toLowerCase();
|
|
4077
|
+
if (this.verifyPubKey !== void 0 && key3 !== this.verifyPubKey) {
|
|
4078
|
+
throw new SignatureVerificationError(
|
|
4079
|
+
"a per-call verifyPubKey cannot replace the judge key this client is pinned to"
|
|
4080
|
+
);
|
|
4081
|
+
}
|
|
4082
|
+
return key3;
|
|
4083
|
+
}
|
|
4084
|
+
/**
|
|
4085
|
+
* Collapse a judge result into a {@link Decision}. The most restrictive
|
|
4086
|
+
* signal wins (security review 2026-09-30, H1): `action_type`, `verdict`
|
|
4087
|
+
* and the canonical `allow` are each able to deny, and only a result where
|
|
4088
|
+
* all three agree may execute. A present `allow` / `decision.allow` that is
|
|
4089
|
+
* not exactly `true` is the judge declining to grant permission, so it
|
|
4090
|
+
* denies even under `failClosed: false` — that option relaxes *errors*, it
|
|
4091
|
+
* never overrides a refusal.
|
|
4092
|
+
*/
|
|
4093
|
+
decide(result) {
|
|
4094
|
+
const toolCallId = result.toolCallId;
|
|
4095
|
+
const deny = (verdict, reason) => ({
|
|
4096
|
+
allow: false,
|
|
4097
|
+
verdict,
|
|
4098
|
+
reason,
|
|
4099
|
+
toolCallId
|
|
4100
|
+
});
|
|
4101
|
+
const notGranted = "the judge response did not grant permission";
|
|
4102
|
+
const held = deny("HOLD", result.reason || "held for human confirmation");
|
|
4103
|
+
const blocked = deny("BLOCK", result.reason || notGranted);
|
|
4104
|
+
if (result.refusal === "BLOCK") return blocked;
|
|
4105
|
+
if (result.verdict === "No verdict") {
|
|
4106
|
+
if (result.actionType === "block") {
|
|
4107
|
+
return blocked;
|
|
4108
|
+
}
|
|
4109
|
+
if (result.actionType === "hold_for_user_confirm") {
|
|
4110
|
+
return held;
|
|
4111
|
+
}
|
|
4112
|
+
if (result.refusal === "HOLD") return held;
|
|
4113
|
+
if (result.status !== "logged") {
|
|
4114
|
+
return this.failJudge(
|
|
4115
|
+
`judge returned no verdict without an audit-tier marker (status: ${result.status || "absent"})`,
|
|
4116
|
+
void 0,
|
|
4117
|
+
toolCallId
|
|
4118
|
+
);
|
|
4037
4119
|
}
|
|
4038
|
-
if (
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
reason: result.reason,
|
|
4044
|
-
toolCallId: result.toolCallId
|
|
4045
|
-
};
|
|
4046
|
-
}
|
|
4047
|
-
if (result.verdict === "BLOCK") {
|
|
4048
|
-
return {
|
|
4049
|
-
allow: false,
|
|
4050
|
-
verdict: "BLOCK",
|
|
4051
|
-
reason: result.reason,
|
|
4052
|
-
toolCallId: result.toolCallId
|
|
4053
|
-
};
|
|
4054
|
-
}
|
|
4120
|
+
if (this.requireEnforcement) {
|
|
4121
|
+
this.logger.warn?.(
|
|
4122
|
+
"[atbash] judge did not enforce this action (audit tier) and requireEnforcement is on: denied",
|
|
4123
|
+
{ toolCallId }
|
|
4124
|
+
);
|
|
4055
4125
|
return {
|
|
4056
|
-
allow:
|
|
4057
|
-
verdict: "
|
|
4058
|
-
reason:
|
|
4059
|
-
toolCallId
|
|
4126
|
+
allow: false,
|
|
4127
|
+
verdict: "ERROR",
|
|
4128
|
+
reason: "Atbash logged this action but did not check it (audit-tier plan), and this client requires a checked decision. Upgrade the plan, or set requireEnforcement: false for log-only mode.",
|
|
4129
|
+
toolCallId
|
|
4060
4130
|
};
|
|
4061
4131
|
}
|
|
4062
|
-
return
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4132
|
+
if (result.allow !== true) return deny("BLOCK", notGranted);
|
|
4133
|
+
return {
|
|
4134
|
+
allow: true,
|
|
4135
|
+
verdict: "ALLOW",
|
|
4136
|
+
reason: result.reason || "audit tier \u2014 request logged on-chain, no AI enforcement",
|
|
4137
|
+
toolCallId
|
|
4138
|
+
};
|
|
4139
|
+
}
|
|
4140
|
+
const action = result.actionType;
|
|
4141
|
+
if (action === "block") {
|
|
4142
|
+
return deny("BLOCK", result.reason || "Blocked by your organisation's Atbash policy.");
|
|
4069
4143
|
}
|
|
4144
|
+
if (action === "hold_for_user_confirm") {
|
|
4145
|
+
return held;
|
|
4146
|
+
}
|
|
4147
|
+
if (result.refusal === "HOLD") return held;
|
|
4148
|
+
if (action === "allow") {
|
|
4149
|
+
if (result.verdict === "HOLD") return held;
|
|
4150
|
+
if (result.verdict === "BLOCK") return blocked;
|
|
4151
|
+
if (result.allow !== true) return deny("BLOCK", notGranted);
|
|
4152
|
+
return {
|
|
4153
|
+
allow: true,
|
|
4154
|
+
verdict: "ALLOW",
|
|
4155
|
+
reason: result.reason,
|
|
4156
|
+
toolCallId
|
|
4157
|
+
};
|
|
4158
|
+
}
|
|
4159
|
+
return this.failJudge(
|
|
4160
|
+
"unrecognized action_type from judge",
|
|
4161
|
+
void 0,
|
|
4162
|
+
toolCallId
|
|
4163
|
+
);
|
|
4070
4164
|
}
|
|
4071
4165
|
/**
|
|
4072
4166
|
* One exit for every judge failure.
|
|
@@ -4078,18 +4172,30 @@ var Atbash = class _Atbash {
|
|
|
4078
4172
|
*/
|
|
4079
4173
|
failJudge(reason, cause, toolCallId) {
|
|
4080
4174
|
const api2 = cause instanceof AtbashAPIError ? cause : null;
|
|
4175
|
+
const userReason = userFacingReason(reason, api2);
|
|
4081
4176
|
const status = api2 ? ` status=${api2.status || "no-response"}` : "";
|
|
4177
|
+
const detail = this.debug ? ` detail=${truncate(reason, 500)}` : "";
|
|
4082
4178
|
const body = this.debug && api2?.body ? ` body=${truncate(api2.body, 500)}` : "";
|
|
4083
4179
|
this.logger.warn?.(
|
|
4084
|
-
`[atbash] judge API failed \u2014${status} reason=${truncate(
|
|
4180
|
+
`[atbash] judge API failed \u2014${status} reason=${truncate(userReason, 300)}${detail}${body}`,
|
|
4085
4181
|
{
|
|
4086
|
-
reason,
|
|
4087
|
-
...api2 ? { status: api2.status
|
|
4182
|
+
reason: userReason,
|
|
4183
|
+
...api2 ? { status: api2.status } : {},
|
|
4184
|
+
...this.debug && api2 ? { body: api2.body, detail: reason } : {},
|
|
4088
4185
|
endpoint: this.endpoint,
|
|
4089
4186
|
...toolCallId ? { toolCallId } : {}
|
|
4090
4187
|
}
|
|
4091
4188
|
);
|
|
4092
|
-
return this.fail(
|
|
4189
|
+
return this.fail(userReason, toolCallId);
|
|
4190
|
+
}
|
|
4191
|
+
/**
|
|
4192
|
+
* The canonical permission for a response body under this client's
|
|
4193
|
+
* settings: {@link canonicalAllow}, minus the audit tier when
|
|
4194
|
+
* `requireEnforcement` is on. Never more permissive than `auditToolCall`.
|
|
4195
|
+
*/
|
|
4196
|
+
executable(data) {
|
|
4197
|
+
if (!canonicalAllow(data)) return false;
|
|
4198
|
+
return !(this.requireEnforcement && isAuditTierBody(data));
|
|
4093
4199
|
}
|
|
4094
4200
|
fail(reason, toolCallId) {
|
|
4095
4201
|
return { allow: !this.failClosed, verdict: "ERROR", reason, toolCallId };
|
|
@@ -4115,11 +4221,16 @@ var Atbash = class _Atbash {
|
|
|
4115
4221
|
this.authHeaders(brid)
|
|
4116
4222
|
);
|
|
4117
4223
|
await this.raiseIfError(resp);
|
|
4118
|
-
const
|
|
4224
|
+
const bytes = Buffer.from(await resp.arrayBuffer());
|
|
4225
|
+
if (this.verifyPubKey !== void 0) {
|
|
4226
|
+
verifyResponseSignature(bytes, resp, this.verifyPubKey);
|
|
4227
|
+
}
|
|
4228
|
+
const parsed = tryParseJson(bytes.toString("utf-8"));
|
|
4229
|
+
const data = isRecord2(parsed) ? parsed : {};
|
|
4119
4230
|
return {
|
|
4120
4231
|
status: normalizeStatus(data.status),
|
|
4121
4232
|
verdict: normalizeVerdict(data.verdict),
|
|
4122
|
-
allow:
|
|
4233
|
+
allow: this.executable(data),
|
|
4123
4234
|
reason: String(data.reason ?? ""),
|
|
4124
4235
|
judgmentId: String(data.judgmentId ?? judgmentId),
|
|
4125
4236
|
onChain: optBool(data.onChain),
|
|
@@ -4594,6 +4705,66 @@ var Atbash = class _Atbash {
|
|
|
4594
4705
|
return native.containsEvasionCharacters(text);
|
|
4595
4706
|
}
|
|
4596
4707
|
};
|
|
4708
|
+
function userFacingReason(reason, api2) {
|
|
4709
|
+
if (api2 && api2.status > 0) {
|
|
4710
|
+
const where = ` (HTTP ${api2.status}${api2.path ? ` from ${api2.path}` : ""})`;
|
|
4711
|
+
const b = api2.body.toLowerCase();
|
|
4712
|
+
if (b.includes("agent not registered")) {
|
|
4713
|
+
return `Agent not registered with Atbash: onboard it in the Atbash dashboard${where}.`;
|
|
4714
|
+
}
|
|
4715
|
+
if (b.includes("jailed")) {
|
|
4716
|
+
return `This agent is jailed; unjail it in the Atbash dashboard${where}.`;
|
|
4717
|
+
}
|
|
4718
|
+
if (b.includes("agent has no policy") || b.includes("no policy configured")) {
|
|
4719
|
+
return `This agent has no policy; attach one in the Atbash dashboard${where}.`;
|
|
4720
|
+
}
|
|
4721
|
+
if (b.includes("audit tier") || b.includes("verdict disabled") || b.includes("verdict not supported")) {
|
|
4722
|
+
return `The organisation's plan does not include checked decisions${where}.`;
|
|
4723
|
+
}
|
|
4724
|
+
return `The Atbash judge answered with an error${where}, so the action was not approved.`;
|
|
4725
|
+
}
|
|
4726
|
+
const text = api2 ? api2.body || reason : reason;
|
|
4727
|
+
return truncate(text.replace(/https?:\/\/[^\s)]+/g, "the judge"), 300);
|
|
4728
|
+
}
|
|
4729
|
+
var DEFAULT_DECISION_TIMEOUT_MS = 6e4;
|
|
4730
|
+
async function withDeadline(p, ms) {
|
|
4731
|
+
let timer;
|
|
4732
|
+
const deadline = new Promise((_, reject) => {
|
|
4733
|
+
timer = setTimeout(
|
|
4734
|
+
() => reject(
|
|
4735
|
+
new Error(
|
|
4736
|
+
`The judge did not give a decision within ${Math.round(ms / 1e3)} s, so the action was not approved. Check the network, or raise decisionTimeoutMs.`
|
|
4737
|
+
)
|
|
4738
|
+
),
|
|
4739
|
+
ms
|
|
4740
|
+
);
|
|
4741
|
+
});
|
|
4742
|
+
p.catch(() => void 0);
|
|
4743
|
+
try {
|
|
4744
|
+
return await Promise.race([p, deadline]);
|
|
4745
|
+
} finally {
|
|
4746
|
+
clearTimeout(timer);
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4749
|
+
function verifyResponseSignature(bodyBytes, resp, verifyPubKey) {
|
|
4750
|
+
const sig = resp.headers.get("X-Atbash-Signature");
|
|
4751
|
+
if (!sig) {
|
|
4752
|
+
throw new SignatureVerificationError("missing X-Atbash-Signature header");
|
|
4753
|
+
}
|
|
4754
|
+
let ok;
|
|
4755
|
+
try {
|
|
4756
|
+
ok = native.verifySignature(bodyBytes, sig, verifyPubKey);
|
|
4757
|
+
} catch (err) {
|
|
4758
|
+
throw new SignatureVerificationError(
|
|
4759
|
+
`signature verification threw: ${errorMessage(err)}`
|
|
4760
|
+
);
|
|
4761
|
+
}
|
|
4762
|
+
if (!ok) {
|
|
4763
|
+
throw new SignatureVerificationError(
|
|
4764
|
+
"signature does not verify against configured verifyPubKey"
|
|
4765
|
+
);
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4597
4768
|
function baseToolCall(raw2) {
|
|
4598
4769
|
return {
|
|
4599
4770
|
toolCallId: String(raw2.tool_call_id ?? ""),
|
|
@@ -4807,7 +4978,11 @@ async function scanMemory(entry, auth, opts) {
|
|
|
4807
4978
|
const atbash = new Atbash(auth.privkey, {
|
|
4808
4979
|
endpoint: opts?.endpoint,
|
|
4809
4980
|
verifyPubKey: opts?.verifyPubKey,
|
|
4810
|
-
orgName: opts?.orgName
|
|
4981
|
+
orgName: opts?.orgName,
|
|
4982
|
+
// Memory scans keep their existing audit-tier semantics (an audit-tier
|
|
4983
|
+
// scan is not a defect the scanner invents). Whether an unenforced scan
|
|
4984
|
+
// may commit memory is the memory-guard PR's decision, not H6's.
|
|
4985
|
+
requireEnforcement: false
|
|
4811
4986
|
});
|
|
4812
4987
|
const result = await atbash.judgeAction(request.prompt, request.context, {
|
|
4813
4988
|
toolName: opts?.toolName ?? "memory_write",
|
|
@@ -4843,6 +5018,7 @@ async function scanMemory(entry, auth, opts) {
|
|
|
4843
5018
|
verdict = "yellow";
|
|
4844
5019
|
}
|
|
4845
5020
|
}
|
|
5021
|
+
if (verdict === "green" && result.allow !== true) verdict = "red";
|
|
4846
5022
|
const parsed = native.parseScoreFromReason(result.reason);
|
|
4847
5023
|
const score = result.score ?? parsed.score ?? native.defaultScoreForVerdict(verdict);
|
|
4848
5024
|
return {
|
|
@@ -43061,7 +43237,7 @@ async function commitMemoryVersion(plaintext, auth, opts) {
|
|
|
43061
43237
|
const chainOpts = await resolveChainOptsForOrg(opts, auth);
|
|
43062
43238
|
const client = await buildChainClient(chainOpts);
|
|
43063
43239
|
const { sigProvider } = buildSigner(auth);
|
|
43064
|
-
await client.signAndSendUniqueTransaction(
|
|
43240
|
+
const receipt = await client.signAndSendUniqueTransaction(
|
|
43065
43241
|
{
|
|
43066
43242
|
name: native.OP_ADD_AGENT_MEMORY,
|
|
43067
43243
|
args: [
|
|
@@ -43074,6 +43250,9 @@ async function commitMemoryVersion(plaintext, auth, opts) {
|
|
|
43074
43250
|
},
|
|
43075
43251
|
sigProvider
|
|
43076
43252
|
);
|
|
43253
|
+
if (receipt.status !== index.ResponseStatus.Confirmed) {
|
|
43254
|
+
throw new Error(`Memory transaction was not confirmed (status=${receipt.status})`);
|
|
43255
|
+
}
|
|
43077
43256
|
}
|
|
43078
43257
|
async function decryptRow(row, key3) {
|
|
43079
43258
|
const parsed = native.parseMemoryRow(gtvToFfiSafe(row));
|
|
@@ -43183,13 +43362,16 @@ async function rollbackMemory(toId, reason, auth, opts) {
|
|
|
43183
43362
|
const chainOpts = await resolveChainOptsForOrg(opts, auth);
|
|
43184
43363
|
const client = await buildChainClient(chainOpts);
|
|
43185
43364
|
const { sigProvider } = buildSigner(auth);
|
|
43186
|
-
await client.signAndSendUniqueTransaction(
|
|
43365
|
+
const receipt = await client.signAndSendUniqueTransaction(
|
|
43187
43366
|
{
|
|
43188
43367
|
name: native.OP_ROLLBACK_AGENT_MEMORY,
|
|
43189
43368
|
args: [toGtxBytes(args.agent_pubkey), args.to_id, args.reason]
|
|
43190
43369
|
},
|
|
43191
43370
|
sigProvider
|
|
43192
43371
|
);
|
|
43372
|
+
if (receipt.status !== index.ResponseStatus.Confirmed) {
|
|
43373
|
+
throw new Error(`Memory rollback was not confirmed (status=${receipt.status})`);
|
|
43374
|
+
}
|
|
43193
43375
|
}
|
|
43194
43376
|
|
|
43195
43377
|
// src-ts/memory/_json_safe.ts
|
|
@@ -43328,6 +43510,7 @@ async function guardMemoryWrite(input) {
|
|
|
43328
43510
|
endpoint,
|
|
43329
43511
|
verifyPubKey,
|
|
43330
43512
|
orgName,
|
|
43513
|
+
chainOpts,
|
|
43331
43514
|
threshold,
|
|
43332
43515
|
patterns,
|
|
43333
43516
|
toolNames,
|
|
@@ -43384,23 +43567,41 @@ async function guardMemoryWrite(input) {
|
|
|
43384
43567
|
committed: false
|
|
43385
43568
|
};
|
|
43386
43569
|
}
|
|
43570
|
+
if (scanResult.verdict !== "green" && scanResult.verdict !== "yellow") {
|
|
43571
|
+
const reason = "Memory scan returned an unrecognized verdict.";
|
|
43572
|
+
return {
|
|
43573
|
+
handled: true,
|
|
43574
|
+
decision: enforce ? { allow: false, block: true, reason } : { allow: true, reason },
|
|
43575
|
+
scanResult,
|
|
43576
|
+
committed: false
|
|
43577
|
+
};
|
|
43578
|
+
}
|
|
43387
43579
|
const filePath = import_node_path4.default.basename(memEntry.key);
|
|
43388
|
-
|
|
43389
|
-
|
|
43390
|
-
|
|
43391
|
-
|
|
43392
|
-
|
|
43393
|
-
|
|
43394
|
-
|
|
43580
|
+
try {
|
|
43581
|
+
await commitMemoryVersion(memEntry.value, auth, {
|
|
43582
|
+
score: scanResult.score,
|
|
43583
|
+
filePath,
|
|
43584
|
+
orgName,
|
|
43585
|
+
endpoint,
|
|
43586
|
+
verifyPubKey,
|
|
43587
|
+
chainOpts
|
|
43588
|
+
});
|
|
43589
|
+
} catch (err) {
|
|
43395
43590
|
const reason = err instanceof Error ? err.message : String(err);
|
|
43396
43591
|
logger2?.warn?.("[atbash] memory commit to chain failed", {
|
|
43397
43592
|
path: memEntry.key,
|
|
43398
43593
|
filePath,
|
|
43399
43594
|
reason
|
|
43400
43595
|
});
|
|
43401
|
-
|
|
43596
|
+
return {
|
|
43597
|
+
handled: true,
|
|
43598
|
+
decision: enforce ? { allow: false, block: true, reason: `Memory commit failed: ${reason}` } : { allow: true, reason: `Memory commit failed: ${reason}` },
|
|
43599
|
+
scanResult,
|
|
43600
|
+
committed: false
|
|
43601
|
+
};
|
|
43602
|
+
}
|
|
43402
43603
|
logger2?.info?.(
|
|
43403
|
-
scanResult.verdict === "yellow" ? "[atbash] memory
|
|
43604
|
+
scanResult.verdict === "yellow" ? "[atbash] memory ALLOW (yellow, low score)" : "[atbash] memory ALLOW",
|
|
43404
43605
|
{
|
|
43405
43606
|
path: memEntry.key,
|
|
43406
43607
|
filePath,
|
|
@@ -43433,7 +43634,7 @@ async function syncLocalMemory(auth, pointer, opts = {}) {
|
|
|
43433
43634
|
if (withinTtl) {
|
|
43434
43635
|
return { drifted: false, checked: false, pointer };
|
|
43435
43636
|
}
|
|
43436
|
-
const currentId = await getActiveMemoryId(auth, opts.chainOpts);
|
|
43637
|
+
const currentId = await getActiveMemoryId(auth, opts.chainOpts, opts.filePath);
|
|
43437
43638
|
const nextPointer = { activeId: currentId, checkedAt: now };
|
|
43438
43639
|
if (currentId === pointer.activeId) {
|
|
43439
43640
|
return { drifted: false, checked: true, pointer: nextPointer };
|
|
@@ -43515,13 +43716,16 @@ function formatMeta(meta) {
|
|
|
43515
43716
|
}
|
|
43516
43717
|
function createFileLogger(filePath, upstream) {
|
|
43517
43718
|
let queue = Promise.resolve();
|
|
43719
|
+
let writeError;
|
|
43518
43720
|
async function ensureDir() {
|
|
43519
43721
|
await import_node_fs5.promises.mkdir(import_node_path6.default.dirname(filePath), { recursive: true });
|
|
43520
43722
|
}
|
|
43521
43723
|
function append(level, message, meta) {
|
|
43522
43724
|
const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${message}${formatMeta(meta)}
|
|
43523
43725
|
`;
|
|
43524
|
-
queue = queue.then(ensureDir).then(() => import_node_fs5.promises.appendFile(filePath, line, "utf8")).catch(() => {
|
|
43726
|
+
queue = queue.then(ensureDir).then(() => import_node_fs5.promises.appendFile(filePath, line, "utf8")).catch((error4) => {
|
|
43727
|
+
writeError = error4 instanceof Error ? error4 : new Error(String(error4));
|
|
43728
|
+
upstream?.warn("[atbash] plugin log write failed", { error: writeError.message });
|
|
43525
43729
|
});
|
|
43526
43730
|
}
|
|
43527
43731
|
return {
|
|
@@ -43532,6 +43736,10 @@ function createFileLogger(filePath, upstream) {
|
|
|
43532
43736
|
warn(message, meta) {
|
|
43533
43737
|
upstream?.warn(message, meta ?? {});
|
|
43534
43738
|
append("warn", message, meta);
|
|
43739
|
+
},
|
|
43740
|
+
async flush() {
|
|
43741
|
+
await queue;
|
|
43742
|
+
if (writeError) throw writeError;
|
|
43535
43743
|
}
|
|
43536
43744
|
};
|
|
43537
43745
|
}
|
|
@@ -43621,7 +43829,15 @@ var MemoryGuardManager = class {
|
|
|
43621
43829
|
orgName: this.opts.orgName
|
|
43622
43830
|
});
|
|
43623
43831
|
const resolved = await atbash.resolveChainForOrg(this.opts.orgName);
|
|
43624
|
-
|
|
43832
|
+
if (!await atbash.checkAgentExists(this.agentPubkeyHex, { network: resolved.network })) {
|
|
43833
|
+
throw new Error(`agent is not registered on the resolved ${resolved.network} chain`);
|
|
43834
|
+
}
|
|
43835
|
+
const chainOpts = {
|
|
43836
|
+
...this.opts.chainOpts,
|
|
43837
|
+
network: resolved.network,
|
|
43838
|
+
blockchainRid: resolved.blockchainRid,
|
|
43839
|
+
nodeUrls: this.opts.chainOpts?.nodeUrls ?? resolved.nodeUrls
|
|
43840
|
+
};
|
|
43625
43841
|
this._resolvedChainOpts = chainOpts;
|
|
43626
43842
|
this.logger.info(
|
|
43627
43843
|
`[atbash] resolved org \u2192 chain \u2014 org=${this.opts.orgName} network=${resolved.network}`
|
|
@@ -43630,9 +43846,9 @@ var MemoryGuardManager = class {
|
|
|
43630
43846
|
} catch (err) {
|
|
43631
43847
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43632
43848
|
this.logger.warn(
|
|
43633
|
-
`[atbash] org \u2192 chain resolution failed (
|
|
43849
|
+
`[atbash] org \u2192 chain resolution failed (memory read will be blocked): ${msg}`
|
|
43634
43850
|
);
|
|
43635
|
-
|
|
43851
|
+
throw err;
|
|
43636
43852
|
} finally {
|
|
43637
43853
|
this._resolveChainInflight = void 0;
|
|
43638
43854
|
}
|
|
@@ -43651,13 +43867,18 @@ var MemoryGuardManager = class {
|
|
|
43651
43867
|
const result = await syncLocalMemory(this.opts.auth, seed, {
|
|
43652
43868
|
ttlMs: 0,
|
|
43653
43869
|
force: true,
|
|
43654
|
-
chainOpts
|
|
43870
|
+
chainOpts,
|
|
43871
|
+
filePath: import_node_path7.default.basename(this.memoryFilePath)
|
|
43655
43872
|
});
|
|
43656
43873
|
if (!result.drifted && result.pointer.activeId == null) {
|
|
43657
43874
|
this.logger.info(
|
|
43658
43875
|
`[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.`
|
|
43659
43876
|
);
|
|
43660
43877
|
} else if (result.drifted && result.current) {
|
|
43878
|
+
if (result.current.filePath !== import_node_path7.default.basename(this.memoryFilePath) || result.current.isActive !== true || result.current.decryptError) {
|
|
43879
|
+
this.logger.warn("[atbash] boot sync REFUSED mismatched memory version");
|
|
43880
|
+
return;
|
|
43881
|
+
}
|
|
43661
43882
|
if (result.current.score < this.rollbackMinScore) {
|
|
43662
43883
|
this.logger.warn(
|
|
43663
43884
|
`[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.`
|
|
@@ -43671,7 +43892,8 @@ var MemoryGuardManager = class {
|
|
|
43671
43892
|
await this.writeMemoryAtomic(result.current.content);
|
|
43672
43893
|
} catch (err) {
|
|
43673
43894
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43674
|
-
this.logger.warn("[atbash] boot sync write failed
|
|
43895
|
+
this.logger.warn("[atbash] boot sync write failed; pointer remains unchanged", { error: msg });
|
|
43896
|
+
return;
|
|
43675
43897
|
}
|
|
43676
43898
|
}
|
|
43677
43899
|
await this.pointerStore.set(this.agentPubkeyHex, result.pointer);
|
|
@@ -43685,9 +43907,9 @@ var MemoryGuardManager = class {
|
|
|
43685
43907
|
}
|
|
43686
43908
|
/**
|
|
43687
43909
|
* Returns a `HookDecision` when the guard reached a decision about this event.
|
|
43688
|
-
* Returns `null`
|
|
43689
|
-
*
|
|
43690
|
-
*
|
|
43910
|
+
* Returns `null` for non-memory events, other files this manager does not
|
|
43911
|
+
* own, or unverified observe-mode reads. An enforced read of the managed
|
|
43912
|
+
* file blocks when verification cannot complete.
|
|
43691
43913
|
*
|
|
43692
43914
|
* A returned decision carries `audited` (see `HookDecision`). Only
|
|
43693
43915
|
* `{ allow: true, audited: true }` means "checked and cleared"; anything else
|
|
@@ -43723,6 +43945,7 @@ var MemoryGuardManager = class {
|
|
|
43723
43945
|
endpoint: this.opts.judgeEndpoint,
|
|
43724
43946
|
verifyPubKey: this.opts.judgeVerifyPubKey,
|
|
43725
43947
|
orgName: this.opts.orgName,
|
|
43948
|
+
chainOpts: this.opts.chainOpts,
|
|
43726
43949
|
patterns: this.opts.memoryPathPatterns,
|
|
43727
43950
|
toolNames: this.opts.memoryWriteToolNames,
|
|
43728
43951
|
enforce: this.enforce,
|
|
@@ -43767,6 +43990,14 @@ var MemoryGuardManager = class {
|
|
|
43767
43990
|
reason: `memory scan returned ${sr2.verdict} but this guard is not enforcing it`
|
|
43768
43991
|
};
|
|
43769
43992
|
}
|
|
43993
|
+
if (guard.committed !== true) {
|
|
43994
|
+
return {
|
|
43995
|
+
allow: true,
|
|
43996
|
+
audited: false,
|
|
43997
|
+
verdict: sr2.verdict,
|
|
43998
|
+
reason: "memory scan passed but chain commit did not confirm"
|
|
43999
|
+
};
|
|
44000
|
+
}
|
|
43770
44001
|
return { allow: true, audited: true, verdict: sr2.verdict };
|
|
43771
44002
|
}
|
|
43772
44003
|
/**
|
|
@@ -43801,62 +44032,73 @@ var MemoryGuardManager = class {
|
|
|
43801
44032
|
return found.every((p) => import_node_path7.default.resolve(p) === managed);
|
|
43802
44033
|
}
|
|
43803
44034
|
async handleMemoryRead(event, ctx) {
|
|
44035
|
+
if (!this.vouchesForTarget(event, ctx)) return null;
|
|
43804
44036
|
const pointer = await this.pointerStore.get(this.agentPubkeyHex);
|
|
43805
44037
|
let result;
|
|
44038
|
+
let chainOpts;
|
|
43806
44039
|
try {
|
|
43807
|
-
|
|
44040
|
+
chainOpts = await this.resolveChainOpts();
|
|
43808
44041
|
result = await syncLocalMemory(this.opts.auth, pointer, {
|
|
43809
44042
|
ttlMs: this.ttlMs,
|
|
43810
|
-
|
|
44043
|
+
force: this.enforce,
|
|
44044
|
+
chainOpts,
|
|
44045
|
+
filePath: import_node_path7.default.basename(this.memoryFilePath)
|
|
43811
44046
|
});
|
|
43812
44047
|
} catch (err) {
|
|
43813
44048
|
if (err instanceof MemoryIntegrityError) {
|
|
43814
44049
|
const reason = `Memory integrity check failed on id ${err.id} \u2014 read blocked.`;
|
|
43815
44050
|
this.logger.warn("[atbash] MEMORY INTEGRITY FAILURE", { id: err.id, error: err.message });
|
|
43816
|
-
|
|
43817
|
-
return { block: true, blockReason: reason, allow: false, reason, audited: true };
|
|
44051
|
+
return this.unverifiedRead(reason);
|
|
43818
44052
|
}
|
|
43819
44053
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43820
|
-
this.logger.warn("[atbash] memory sync failed
|
|
43821
|
-
return
|
|
44054
|
+
this.logger.warn("[atbash] memory sync failed", { error: msg });
|
|
44055
|
+
return this.unverifiedRead(`Memory chain sync failed: ${msg}`);
|
|
43822
44056
|
}
|
|
43823
|
-
|
|
43824
|
-
|
|
43825
|
-
|
|
43826
|
-
|
|
43827
|
-
if (fresh.score < this.rollbackMinScore) {
|
|
43828
|
-
const reason = `Rolled-back memory version #${fresh.id} scored ${fresh.score} (below threshold ${this.rollbackMinScore}) \u2014 read blocked.`;
|
|
43829
|
-
this.logger.warn("[atbash] blocking read on low-score rollback", { id: fresh.id, score: fresh.score });
|
|
43830
|
-
if (!this.enforce) return null;
|
|
43831
|
-
return { block: true, blockReason: reason, allow: false, reason, audited: true };
|
|
43832
|
-
}
|
|
43833
|
-
this.logger.info("[atbash] memory drift detected \u2014 refreshing local file", {
|
|
43834
|
-
id: fresh.id,
|
|
43835
|
-
score: fresh.score
|
|
43836
|
-
});
|
|
43837
|
-
try {
|
|
43838
|
-
await this.writeMemoryAtomic(fresh.content);
|
|
43839
|
-
} catch (err) {
|
|
43840
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
43841
|
-
this.logger.warn("[atbash] failed to write refreshed memory (serving old)", { error: msg });
|
|
43842
|
-
onDiskIsCurrent = false;
|
|
43843
|
-
}
|
|
43844
|
-
} else {
|
|
43845
|
-
this.logger.info("[atbash] active memory removed on chain", { pubkey: this.agentPubkeyHex });
|
|
43846
|
-
onDiskIsCurrent = false;
|
|
43847
|
-
}
|
|
44057
|
+
if (!result.checked) return this.unverifiedRead("Memory chain state was not checked.");
|
|
44058
|
+
const activeId = result.pointer.activeId;
|
|
44059
|
+
if (activeId === null) {
|
|
44060
|
+
return this.unverifiedRead("No active chain version exists for the managed memory file.");
|
|
43848
44061
|
}
|
|
43849
|
-
|
|
43850
|
-
|
|
43851
|
-
|
|
43852
|
-
|
|
43853
|
-
|
|
43854
|
-
);
|
|
44062
|
+
let active;
|
|
44063
|
+
try {
|
|
44064
|
+
active = result.drifted ? result.current : await getMemoryById(activeId, this.opts.auth, chainOpts);
|
|
44065
|
+
} catch (err) {
|
|
44066
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
44067
|
+
this.logger.warn("[atbash] active memory row could not be verified", { error: msg });
|
|
44068
|
+
return this.unverifiedRead(`Memory version could not be verified: ${msg}`);
|
|
43855
44069
|
}
|
|
43856
|
-
|
|
43857
|
-
if (!
|
|
44070
|
+
const filePath = import_node_path7.default.basename(this.memoryFilePath);
|
|
44071
|
+
if (!active || active.id !== activeId || active.filePath !== filePath || active.isActive !== true || active.decryptError) {
|
|
44072
|
+
return this.unverifiedRead("Active memory version does not match the managed file.");
|
|
44073
|
+
}
|
|
44074
|
+
if (active.score < this.rollbackMinScore) {
|
|
44075
|
+
return this.unverifiedRead(`Memory version #${active.id} is below the minimum safety score.`);
|
|
44076
|
+
}
|
|
44077
|
+
try {
|
|
44078
|
+
if (result.drifted) {
|
|
44079
|
+
this.logger.info("[atbash] memory drift detected \u2014 refreshing local file", { id: active.id });
|
|
44080
|
+
await this.writeMemoryAtomic(active.content);
|
|
44081
|
+
}
|
|
44082
|
+
const onDisk = await import_node_fs6.promises.readFile(this.memoryFilePath, "utf8");
|
|
44083
|
+
if (onDisk !== active.content) {
|
|
44084
|
+
return this.unverifiedRead("Local memory bytes do not match the active chain version.");
|
|
44085
|
+
}
|
|
44086
|
+
} catch (err) {
|
|
44087
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
44088
|
+
this.logger.warn("[atbash] managed memory file could not be verified", { error: msg });
|
|
44089
|
+
return this.unverifiedRead(`Managed memory file could not be verified: ${msg}`);
|
|
44090
|
+
}
|
|
44091
|
+
await this.pointerStore.set(this.agentPubkeyHex, result.pointer);
|
|
43858
44092
|
return { allow: true, audited: true };
|
|
43859
44093
|
}
|
|
44094
|
+
/** Complete queued audit-log writes before a host shuts down or removes its workspace. */
|
|
44095
|
+
async flushLogs() {
|
|
44096
|
+
await this.logger.flush();
|
|
44097
|
+
}
|
|
44098
|
+
unverifiedRead(reason) {
|
|
44099
|
+
if (!this.enforce) return null;
|
|
44100
|
+
return { block: true, blockReason: reason, allow: false, reason, audited: true };
|
|
44101
|
+
}
|
|
43860
44102
|
async writeMemoryAtomic(content) {
|
|
43861
44103
|
await import_node_fs6.promises.mkdir(import_node_path7.default.dirname(this.memoryFilePath), { recursive: true });
|
|
43862
44104
|
const tmp = `${this.memoryFilePath}.${process.pid}.tmp`;
|