@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.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;
|
|
@@ -3198,7 +3205,7 @@ function loadAgentFromFile(keyPath) {
|
|
|
3198
3205
|
// src-ts/normalize.ts
|
|
3199
3206
|
function normalizeVerdict(raw2) {
|
|
3200
3207
|
if (raw2 === null || raw2 === void 0) return "No verdict";
|
|
3201
|
-
const v = String(raw2).toUpperCase();
|
|
3208
|
+
const v = String(raw2).trim().toUpperCase();
|
|
3202
3209
|
if (v === "ALLOW" || v === "GREEN") return "ALLOW";
|
|
3203
3210
|
if (v === "HOLD" || v === "YELLOW") return "HOLD";
|
|
3204
3211
|
if (v === "BLOCK" || v === "RED") return "BLOCK";
|
|
@@ -3235,17 +3242,58 @@ function allowSignalsPermit(data) {
|
|
|
3235
3242
|
}
|
|
3236
3243
|
return signals.length === 0 || signals.every((value) => value === true);
|
|
3237
3244
|
}
|
|
3245
|
+
function explicitRefusal(data) {
|
|
3246
|
+
let hold = false;
|
|
3247
|
+
for (const source of decisionSources(data)) {
|
|
3248
|
+
const verdict = verdictOf(source);
|
|
3249
|
+
if (verdict === "BLOCK" || verdict === "RED") return "BLOCK";
|
|
3250
|
+
if (verdict === "HOLD" || verdict === "YELLOW") hold = true;
|
|
3251
|
+
const actionType = actionTypeOf(source);
|
|
3252
|
+
if (actionType === "block") return "BLOCK";
|
|
3253
|
+
if (actionType === "hold_for_user_confirm") hold = true;
|
|
3254
|
+
}
|
|
3255
|
+
if (!allowSignalsPermit(data)) return "BLOCK";
|
|
3256
|
+
return hold ? "HOLD" : "";
|
|
3257
|
+
}
|
|
3258
|
+
function decisionSources(data) {
|
|
3259
|
+
return [data, isRecord(data.decision) ? data.decision : {}];
|
|
3260
|
+
}
|
|
3261
|
+
function verdictOf(source) {
|
|
3262
|
+
return typeof source.verdict === "string" ? source.verdict.trim().toUpperCase() : "";
|
|
3263
|
+
}
|
|
3264
|
+
function actionTypeRank(actionType) {
|
|
3265
|
+
if (actionType === "block") return 4;
|
|
3266
|
+
if (actionType === "hold_for_user_confirm") return 3;
|
|
3267
|
+
if (actionType === "") return 1;
|
|
3268
|
+
if (actionType === "allow") return 0;
|
|
3269
|
+
return 2;
|
|
3270
|
+
}
|
|
3271
|
+
function actionTypeOf(source) {
|
|
3272
|
+
let winner = "";
|
|
3273
|
+
for (const key3 of ["action_type", "actionType"]) {
|
|
3274
|
+
const raw2 = source[key3];
|
|
3275
|
+
if (raw2 === void 0 || raw2 === null) continue;
|
|
3276
|
+
const actionType = normalizeActionType(raw2);
|
|
3277
|
+
if (actionTypeRank(actionType) > actionTypeRank(winner)) winner = actionType;
|
|
3278
|
+
}
|
|
3279
|
+
return winner;
|
|
3280
|
+
}
|
|
3238
3281
|
function wireActionType(data) {
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3282
|
+
for (const source of decisionSources(data)) {
|
|
3283
|
+
const actionType = actionTypeOf(source);
|
|
3284
|
+
if (actionType !== "") return actionType;
|
|
3285
|
+
}
|
|
3286
|
+
return "";
|
|
3242
3287
|
}
|
|
3243
3288
|
function isAuditTier(data) {
|
|
3244
3289
|
const absentVerdict = data.verdict === null || data.verdict === void 0;
|
|
3245
3290
|
return absentVerdict && data.status === "logged";
|
|
3246
3291
|
}
|
|
3292
|
+
function isAuditTierBody(data) {
|
|
3293
|
+
return isAuditTier(data);
|
|
3294
|
+
}
|
|
3247
3295
|
function canonicalAllow(data) {
|
|
3248
|
-
if (
|
|
3296
|
+
if (explicitRefusal(data) !== "") return false;
|
|
3249
3297
|
const actionType = wireActionType(data);
|
|
3250
3298
|
if (actionType && actionType !== "allow") return false;
|
|
3251
3299
|
if (isAuditTier(data)) return true;
|
|
@@ -3487,6 +3535,10 @@ var Atbash = class _Atbash {
|
|
|
3487
3535
|
orgEncryptionPubKey;
|
|
3488
3536
|
/** When true (default), `auditToolCall` denies on any error. */
|
|
3489
3537
|
failClosed;
|
|
3538
|
+
/** When true (default), an unenforced (audit-tier) answer is refused. */
|
|
3539
|
+
requireEnforcement;
|
|
3540
|
+
/** Overall deadline for one auditToolCall decision, in ms. */
|
|
3541
|
+
decisionTimeoutMs;
|
|
3490
3542
|
/** Org key learned from the last agent-exists check, for this agent only. */
|
|
3491
3543
|
_orgKeyFromChain = null;
|
|
3492
3544
|
debug;
|
|
@@ -3560,6 +3612,8 @@ var Atbash = class _Atbash {
|
|
|
3560
3612
|
this.verifyPubKey = validated.verifyPubKey ?? void 0;
|
|
3561
3613
|
this.orgEncryptionPubKey = options.orgEncryptionPubKey;
|
|
3562
3614
|
this.failClosed = options.failClosed !== false;
|
|
3615
|
+
this.requireEnforcement = options.requireEnforcement !== false;
|
|
3616
|
+
this.decisionTimeoutMs = options.decisionTimeoutMs ?? DEFAULT_DECISION_TIMEOUT_MS;
|
|
3563
3617
|
this.debug = options.debug === true;
|
|
3564
3618
|
this.logger = options.logger ?? {};
|
|
3565
3619
|
this.http = new HttpClient(this.endpoint, options.timeoutMs ?? 12e4);
|
|
@@ -3570,16 +3624,23 @@ var Atbash = class _Atbash {
|
|
|
3570
3624
|
verifying: this.verifyPubKey ? "with response-signature pubkey configured" : "without signature verification"
|
|
3571
3625
|
});
|
|
3572
3626
|
}
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3627
|
+
if (isAllowlistedJudgeHost(this.endpoint)) {
|
|
3628
|
+
try {
|
|
3629
|
+
setupTelemetry({
|
|
3630
|
+
enabled: true,
|
|
3631
|
+
endpoint: this.endpoint,
|
|
3632
|
+
getAuthHeaders: () => this.authHeaders()
|
|
3633
|
+
});
|
|
3634
|
+
} catch (err) {
|
|
3635
|
+
this.logger.warn?.(
|
|
3636
|
+
"[atbash] telemetry setup failed \u2014 continuing without metrics",
|
|
3637
|
+
{ error: String(err) }
|
|
3638
|
+
);
|
|
3639
|
+
}
|
|
3640
|
+
} else {
|
|
3641
|
+
this.logger.info?.(
|
|
3642
|
+
"[atbash] telemetry disabled \u2014 the agent bearer is only sent to hosts that already receive it, and this judge endpoint is not one",
|
|
3643
|
+
{ endpoint: this.endpoint }
|
|
3583
3644
|
);
|
|
3584
3645
|
}
|
|
3585
3646
|
}
|
|
@@ -3643,6 +3704,8 @@ var Atbash = class _Atbash {
|
|
|
3643
3704
|
orgName: options.orgName,
|
|
3644
3705
|
verifyPubKey: validated.verifyPubKey ?? void 0,
|
|
3645
3706
|
failClosed: options.failClosed,
|
|
3707
|
+
requireEnforcement: options.requireEnforcement,
|
|
3708
|
+
decisionTimeoutMs: options.decisionTimeoutMs,
|
|
3646
3709
|
// ATBASH_DEBUG lets an operator turn diagnostics on without editing a
|
|
3647
3710
|
// host's plugin config, which is usually the harder half.
|
|
3648
3711
|
debug: options.debug ?? /^(1|true|yes)$/i.test(resolve("debug")),
|
|
@@ -3841,34 +3904,16 @@ var Atbash = class _Atbash {
|
|
|
3841
3904
|
}
|
|
3842
3905
|
if (!resp.ok) throw await this.httpError(resp);
|
|
3843
3906
|
const bodyBytes = Buffer.from(await resp.arrayBuffer());
|
|
3844
|
-
const verifyPubKey = options.verifyPubKey
|
|
3907
|
+
const verifyPubKey = this.effectiveVerifyKey(options.verifyPubKey);
|
|
3845
3908
|
if (verifyPubKey !== void 0) {
|
|
3846
|
-
|
|
3847
|
-
if (!sig) {
|
|
3848
|
-
throw new SignatureVerificationError(
|
|
3849
|
-
"missing X-Atbash-Signature header"
|
|
3850
|
-
);
|
|
3851
|
-
}
|
|
3852
|
-
let ok;
|
|
3853
|
-
try {
|
|
3854
|
-
ok = native.verifySignature(bodyBytes, sig, verifyPubKey);
|
|
3855
|
-
} catch (err) {
|
|
3856
|
-
throw new SignatureVerificationError(
|
|
3857
|
-
`signature verification threw: ${errorMessage(err)}`
|
|
3858
|
-
);
|
|
3859
|
-
}
|
|
3860
|
-
if (!ok) {
|
|
3861
|
-
throw new SignatureVerificationError(
|
|
3862
|
-
"signature does not verify against configured verifyPubKey"
|
|
3863
|
-
);
|
|
3864
|
-
}
|
|
3909
|
+
verifyResponseSignature(bodyBytes, resp, verifyPubKey);
|
|
3865
3910
|
}
|
|
3866
3911
|
const data = parseJson(bodyBytes);
|
|
3867
3912
|
const rawScore = data.score;
|
|
3868
3913
|
const score = typeof rawScore === "number" && Number.isInteger(rawScore) && rawScore >= 1 && rawScore <= 10 ? rawScore : void 0;
|
|
3869
3914
|
return {
|
|
3870
3915
|
verdict: normalizeVerdict(data.verdict),
|
|
3871
|
-
allow:
|
|
3916
|
+
allow: this.executable(data),
|
|
3872
3917
|
actionType: normalizeActionType(data.action_type),
|
|
3873
3918
|
reason: String(data.reason ?? ""),
|
|
3874
3919
|
confidence: Number(data.confidence ?? 0),
|
|
@@ -3879,7 +3924,8 @@ var Atbash = class _Atbash {
|
|
|
3879
3924
|
enforced: Boolean(data.enforced),
|
|
3880
3925
|
enforcementMode: String(data.enforcement_mode ?? ""),
|
|
3881
3926
|
status: String(data.status ?? ""),
|
|
3882
|
-
score
|
|
3927
|
+
score,
|
|
3928
|
+
refusal: explicitRefusal(data)
|
|
3883
3929
|
};
|
|
3884
3930
|
}
|
|
3885
3931
|
/* ── audit_tool_call (redact → judge → decision) ───────────────────────── */
|
|
@@ -3912,77 +3958,125 @@ var Atbash = class _Atbash {
|
|
|
3912
3958
|
}
|
|
3913
3959
|
try {
|
|
3914
3960
|
this.logger.info?.("[atbash] judge API called", { tool: toolName });
|
|
3915
|
-
const result = await
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
}
|
|
3930
|
-
return {
|
|
3931
|
-
allow: true,
|
|
3932
|
-
verdict: "ALLOW",
|
|
3933
|
-
reason: result.reason || "audit tier \u2014 request logged on-chain, no AI enforcement",
|
|
3934
|
-
toolCallId: result.toolCallId
|
|
3935
|
-
};
|
|
3936
|
-
}
|
|
3937
|
-
const action = result.actionType;
|
|
3938
|
-
if (action === "block") {
|
|
3961
|
+
const result = await withDeadline(
|
|
3962
|
+
this.judgeAction(actionText, contextText, {
|
|
3963
|
+
toolName,
|
|
3964
|
+
toolArgsJson: argsJson,
|
|
3965
|
+
orgName: this.orgName,
|
|
3966
|
+
// Passed through unredacted — caller vouches for the contents.
|
|
3967
|
+
resolved: input.resolved
|
|
3968
|
+
}),
|
|
3969
|
+
this.decisionTimeoutMs
|
|
3970
|
+
);
|
|
3971
|
+
return this.decide(result);
|
|
3972
|
+
} catch (err) {
|
|
3973
|
+
if (err instanceof SignatureVerificationError) {
|
|
3974
|
+
this.failJudge(errorMessage(err), err);
|
|
3939
3975
|
return {
|
|
3940
3976
|
allow: false,
|
|
3941
|
-
verdict: "
|
|
3942
|
-
reason:
|
|
3943
|
-
toolCallId: result.toolCallId
|
|
3977
|
+
verdict: "ERROR",
|
|
3978
|
+
reason: `The judge's reply failed its signature check (${errorMessage(err)}), so it was not trusted.`
|
|
3944
3979
|
};
|
|
3945
3980
|
}
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3981
|
+
return this.failJudge(errorMessage(err), err);
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
/**
|
|
3985
|
+
* The verify key for one call (R4, governor tier-2 review). A per-call key
|
|
3986
|
+
* may only narrow what the client accepts: on a client pinned to a judge
|
|
3987
|
+
* key it must be that same key, never a replacement; on an unpinned client
|
|
3988
|
+
* it adds the check.
|
|
3989
|
+
*/
|
|
3990
|
+
effectiveVerifyKey(perCall) {
|
|
3991
|
+
if (perCall === void 0) return this.verifyPubKey;
|
|
3992
|
+
const key3 = perCall.trim().toLowerCase();
|
|
3993
|
+
if (this.verifyPubKey !== void 0 && key3 !== this.verifyPubKey) {
|
|
3994
|
+
throw new SignatureVerificationError(
|
|
3995
|
+
"a per-call verifyPubKey cannot replace the judge key this client is pinned to"
|
|
3996
|
+
);
|
|
3997
|
+
}
|
|
3998
|
+
return key3;
|
|
3999
|
+
}
|
|
4000
|
+
/**
|
|
4001
|
+
* Collapse a judge result into a {@link Decision}. The most restrictive
|
|
4002
|
+
* signal wins (security review 2026-09-30, H1): `action_type`, `verdict`
|
|
4003
|
+
* and the canonical `allow` are each able to deny, and only a result where
|
|
4004
|
+
* all three agree may execute. A present `allow` / `decision.allow` that is
|
|
4005
|
+
* not exactly `true` is the judge declining to grant permission, so it
|
|
4006
|
+
* denies even under `failClosed: false` — that option relaxes *errors*, it
|
|
4007
|
+
* never overrides a refusal.
|
|
4008
|
+
*/
|
|
4009
|
+
decide(result) {
|
|
4010
|
+
const toolCallId = result.toolCallId;
|
|
4011
|
+
const deny = (verdict, reason) => ({
|
|
4012
|
+
allow: false,
|
|
4013
|
+
verdict,
|
|
4014
|
+
reason,
|
|
4015
|
+
toolCallId
|
|
4016
|
+
});
|
|
4017
|
+
const notGranted = "the judge response did not grant permission";
|
|
4018
|
+
const held = deny("HOLD", result.reason || "held for human confirmation");
|
|
4019
|
+
const blocked = deny("BLOCK", result.reason || notGranted);
|
|
4020
|
+
if (result.refusal === "BLOCK") return blocked;
|
|
4021
|
+
if (result.verdict === "No verdict") {
|
|
4022
|
+
if (result.actionType === "block") {
|
|
4023
|
+
return blocked;
|
|
4024
|
+
}
|
|
4025
|
+
if (result.actionType === "hold_for_user_confirm") {
|
|
4026
|
+
return held;
|
|
4027
|
+
}
|
|
4028
|
+
if (result.refusal === "HOLD") return held;
|
|
4029
|
+
if (result.status !== "logged") {
|
|
4030
|
+
return this.failJudge(
|
|
4031
|
+
`judge returned no verdict without an audit-tier marker (status: ${result.status || "absent"})`,
|
|
4032
|
+
void 0,
|
|
4033
|
+
toolCallId
|
|
4034
|
+
);
|
|
3953
4035
|
}
|
|
3954
|
-
if (
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
reason: result.reason,
|
|
3960
|
-
toolCallId: result.toolCallId
|
|
3961
|
-
};
|
|
3962
|
-
}
|
|
3963
|
-
if (result.verdict === "BLOCK") {
|
|
3964
|
-
return {
|
|
3965
|
-
allow: false,
|
|
3966
|
-
verdict: "BLOCK",
|
|
3967
|
-
reason: result.reason,
|
|
3968
|
-
toolCallId: result.toolCallId
|
|
3969
|
-
};
|
|
3970
|
-
}
|
|
4036
|
+
if (this.requireEnforcement) {
|
|
4037
|
+
this.logger.warn?.(
|
|
4038
|
+
"[atbash] judge did not enforce this action (audit tier) and requireEnforcement is on: denied",
|
|
4039
|
+
{ toolCallId }
|
|
4040
|
+
);
|
|
3971
4041
|
return {
|
|
3972
|
-
allow:
|
|
3973
|
-
verdict: "
|
|
3974
|
-
reason:
|
|
3975
|
-
toolCallId
|
|
4042
|
+
allow: false,
|
|
4043
|
+
verdict: "ERROR",
|
|
4044
|
+
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.",
|
|
4045
|
+
toolCallId
|
|
3976
4046
|
};
|
|
3977
4047
|
}
|
|
3978
|
-
return
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
4048
|
+
if (result.allow !== true) return deny("BLOCK", notGranted);
|
|
4049
|
+
return {
|
|
4050
|
+
allow: true,
|
|
4051
|
+
verdict: "ALLOW",
|
|
4052
|
+
reason: result.reason || "audit tier \u2014 request logged on-chain, no AI enforcement",
|
|
4053
|
+
toolCallId
|
|
4054
|
+
};
|
|
4055
|
+
}
|
|
4056
|
+
const action = result.actionType;
|
|
4057
|
+
if (action === "block") {
|
|
4058
|
+
return deny("BLOCK", result.reason || "Blocked by your organisation's Atbash policy.");
|
|
3985
4059
|
}
|
|
4060
|
+
if (action === "hold_for_user_confirm") {
|
|
4061
|
+
return held;
|
|
4062
|
+
}
|
|
4063
|
+
if (result.refusal === "HOLD") return held;
|
|
4064
|
+
if (action === "allow") {
|
|
4065
|
+
if (result.verdict === "HOLD") return held;
|
|
4066
|
+
if (result.verdict === "BLOCK") return blocked;
|
|
4067
|
+
if (result.allow !== true) return deny("BLOCK", notGranted);
|
|
4068
|
+
return {
|
|
4069
|
+
allow: true,
|
|
4070
|
+
verdict: "ALLOW",
|
|
4071
|
+
reason: result.reason,
|
|
4072
|
+
toolCallId
|
|
4073
|
+
};
|
|
4074
|
+
}
|
|
4075
|
+
return this.failJudge(
|
|
4076
|
+
"unrecognized action_type from judge",
|
|
4077
|
+
void 0,
|
|
4078
|
+
toolCallId
|
|
4079
|
+
);
|
|
3986
4080
|
}
|
|
3987
4081
|
/**
|
|
3988
4082
|
* One exit for every judge failure.
|
|
@@ -3994,18 +4088,30 @@ var Atbash = class _Atbash {
|
|
|
3994
4088
|
*/
|
|
3995
4089
|
failJudge(reason, cause, toolCallId) {
|
|
3996
4090
|
const api2 = cause instanceof AtbashAPIError ? cause : null;
|
|
4091
|
+
const userReason = userFacingReason(reason, api2);
|
|
3997
4092
|
const status = api2 ? ` status=${api2.status || "no-response"}` : "";
|
|
4093
|
+
const detail = this.debug ? ` detail=${truncate(reason, 500)}` : "";
|
|
3998
4094
|
const body = this.debug && api2?.body ? ` body=${truncate(api2.body, 500)}` : "";
|
|
3999
4095
|
this.logger.warn?.(
|
|
4000
|
-
`[atbash] judge API failed \u2014${status} reason=${truncate(
|
|
4096
|
+
`[atbash] judge API failed \u2014${status} reason=${truncate(userReason, 300)}${detail}${body}`,
|
|
4001
4097
|
{
|
|
4002
|
-
reason,
|
|
4003
|
-
...api2 ? { status: api2.status
|
|
4098
|
+
reason: userReason,
|
|
4099
|
+
...api2 ? { status: api2.status } : {},
|
|
4100
|
+
...this.debug && api2 ? { body: api2.body, detail: reason } : {},
|
|
4004
4101
|
endpoint: this.endpoint,
|
|
4005
4102
|
...toolCallId ? { toolCallId } : {}
|
|
4006
4103
|
}
|
|
4007
4104
|
);
|
|
4008
|
-
return this.fail(
|
|
4105
|
+
return this.fail(userReason, toolCallId);
|
|
4106
|
+
}
|
|
4107
|
+
/**
|
|
4108
|
+
* The canonical permission for a response body under this client's
|
|
4109
|
+
* settings: {@link canonicalAllow}, minus the audit tier when
|
|
4110
|
+
* `requireEnforcement` is on. Never more permissive than `auditToolCall`.
|
|
4111
|
+
*/
|
|
4112
|
+
executable(data) {
|
|
4113
|
+
if (!canonicalAllow(data)) return false;
|
|
4114
|
+
return !(this.requireEnforcement && isAuditTierBody(data));
|
|
4009
4115
|
}
|
|
4010
4116
|
fail(reason, toolCallId) {
|
|
4011
4117
|
return { allow: !this.failClosed, verdict: "ERROR", reason, toolCallId };
|
|
@@ -4031,11 +4137,16 @@ var Atbash = class _Atbash {
|
|
|
4031
4137
|
this.authHeaders(brid)
|
|
4032
4138
|
);
|
|
4033
4139
|
await this.raiseIfError(resp);
|
|
4034
|
-
const
|
|
4140
|
+
const bytes = Buffer.from(await resp.arrayBuffer());
|
|
4141
|
+
if (this.verifyPubKey !== void 0) {
|
|
4142
|
+
verifyResponseSignature(bytes, resp, this.verifyPubKey);
|
|
4143
|
+
}
|
|
4144
|
+
const parsed = tryParseJson(bytes.toString("utf-8"));
|
|
4145
|
+
const data = isRecord2(parsed) ? parsed : {};
|
|
4035
4146
|
return {
|
|
4036
4147
|
status: normalizeStatus(data.status),
|
|
4037
4148
|
verdict: normalizeVerdict(data.verdict),
|
|
4038
|
-
allow:
|
|
4149
|
+
allow: this.executable(data),
|
|
4039
4150
|
reason: String(data.reason ?? ""),
|
|
4040
4151
|
judgmentId: String(data.judgmentId ?? judgmentId),
|
|
4041
4152
|
onChain: optBool(data.onChain),
|
|
@@ -4510,6 +4621,66 @@ var Atbash = class _Atbash {
|
|
|
4510
4621
|
return native.containsEvasionCharacters(text);
|
|
4511
4622
|
}
|
|
4512
4623
|
};
|
|
4624
|
+
function userFacingReason(reason, api2) {
|
|
4625
|
+
if (api2 && api2.status > 0) {
|
|
4626
|
+
const where = ` (HTTP ${api2.status}${api2.path ? ` from ${api2.path}` : ""})`;
|
|
4627
|
+
const b = api2.body.toLowerCase();
|
|
4628
|
+
if (b.includes("agent not registered")) {
|
|
4629
|
+
return `Agent not registered with Atbash: onboard it in the Atbash dashboard${where}.`;
|
|
4630
|
+
}
|
|
4631
|
+
if (b.includes("jailed")) {
|
|
4632
|
+
return `This agent is jailed; unjail it in the Atbash dashboard${where}.`;
|
|
4633
|
+
}
|
|
4634
|
+
if (b.includes("agent has no policy") || b.includes("no policy configured")) {
|
|
4635
|
+
return `This agent has no policy; attach one in the Atbash dashboard${where}.`;
|
|
4636
|
+
}
|
|
4637
|
+
if (b.includes("audit tier") || b.includes("verdict disabled") || b.includes("verdict not supported")) {
|
|
4638
|
+
return `The organisation's plan does not include checked decisions${where}.`;
|
|
4639
|
+
}
|
|
4640
|
+
return `The Atbash judge answered with an error${where}, so the action was not approved.`;
|
|
4641
|
+
}
|
|
4642
|
+
const text = api2 ? api2.body || reason : reason;
|
|
4643
|
+
return truncate(text.replace(/https?:\/\/[^\s)]+/g, "the judge"), 300);
|
|
4644
|
+
}
|
|
4645
|
+
var DEFAULT_DECISION_TIMEOUT_MS = 6e4;
|
|
4646
|
+
async function withDeadline(p, ms) {
|
|
4647
|
+
let timer;
|
|
4648
|
+
const deadline = new Promise((_, reject) => {
|
|
4649
|
+
timer = setTimeout(
|
|
4650
|
+
() => reject(
|
|
4651
|
+
new Error(
|
|
4652
|
+
`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.`
|
|
4653
|
+
)
|
|
4654
|
+
),
|
|
4655
|
+
ms
|
|
4656
|
+
);
|
|
4657
|
+
});
|
|
4658
|
+
p.catch(() => void 0);
|
|
4659
|
+
try {
|
|
4660
|
+
return await Promise.race([p, deadline]);
|
|
4661
|
+
} finally {
|
|
4662
|
+
clearTimeout(timer);
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
function verifyResponseSignature(bodyBytes, resp, verifyPubKey) {
|
|
4666
|
+
const sig = resp.headers.get("X-Atbash-Signature");
|
|
4667
|
+
if (!sig) {
|
|
4668
|
+
throw new SignatureVerificationError("missing X-Atbash-Signature header");
|
|
4669
|
+
}
|
|
4670
|
+
let ok;
|
|
4671
|
+
try {
|
|
4672
|
+
ok = native.verifySignature(bodyBytes, sig, verifyPubKey);
|
|
4673
|
+
} catch (err) {
|
|
4674
|
+
throw new SignatureVerificationError(
|
|
4675
|
+
`signature verification threw: ${errorMessage(err)}`
|
|
4676
|
+
);
|
|
4677
|
+
}
|
|
4678
|
+
if (!ok) {
|
|
4679
|
+
throw new SignatureVerificationError(
|
|
4680
|
+
"signature does not verify against configured verifyPubKey"
|
|
4681
|
+
);
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4513
4684
|
function baseToolCall(raw2) {
|
|
4514
4685
|
return {
|
|
4515
4686
|
toolCallId: String(raw2.tool_call_id ?? ""),
|
|
@@ -4723,7 +4894,11 @@ async function scanMemory(entry, auth, opts) {
|
|
|
4723
4894
|
const atbash = new Atbash(auth.privkey, {
|
|
4724
4895
|
endpoint: opts?.endpoint,
|
|
4725
4896
|
verifyPubKey: opts?.verifyPubKey,
|
|
4726
|
-
orgName: opts?.orgName
|
|
4897
|
+
orgName: opts?.orgName,
|
|
4898
|
+
// Memory scans keep their existing audit-tier semantics (an audit-tier
|
|
4899
|
+
// scan is not a defect the scanner invents). Whether an unenforced scan
|
|
4900
|
+
// may commit memory is the memory-guard PR's decision, not H6's.
|
|
4901
|
+
requireEnforcement: false
|
|
4727
4902
|
});
|
|
4728
4903
|
const result = await atbash.judgeAction(request.prompt, request.context, {
|
|
4729
4904
|
toolName: opts?.toolName ?? "memory_write",
|
|
@@ -4759,6 +4934,7 @@ async function scanMemory(entry, auth, opts) {
|
|
|
4759
4934
|
verdict = "yellow";
|
|
4760
4935
|
}
|
|
4761
4936
|
}
|
|
4937
|
+
if (verdict === "green" && result.allow !== true) verdict = "red";
|
|
4762
4938
|
const parsed = native.parseScoreFromReason(result.reason);
|
|
4763
4939
|
const score = result.score ?? parsed.score ?? native.defaultScoreForVerdict(verdict);
|
|
4764
4940
|
return {
|
|
@@ -42977,7 +43153,7 @@ async function commitMemoryVersion(plaintext, auth, opts) {
|
|
|
42977
43153
|
const chainOpts = await resolveChainOptsForOrg(opts, auth);
|
|
42978
43154
|
const client = await buildChainClient(chainOpts);
|
|
42979
43155
|
const { sigProvider } = buildSigner(auth);
|
|
42980
|
-
await client.signAndSendUniqueTransaction(
|
|
43156
|
+
const receipt = await client.signAndSendUniqueTransaction(
|
|
42981
43157
|
{
|
|
42982
43158
|
name: native.OP_ADD_AGENT_MEMORY,
|
|
42983
43159
|
args: [
|
|
@@ -42990,6 +43166,9 @@ async function commitMemoryVersion(plaintext, auth, opts) {
|
|
|
42990
43166
|
},
|
|
42991
43167
|
sigProvider
|
|
42992
43168
|
);
|
|
43169
|
+
if (receipt.status !== index.ResponseStatus.Confirmed) {
|
|
43170
|
+
throw new Error(`Memory transaction was not confirmed (status=${receipt.status})`);
|
|
43171
|
+
}
|
|
42993
43172
|
}
|
|
42994
43173
|
async function decryptRow(row, key3) {
|
|
42995
43174
|
const parsed = native.parseMemoryRow(gtvToFfiSafe(row));
|
|
@@ -43099,13 +43278,16 @@ async function rollbackMemory(toId, reason, auth, opts) {
|
|
|
43099
43278
|
const chainOpts = await resolveChainOptsForOrg(opts, auth);
|
|
43100
43279
|
const client = await buildChainClient(chainOpts);
|
|
43101
43280
|
const { sigProvider } = buildSigner(auth);
|
|
43102
|
-
await client.signAndSendUniqueTransaction(
|
|
43281
|
+
const receipt = await client.signAndSendUniqueTransaction(
|
|
43103
43282
|
{
|
|
43104
43283
|
name: native.OP_ROLLBACK_AGENT_MEMORY,
|
|
43105
43284
|
args: [toGtxBytes(args.agent_pubkey), args.to_id, args.reason]
|
|
43106
43285
|
},
|
|
43107
43286
|
sigProvider
|
|
43108
43287
|
);
|
|
43288
|
+
if (receipt.status !== index.ResponseStatus.Confirmed) {
|
|
43289
|
+
throw new Error(`Memory rollback was not confirmed (status=${receipt.status})`);
|
|
43290
|
+
}
|
|
43109
43291
|
}
|
|
43110
43292
|
|
|
43111
43293
|
// src-ts/memory/_json_safe.ts
|
|
@@ -43244,6 +43426,7 @@ async function guardMemoryWrite(input) {
|
|
|
43244
43426
|
endpoint,
|
|
43245
43427
|
verifyPubKey,
|
|
43246
43428
|
orgName,
|
|
43429
|
+
chainOpts,
|
|
43247
43430
|
threshold,
|
|
43248
43431
|
patterns,
|
|
43249
43432
|
toolNames,
|
|
@@ -43300,23 +43483,41 @@ async function guardMemoryWrite(input) {
|
|
|
43300
43483
|
committed: false
|
|
43301
43484
|
};
|
|
43302
43485
|
}
|
|
43486
|
+
if (scanResult.verdict !== "green" && scanResult.verdict !== "yellow") {
|
|
43487
|
+
const reason = "Memory scan returned an unrecognized verdict.";
|
|
43488
|
+
return {
|
|
43489
|
+
handled: true,
|
|
43490
|
+
decision: enforce ? { allow: false, block: true, reason } : { allow: true, reason },
|
|
43491
|
+
scanResult,
|
|
43492
|
+
committed: false
|
|
43493
|
+
};
|
|
43494
|
+
}
|
|
43303
43495
|
const filePath = path3.basename(memEntry.key);
|
|
43304
|
-
|
|
43305
|
-
|
|
43306
|
-
|
|
43307
|
-
|
|
43308
|
-
|
|
43309
|
-
|
|
43310
|
-
|
|
43496
|
+
try {
|
|
43497
|
+
await commitMemoryVersion(memEntry.value, auth, {
|
|
43498
|
+
score: scanResult.score,
|
|
43499
|
+
filePath,
|
|
43500
|
+
orgName,
|
|
43501
|
+
endpoint,
|
|
43502
|
+
verifyPubKey,
|
|
43503
|
+
chainOpts
|
|
43504
|
+
});
|
|
43505
|
+
} catch (err) {
|
|
43311
43506
|
const reason = err instanceof Error ? err.message : String(err);
|
|
43312
43507
|
logger2?.warn?.("[atbash] memory commit to chain failed", {
|
|
43313
43508
|
path: memEntry.key,
|
|
43314
43509
|
filePath,
|
|
43315
43510
|
reason
|
|
43316
43511
|
});
|
|
43317
|
-
|
|
43512
|
+
return {
|
|
43513
|
+
handled: true,
|
|
43514
|
+
decision: enforce ? { allow: false, block: true, reason: `Memory commit failed: ${reason}` } : { allow: true, reason: `Memory commit failed: ${reason}` },
|
|
43515
|
+
scanResult,
|
|
43516
|
+
committed: false
|
|
43517
|
+
};
|
|
43518
|
+
}
|
|
43318
43519
|
logger2?.info?.(
|
|
43319
|
-
scanResult.verdict === "yellow" ? "[atbash] memory
|
|
43520
|
+
scanResult.verdict === "yellow" ? "[atbash] memory ALLOW (yellow, low score)" : "[atbash] memory ALLOW",
|
|
43320
43521
|
{
|
|
43321
43522
|
path: memEntry.key,
|
|
43322
43523
|
filePath,
|
|
@@ -43349,7 +43550,7 @@ async function syncLocalMemory(auth, pointer, opts = {}) {
|
|
|
43349
43550
|
if (withinTtl) {
|
|
43350
43551
|
return { drifted: false, checked: false, pointer };
|
|
43351
43552
|
}
|
|
43352
|
-
const currentId = await getActiveMemoryId(auth, opts.chainOpts);
|
|
43553
|
+
const currentId = await getActiveMemoryId(auth, opts.chainOpts, opts.filePath);
|
|
43353
43554
|
const nextPointer = { activeId: currentId, checkedAt: now };
|
|
43354
43555
|
if (currentId === pointer.activeId) {
|
|
43355
43556
|
return { drifted: false, checked: true, pointer: nextPointer };
|
|
@@ -43431,13 +43632,16 @@ function formatMeta(meta) {
|
|
|
43431
43632
|
}
|
|
43432
43633
|
function createFileLogger(filePath, upstream) {
|
|
43433
43634
|
let queue = Promise.resolve();
|
|
43635
|
+
let writeError;
|
|
43434
43636
|
async function ensureDir() {
|
|
43435
43637
|
await fs2.mkdir(path5.dirname(filePath), { recursive: true });
|
|
43436
43638
|
}
|
|
43437
43639
|
function append(level, message, meta) {
|
|
43438
43640
|
const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${message}${formatMeta(meta)}
|
|
43439
43641
|
`;
|
|
43440
|
-
queue = queue.then(ensureDir).then(() => fs2.appendFile(filePath, line, "utf8")).catch(() => {
|
|
43642
|
+
queue = queue.then(ensureDir).then(() => fs2.appendFile(filePath, line, "utf8")).catch((error4) => {
|
|
43643
|
+
writeError = error4 instanceof Error ? error4 : new Error(String(error4));
|
|
43644
|
+
upstream?.warn("[atbash] plugin log write failed", { error: writeError.message });
|
|
43441
43645
|
});
|
|
43442
43646
|
}
|
|
43443
43647
|
return {
|
|
@@ -43448,6 +43652,10 @@ function createFileLogger(filePath, upstream) {
|
|
|
43448
43652
|
warn(message, meta) {
|
|
43449
43653
|
upstream?.warn(message, meta ?? {});
|
|
43450
43654
|
append("warn", message, meta);
|
|
43655
|
+
},
|
|
43656
|
+
async flush() {
|
|
43657
|
+
await queue;
|
|
43658
|
+
if (writeError) throw writeError;
|
|
43451
43659
|
}
|
|
43452
43660
|
};
|
|
43453
43661
|
}
|
|
@@ -43537,7 +43745,15 @@ var MemoryGuardManager = class {
|
|
|
43537
43745
|
orgName: this.opts.orgName
|
|
43538
43746
|
});
|
|
43539
43747
|
const resolved = await atbash.resolveChainForOrg(this.opts.orgName);
|
|
43540
|
-
|
|
43748
|
+
if (!await atbash.checkAgentExists(this.agentPubkeyHex, { network: resolved.network })) {
|
|
43749
|
+
throw new Error(`agent is not registered on the resolved ${resolved.network} chain`);
|
|
43750
|
+
}
|
|
43751
|
+
const chainOpts = {
|
|
43752
|
+
...this.opts.chainOpts,
|
|
43753
|
+
network: resolved.network,
|
|
43754
|
+
blockchainRid: resolved.blockchainRid,
|
|
43755
|
+
nodeUrls: this.opts.chainOpts?.nodeUrls ?? resolved.nodeUrls
|
|
43756
|
+
};
|
|
43541
43757
|
this._resolvedChainOpts = chainOpts;
|
|
43542
43758
|
this.logger.info(
|
|
43543
43759
|
`[atbash] resolved org \u2192 chain \u2014 org=${this.opts.orgName} network=${resolved.network}`
|
|
@@ -43546,9 +43762,9 @@ var MemoryGuardManager = class {
|
|
|
43546
43762
|
} catch (err) {
|
|
43547
43763
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43548
43764
|
this.logger.warn(
|
|
43549
|
-
`[atbash] org \u2192 chain resolution failed (
|
|
43765
|
+
`[atbash] org \u2192 chain resolution failed (memory read will be blocked): ${msg}`
|
|
43550
43766
|
);
|
|
43551
|
-
|
|
43767
|
+
throw err;
|
|
43552
43768
|
} finally {
|
|
43553
43769
|
this._resolveChainInflight = void 0;
|
|
43554
43770
|
}
|
|
@@ -43567,13 +43783,18 @@ var MemoryGuardManager = class {
|
|
|
43567
43783
|
const result = await syncLocalMemory(this.opts.auth, seed, {
|
|
43568
43784
|
ttlMs: 0,
|
|
43569
43785
|
force: true,
|
|
43570
|
-
chainOpts
|
|
43786
|
+
chainOpts,
|
|
43787
|
+
filePath: path6.basename(this.memoryFilePath)
|
|
43571
43788
|
});
|
|
43572
43789
|
if (!result.drifted && result.pointer.activeId == null) {
|
|
43573
43790
|
this.logger.info(
|
|
43574
43791
|
`[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.`
|
|
43575
43792
|
);
|
|
43576
43793
|
} else if (result.drifted && result.current) {
|
|
43794
|
+
if (result.current.filePath !== path6.basename(this.memoryFilePath) || result.current.isActive !== true || result.current.decryptError) {
|
|
43795
|
+
this.logger.warn("[atbash] boot sync REFUSED mismatched memory version");
|
|
43796
|
+
return;
|
|
43797
|
+
}
|
|
43577
43798
|
if (result.current.score < this.rollbackMinScore) {
|
|
43578
43799
|
this.logger.warn(
|
|
43579
43800
|
`[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.`
|
|
@@ -43587,7 +43808,8 @@ var MemoryGuardManager = class {
|
|
|
43587
43808
|
await this.writeMemoryAtomic(result.current.content);
|
|
43588
43809
|
} catch (err) {
|
|
43589
43810
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43590
|
-
this.logger.warn("[atbash] boot sync write failed
|
|
43811
|
+
this.logger.warn("[atbash] boot sync write failed; pointer remains unchanged", { error: msg });
|
|
43812
|
+
return;
|
|
43591
43813
|
}
|
|
43592
43814
|
}
|
|
43593
43815
|
await this.pointerStore.set(this.agentPubkeyHex, result.pointer);
|
|
@@ -43601,9 +43823,9 @@ var MemoryGuardManager = class {
|
|
|
43601
43823
|
}
|
|
43602
43824
|
/**
|
|
43603
43825
|
* Returns a `HookDecision` when the guard reached a decision about this event.
|
|
43604
|
-
* Returns `null`
|
|
43605
|
-
*
|
|
43606
|
-
*
|
|
43826
|
+
* Returns `null` for non-memory events, other files this manager does not
|
|
43827
|
+
* own, or unverified observe-mode reads. An enforced read of the managed
|
|
43828
|
+
* file blocks when verification cannot complete.
|
|
43607
43829
|
*
|
|
43608
43830
|
* A returned decision carries `audited` (see `HookDecision`). Only
|
|
43609
43831
|
* `{ allow: true, audited: true }` means "checked and cleared"; anything else
|
|
@@ -43639,6 +43861,7 @@ var MemoryGuardManager = class {
|
|
|
43639
43861
|
endpoint: this.opts.judgeEndpoint,
|
|
43640
43862
|
verifyPubKey: this.opts.judgeVerifyPubKey,
|
|
43641
43863
|
orgName: this.opts.orgName,
|
|
43864
|
+
chainOpts: this.opts.chainOpts,
|
|
43642
43865
|
patterns: this.opts.memoryPathPatterns,
|
|
43643
43866
|
toolNames: this.opts.memoryWriteToolNames,
|
|
43644
43867
|
enforce: this.enforce,
|
|
@@ -43683,6 +43906,14 @@ var MemoryGuardManager = class {
|
|
|
43683
43906
|
reason: `memory scan returned ${sr2.verdict} but this guard is not enforcing it`
|
|
43684
43907
|
};
|
|
43685
43908
|
}
|
|
43909
|
+
if (guard.committed !== true) {
|
|
43910
|
+
return {
|
|
43911
|
+
allow: true,
|
|
43912
|
+
audited: false,
|
|
43913
|
+
verdict: sr2.verdict,
|
|
43914
|
+
reason: "memory scan passed but chain commit did not confirm"
|
|
43915
|
+
};
|
|
43916
|
+
}
|
|
43686
43917
|
return { allow: true, audited: true, verdict: sr2.verdict };
|
|
43687
43918
|
}
|
|
43688
43919
|
/**
|
|
@@ -43717,62 +43948,73 @@ var MemoryGuardManager = class {
|
|
|
43717
43948
|
return found.every((p) => path6.resolve(p) === managed);
|
|
43718
43949
|
}
|
|
43719
43950
|
async handleMemoryRead(event, ctx) {
|
|
43951
|
+
if (!this.vouchesForTarget(event, ctx)) return null;
|
|
43720
43952
|
const pointer = await this.pointerStore.get(this.agentPubkeyHex);
|
|
43721
43953
|
let result;
|
|
43954
|
+
let chainOpts;
|
|
43722
43955
|
try {
|
|
43723
|
-
|
|
43956
|
+
chainOpts = await this.resolveChainOpts();
|
|
43724
43957
|
result = await syncLocalMemory(this.opts.auth, pointer, {
|
|
43725
43958
|
ttlMs: this.ttlMs,
|
|
43726
|
-
|
|
43959
|
+
force: this.enforce,
|
|
43960
|
+
chainOpts,
|
|
43961
|
+
filePath: path6.basename(this.memoryFilePath)
|
|
43727
43962
|
});
|
|
43728
43963
|
} catch (err) {
|
|
43729
43964
|
if (err instanceof MemoryIntegrityError) {
|
|
43730
43965
|
const reason = `Memory integrity check failed on id ${err.id} \u2014 read blocked.`;
|
|
43731
43966
|
this.logger.warn("[atbash] MEMORY INTEGRITY FAILURE", { id: err.id, error: err.message });
|
|
43732
|
-
|
|
43733
|
-
return { block: true, blockReason: reason, allow: false, reason, audited: true };
|
|
43967
|
+
return this.unverifiedRead(reason);
|
|
43734
43968
|
}
|
|
43735
43969
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43736
|
-
this.logger.warn("[atbash] memory sync failed
|
|
43737
|
-
return
|
|
43970
|
+
this.logger.warn("[atbash] memory sync failed", { error: msg });
|
|
43971
|
+
return this.unverifiedRead(`Memory chain sync failed: ${msg}`);
|
|
43738
43972
|
}
|
|
43739
|
-
|
|
43740
|
-
|
|
43741
|
-
|
|
43742
|
-
|
|
43743
|
-
if (fresh.score < this.rollbackMinScore) {
|
|
43744
|
-
const reason = `Rolled-back memory version #${fresh.id} scored ${fresh.score} (below threshold ${this.rollbackMinScore}) \u2014 read blocked.`;
|
|
43745
|
-
this.logger.warn("[atbash] blocking read on low-score rollback", { id: fresh.id, score: fresh.score });
|
|
43746
|
-
if (!this.enforce) return null;
|
|
43747
|
-
return { block: true, blockReason: reason, allow: false, reason, audited: true };
|
|
43748
|
-
}
|
|
43749
|
-
this.logger.info("[atbash] memory drift detected \u2014 refreshing local file", {
|
|
43750
|
-
id: fresh.id,
|
|
43751
|
-
score: fresh.score
|
|
43752
|
-
});
|
|
43753
|
-
try {
|
|
43754
|
-
await this.writeMemoryAtomic(fresh.content);
|
|
43755
|
-
} catch (err) {
|
|
43756
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
43757
|
-
this.logger.warn("[atbash] failed to write refreshed memory (serving old)", { error: msg });
|
|
43758
|
-
onDiskIsCurrent = false;
|
|
43759
|
-
}
|
|
43760
|
-
} else {
|
|
43761
|
-
this.logger.info("[atbash] active memory removed on chain", { pubkey: this.agentPubkeyHex });
|
|
43762
|
-
onDiskIsCurrent = false;
|
|
43763
|
-
}
|
|
43973
|
+
if (!result.checked) return this.unverifiedRead("Memory chain state was not checked.");
|
|
43974
|
+
const activeId = result.pointer.activeId;
|
|
43975
|
+
if (activeId === null) {
|
|
43976
|
+
return this.unverifiedRead("No active chain version exists for the managed memory file.");
|
|
43764
43977
|
}
|
|
43765
|
-
|
|
43766
|
-
|
|
43767
|
-
|
|
43768
|
-
|
|
43769
|
-
|
|
43770
|
-
);
|
|
43978
|
+
let active;
|
|
43979
|
+
try {
|
|
43980
|
+
active = result.drifted ? result.current : await getMemoryById(activeId, this.opts.auth, chainOpts);
|
|
43981
|
+
} catch (err) {
|
|
43982
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
43983
|
+
this.logger.warn("[atbash] active memory row could not be verified", { error: msg });
|
|
43984
|
+
return this.unverifiedRead(`Memory version could not be verified: ${msg}`);
|
|
43771
43985
|
}
|
|
43772
|
-
|
|
43773
|
-
if (!
|
|
43986
|
+
const filePath = path6.basename(this.memoryFilePath);
|
|
43987
|
+
if (!active || active.id !== activeId || active.filePath !== filePath || active.isActive !== true || active.decryptError) {
|
|
43988
|
+
return this.unverifiedRead("Active memory version does not match the managed file.");
|
|
43989
|
+
}
|
|
43990
|
+
if (active.score < this.rollbackMinScore) {
|
|
43991
|
+
return this.unverifiedRead(`Memory version #${active.id} is below the minimum safety score.`);
|
|
43992
|
+
}
|
|
43993
|
+
try {
|
|
43994
|
+
if (result.drifted) {
|
|
43995
|
+
this.logger.info("[atbash] memory drift detected \u2014 refreshing local file", { id: active.id });
|
|
43996
|
+
await this.writeMemoryAtomic(active.content);
|
|
43997
|
+
}
|
|
43998
|
+
const onDisk = await fs3.readFile(this.memoryFilePath, "utf8");
|
|
43999
|
+
if (onDisk !== active.content) {
|
|
44000
|
+
return this.unverifiedRead("Local memory bytes do not match the active chain version.");
|
|
44001
|
+
}
|
|
44002
|
+
} catch (err) {
|
|
44003
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
44004
|
+
this.logger.warn("[atbash] managed memory file could not be verified", { error: msg });
|
|
44005
|
+
return this.unverifiedRead(`Managed memory file could not be verified: ${msg}`);
|
|
44006
|
+
}
|
|
44007
|
+
await this.pointerStore.set(this.agentPubkeyHex, result.pointer);
|
|
43774
44008
|
return { allow: true, audited: true };
|
|
43775
44009
|
}
|
|
44010
|
+
/** Complete queued audit-log writes before a host shuts down or removes its workspace. */
|
|
44011
|
+
async flushLogs() {
|
|
44012
|
+
await this.logger.flush();
|
|
44013
|
+
}
|
|
44014
|
+
unverifiedRead(reason) {
|
|
44015
|
+
if (!this.enforce) return null;
|
|
44016
|
+
return { block: true, blockReason: reason, allow: false, reason, audited: true };
|
|
44017
|
+
}
|
|
43776
44018
|
async writeMemoryAtomic(content) {
|
|
43777
44019
|
await fs3.mkdir(path6.dirname(this.memoryFilePath), { recursive: true });
|
|
43778
44020
|
const tmp = `${this.memoryFilePath}.${process.pid}.tmp`;
|