@astrasyncai/verification-gateway 3.11.0 → 3.12.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.
Files changed (103) hide show
  1. package/dist/adapter-interface/interface.d.mts +2 -2
  2. package/dist/adapter-interface/interface.d.ts +2 -2
  3. package/dist/adapters/express.d.mts +2 -2
  4. package/dist/adapters/express.d.ts +2 -2
  5. package/dist/adapters/express.js +22 -3
  6. package/dist/adapters/express.js.map +1 -1
  7. package/dist/adapters/express.mjs +22 -3
  8. package/dist/adapters/express.mjs.map +1 -1
  9. package/dist/adapters/mcp.d.mts +1 -1
  10. package/dist/adapters/mcp.d.ts +1 -1
  11. package/dist/adapters/mcp.js +22 -3
  12. package/dist/adapters/mcp.js.map +1 -1
  13. package/dist/adapters/mcp.mjs +22 -3
  14. package/dist/adapters/mcp.mjs.map +1 -1
  15. package/dist/adapters/nextjs.d.mts +2 -2
  16. package/dist/adapters/nextjs.d.ts +2 -2
  17. package/dist/adapters/nextjs.js +22 -3
  18. package/dist/adapters/nextjs.js.map +1 -1
  19. package/dist/adapters/nextjs.mjs +22 -3
  20. package/dist/adapters/nextjs.mjs.map +1 -1
  21. package/dist/adapters/sdk.d.mts +2 -2
  22. package/dist/adapters/sdk.d.ts +2 -2
  23. package/dist/adapters/sdk.js +22 -3
  24. package/dist/adapters/sdk.js.map +1 -1
  25. package/dist/adapters/sdk.mjs +22 -3
  26. package/dist/adapters/sdk.mjs.map +1 -1
  27. package/dist/agent/index.d.mts +2 -2
  28. package/dist/agent/index.d.ts +2 -2
  29. package/dist/bin/astrasync-claude-hook.js +84 -21
  30. package/dist/bin/astrasync-codex-hook.js +84 -21
  31. package/dist/bin/astrasync-guard.js +84 -21
  32. package/dist/bin/astrasync.js +85 -21
  33. package/dist/browser/background.js +84 -21
  34. package/dist/browser/background.js.map +1 -1
  35. package/dist/browser/background.mjs +84 -21
  36. package/dist/browser/background.mjs.map +1 -1
  37. package/dist/browser/browser-adapter.d.mts +2 -2
  38. package/dist/browser/browser-adapter.d.ts +2 -2
  39. package/dist/claude-code/claude-code-adapter.d.mts +2 -2
  40. package/dist/claude-code/claude-code-adapter.d.ts +2 -2
  41. package/dist/cli/index.d.mts +2 -2
  42. package/dist/cli/index.d.ts +2 -2
  43. package/dist/codex/index.d.mts +2 -2
  44. package/dist/codex/index.d.ts +2 -2
  45. package/dist/codex/index.js +84 -21
  46. package/dist/codex/index.js.map +1 -1
  47. package/dist/codex/index.mjs +84 -21
  48. package/dist/codex/index.mjs.map +1 -1
  49. package/dist/cursor/cursor-adapter.d.mts +2 -2
  50. package/dist/cursor/cursor-adapter.d.ts +2 -2
  51. package/dist/cursor/extension.d.mts +2 -2
  52. package/dist/cursor/extension.d.ts +2 -2
  53. package/dist/cursor/extension.js +84 -21
  54. package/dist/cursor/extension.js.map +1 -1
  55. package/dist/cursor/extension.mjs +84 -21
  56. package/dist/cursor/extension.mjs.map +1 -1
  57. package/dist/edge-config.d.mts +1 -1
  58. package/dist/edge-config.d.ts +1 -1
  59. package/dist/{express-DCXa1oqj.d.mts → express-B39o89gf.d.mts} +1 -1
  60. package/dist/{express-ClNtDhhc.d.ts → express-BuxKNO_q.d.ts} +1 -1
  61. package/dist/gateway/gateway.d.mts +2 -2
  62. package/dist/gateway/gateway.d.ts +2 -2
  63. package/dist/gateway/gateway.js +84 -21
  64. package/dist/gateway/gateway.js.map +1 -1
  65. package/dist/gateway/gateway.mjs +84 -21
  66. package/dist/gateway/gateway.mjs.map +1 -1
  67. package/dist/git-trigger/git-hooks.d.mts +2 -2
  68. package/dist/git-trigger/git-hooks.d.ts +2 -2
  69. package/dist/{index-CfHurO1P.d.ts → index-BG62SXso.d.ts} +1 -1
  70. package/dist/{index-D2ztkumS.d.ts → index-CLHdqs2M.d.ts} +1 -1
  71. package/dist/{index-Dr_XiN65.d.mts → index-CqydB4ks.d.mts} +1 -1
  72. package/dist/{index-BrNKDmiR.d.mts → index-DME3w3lf.d.mts} +1 -1
  73. package/dist/index.d.mts +9 -9
  74. package/dist/index.d.ts +9 -9
  75. package/dist/index.js +42 -3
  76. package/dist/index.js.map +1 -1
  77. package/dist/index.mjs +41 -3
  78. package/dist/index.mjs.map +1 -1
  79. package/dist/local-evaluator/evaluator.d.mts +11 -2
  80. package/dist/local-evaluator/evaluator.d.ts +11 -2
  81. package/dist/local-evaluator/evaluator.js +20 -9
  82. package/dist/local-evaluator/evaluator.js.map +1 -1
  83. package/dist/local-evaluator/evaluator.mjs +20 -9
  84. package/dist/local-evaluator/evaluator.mjs.map +1 -1
  85. package/dist/{nextjs-DcQIk6Zj.d.mts → nextjs-D74W7wt-.d.mts} +1 -1
  86. package/dist/{nextjs-CEBVBsdi.d.ts → nextjs-Ddlw_J1J.d.ts} +1 -1
  87. package/dist/{sdk-CZkbSOeO.d.mts → sdk-Bn6bj7kt.d.mts} +1 -1
  88. package/dist/{sdk-H0JCLW2y.d.ts → sdk-CT7gdkfR.d.ts} +1 -1
  89. package/dist/transport/index.d.mts +2 -2
  90. package/dist/transport/index.d.ts +2 -2
  91. package/dist/{types-sy3tq8lp.d.mts → types-BntuzEEn.d.mts} +103 -1
  92. package/dist/{types-BV6abBnF.d.mts → types-DHu7m9HI.d.ts} +7 -2
  93. package/dist/{types-C23_uaY8.d.ts → types-Djw4GLtz.d.mts} +7 -2
  94. package/dist/{types-9tns4wyl.d.ts → types-SDu4Llbe.d.ts} +103 -1
  95. package/dist/ui/index.d.mts +1 -1
  96. package/dist/ui/index.d.ts +1 -1
  97. package/dist/verify.d.mts +13 -2
  98. package/dist/verify.d.ts +13 -2
  99. package/dist/verify.js +42 -3
  100. package/dist/verify.js.map +1 -1
  101. package/dist/verify.mjs +41 -3
  102. package/dist/verify.mjs.map +1 -1
  103. package/package.json +1 -1
@@ -4628,8 +4628,9 @@ function extractBacktickSubstitutions(text, collect) {
4628
4628
  return kept.join(" ");
4629
4629
  }
4630
4630
  function basename(token) {
4631
- const slash = token.lastIndexOf("/");
4632
- return slash === -1 ? token : token.slice(slash + 1);
4631
+ const slash = Math.max(token.lastIndexOf("/"), token.lastIndexOf("\\"));
4632
+ const name = slash === -1 ? token : token.slice(slash + 1);
4633
+ return name.replace(/\.(exe|cmd|bat|ps1)$/i, "");
4633
4634
  }
4634
4635
  function matchSegment(segment, list) {
4635
4636
  const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
@@ -4795,7 +4796,9 @@ var init_evaluator = __esm({
4795
4796
  }
4796
4797
  return {
4797
4798
  recommendation: "ALLOW",
4798
- reason: "All PDLSS checks passed",
4799
+ // "evaluated": dimensions absent from the policy are skipped, not
4800
+ // failed — evaluatedDimensions below records which ones ran.
4801
+ reason: "All evaluated PDLSS checks passed",
4799
4802
  evaluatedDimensions: {
4800
4803
  purpose: true,
4801
4804
  scope: !!this.policy.scope,
@@ -4894,7 +4897,7 @@ var init_evaluator = __esm({
4894
4897
  }
4895
4898
  }
4896
4899
  if ((context.purpose === "file.read" || context.purpose === "file.write" || context.purpose === "file.delete") && context.target) {
4897
- const targetLower = context.target.toLowerCase();
4900
+ const targetLower = context.target.toLowerCase().replace(/\\/g, "/");
4898
4901
  for (const sensitivePath of SENSITIVE_PATHS) {
4899
4902
  if (targetLower.includes(sensitivePath.toLowerCase())) {
4900
4903
  score = Math.max(score, 50);
@@ -4923,12 +4926,20 @@ var init_evaluator = __esm({
4923
4926
  matchesAnyPattern(value, patterns) {
4924
4927
  return patterns.some((pattern) => this.matchGlob(value, pattern));
4925
4928
  }
4926
- /**
4927
- * Simple glob pattern matching.
4928
- * Supports * (matches any characters including / and spaces) and ? (single char).
4929
- * Uses the same approach as the backend's matchGlobPattern.
4930
- */
4929
+ homePrefix() {
4930
+ if (this.cachedHomePrefix !== void 0) return this.cachedHomePrefix;
4931
+ const env = typeof process !== "undefined" ? process.env : void 0;
4932
+ const home = env?.HOME || env?.USERPROFILE;
4933
+ this.cachedHomePrefix = home ? home.replace(/\\/g, "/") : null;
4934
+ return this.cachedHomePrefix;
4935
+ }
4931
4936
  matchGlob(value, pattern) {
4937
+ value = value.replace(/\\/g, "/");
4938
+ pattern = pattern.replace(/\\/g, "/");
4939
+ if (pattern === "~" || pattern.startsWith("~/")) {
4940
+ const home = this.homePrefix();
4941
+ if (home) pattern = home + pattern.slice(1);
4942
+ }
4932
4943
  if (pattern === value) return true;
4933
4944
  const starCount = (pattern.match(/\*/g) ?? []).length;
4934
4945
  if (starCount > 8) {
@@ -4989,7 +5000,7 @@ var SDK_VERSION;
4989
5000
  var init_version = __esm({
4990
5001
  "src/version.ts"() {
4991
5002
  "use strict";
4992
- SDK_VERSION = "3.11.0";
5003
+ SDK_VERSION = "3.12.0";
4993
5004
  }
4994
5005
  });
4995
5006
 
@@ -5146,6 +5157,7 @@ function createGuidanceResponse(_config, reason, options = {}) {
5146
5157
  }
5147
5158
  ] : void 0,
5148
5159
  correlationId: options.correlationId,
5160
+ attemptId: options.attemptId,
5149
5161
  verifiedAt: /* @__PURE__ */ new Date()
5150
5162
  };
5151
5163
  }
@@ -5175,6 +5187,8 @@ async function callVerifyAccessAPI(config, request2) {
5175
5187
  body.runtimeChallengeOptions = requestData.runtimeChallengeOptions;
5176
5188
  if (requestData.invocationProtocol) body.invocationProtocol = requestData.invocationProtocol;
5177
5189
  if (requestData.commerceArtifacts) body.commerceArtifacts = requestData.commerceArtifacts;
5190
+ if (requestData.attemptId) body.attemptId = requestData.attemptId;
5191
+ if (requestData.considerationSet) body.considerationSet = requestData.considerationSet;
5178
5192
  body.sdkVersion = SDK_VERSION;
5179
5193
  if (requestData.callerMetadata || requestData.clientIp || requestData.userAgent) {
5180
5194
  const meta = {
@@ -5202,6 +5216,9 @@ async function callVerifyAccessAPI(config, request2) {
5202
5216
  if (response.status === 410) {
5203
5217
  return {
5204
5218
  success: true,
5219
+ // 3.12.0 (Trust Record): keep the server-echoed attempt handle even on
5220
+ // the synthesised 410 deny shape (echoed on every branch).
5221
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
5205
5222
  access: {
5206
5223
  allowed: false,
5207
5224
  accessLevel: "none",
@@ -5220,7 +5237,8 @@ async function callVerifyAccessAPI(config, request2) {
5220
5237
  return {
5221
5238
  success: false,
5222
5239
  error: data.message || data.error || `API returned ${response.status}`,
5223
- correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0
5240
+ correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
5241
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
5224
5242
  };
5225
5243
  }
5226
5244
  return data;
@@ -5254,7 +5272,13 @@ async function verify(config, request2, options) {
5254
5272
  if (mergedConfig.debug) {
5255
5273
  console.log("[VerificationGateway] Returning cached result");
5256
5274
  }
5257
- return cached;
5275
+ const hit = { ...cached };
5276
+ if (request2.attemptId) {
5277
+ hit.attemptId = request2.attemptId;
5278
+ } else {
5279
+ delete hit.attemptId;
5280
+ }
5281
+ return hit;
5258
5282
  }
5259
5283
  }
5260
5284
  const enrichedRequest = { ...request2 };
@@ -5274,6 +5298,7 @@ async function verify(config, request2, options) {
5274
5298
  return createGuidanceResponse(mergedConfig, apiResponse.error, {
5275
5299
  source: "api_error",
5276
5300
  correlationId: apiResponse.correlationId,
5301
+ attemptId: apiResponse.attemptId,
5277
5302
  urls
5278
5303
  });
5279
5304
  }
@@ -5309,6 +5334,9 @@ async function verify(config, request2, options) {
5309
5334
  // Anonymous traffic has no session → correlationId is the per-attempt
5310
5335
  // linking key (the sessionId-equivalent for anonymous callers).
5311
5336
  correlationId: apiResponse.correlationId,
5337
+ // 3.12.0 (Trust Record): server echoes the attempt-chain handle on
5338
+ // every branch — denials included (the funnel outlives one deny).
5339
+ attemptId: apiResponse.attemptId,
5312
5340
  recommendation: apiResponse.recommendation,
5313
5341
  recommendationReasons: apiResponse.recommendationReasons,
5314
5342
  stepUpApproval: apiResponse.stepUpApproval,
@@ -5360,6 +5388,8 @@ async function verify(config, request2, options) {
5360
5388
  // v2.3.10 (defect #34, round-4): anonymous responses surface correlationId
5361
5389
  // (no session row exists for unverified callers).
5362
5390
  correlationId: apiResponse.correlationId,
5391
+ // 3.12.0 (Trust Record): server-echoed attempt-chain handle.
5392
+ attemptId: apiResponse.attemptId,
5363
5393
  runtimeChallenge: apiResponse.runtimeChallenge,
5364
5394
  tokenGuidance: apiResponse.tokenGuidance,
5365
5395
  recommendation: apiResponse.recommendation,
@@ -5938,6 +5968,7 @@ var init_src = __esm({
5938
5968
  init_edge_config();
5939
5969
  init_platform_signatures();
5940
5970
  init_verify();
5971
+ init_verify();
5941
5972
  init_metadata_capture();
5942
5973
  init_version();
5943
5974
  }
@@ -6231,13 +6262,29 @@ var init_trace_logger = __esm({
6231
6262
  */
6232
6263
  logEvaluation(context, decision) {
6233
6264
  if (!this.enabled) return;
6234
- this.write({
6235
- id: this.generateId(),
6236
- timestamp: /* @__PURE__ */ new Date(),
6237
- type: "evaluation",
6238
- context: redactDeep(context),
6239
- decision: redactDeep(decision)
6240
- });
6265
+ let event;
6266
+ try {
6267
+ event = {
6268
+ id: this.generateId(),
6269
+ timestamp: /* @__PURE__ */ new Date(),
6270
+ type: "evaluation",
6271
+ context: redactDeep(context),
6272
+ decision: redactDeep(decision)
6273
+ };
6274
+ } catch (err) {
6275
+ event = {
6276
+ id: this.generateId(),
6277
+ timestamp: /* @__PURE__ */ new Date(),
6278
+ type: "error",
6279
+ metadata: {
6280
+ error: `trace redaction failed: ${err instanceof Error ? err.message : String(err)}`,
6281
+ recommendation: decision.recommendation,
6282
+ purpose: context.purpose,
6283
+ action: context.action
6284
+ }
6285
+ };
6286
+ }
6287
+ this.write(event);
6241
6288
  }
6242
6289
  /**
6243
6290
  * Log a tool call the guard saw but did not govern. One line per skip
@@ -6279,17 +6326,34 @@ var init_trace_logger = __esm({
6279
6326
  });
6280
6327
  }
6281
6328
  write(event) {
6329
+ let line;
6330
+ try {
6331
+ line = JSON.stringify(event) + "\n";
6332
+ } catch {
6333
+ return;
6334
+ }
6282
6335
  try {
6283
6336
  const filePath = this.getCurrentFilePath();
6284
- const line = JSON.stringify(event) + "\n";
6285
6337
  if ((0, import_fs3.existsSync)(filePath)) {
6286
6338
  const stats = (0, import_fs3.statSync)(filePath);
6287
6339
  if (stats.size > MAX_FILE_SIZE) {
6288
6340
  this.rotate();
6289
6341
  }
6290
6342
  }
6291
- (0, import_fs3.appendFileSync)(filePath, line);
6343
+ for (let attempt = 0; attempt < 3; attempt++) {
6344
+ try {
6345
+ (0, import_fs3.appendFileSync)(filePath, line);
6346
+ return;
6347
+ } catch {
6348
+ }
6349
+ }
6350
+ const fallbackPath = filePath.replace(/\.jsonl$/, `-r${process.pid}.jsonl`);
6351
+ (0, import_fs3.appendFileSync)(fallbackPath, line);
6292
6352
  } catch {
6353
+ try {
6354
+ console.error(`[AstraSync TraceLogger] failed to persist trace line: ${line.trim()}`);
6355
+ } catch {
6356
+ }
6293
6357
  }
6294
6358
  }
6295
6359
  getCurrentFilePath() {
@@ -387,8 +387,9 @@ function extractBacktickSubstitutions(text, collect) {
387
387
  return kept.join(" ");
388
388
  }
389
389
  function basename(token) {
390
- const slash = token.lastIndexOf("/");
391
- return slash === -1 ? token : token.slice(slash + 1);
390
+ const slash = Math.max(token.lastIndexOf("/"), token.lastIndexOf("\\"));
391
+ const name = slash === -1 ? token : token.slice(slash + 1);
392
+ return name.replace(/\.(exe|cmd|bat|ps1)$/i, "");
392
393
  }
393
394
  function matchSegment(segment, list) {
394
395
  const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
@@ -521,7 +522,9 @@ var LocalEvaluator = class {
521
522
  }
522
523
  return {
523
524
  recommendation: "ALLOW",
524
- reason: "All PDLSS checks passed",
525
+ // "evaluated": dimensions absent from the policy are skipped, not
526
+ // failed — evaluatedDimensions below records which ones ran.
527
+ reason: "All evaluated PDLSS checks passed",
525
528
  evaluatedDimensions: {
526
529
  purpose: true,
527
530
  scope: !!this.policy.scope,
@@ -620,7 +623,7 @@ var LocalEvaluator = class {
620
623
  }
621
624
  }
622
625
  if ((context.purpose === "file.read" || context.purpose === "file.write" || context.purpose === "file.delete") && context.target) {
623
- const targetLower = context.target.toLowerCase();
626
+ const targetLower = context.target.toLowerCase().replace(/\\/g, "/");
624
627
  for (const sensitivePath of SENSITIVE_PATHS) {
625
628
  if (targetLower.includes(sensitivePath.toLowerCase())) {
626
629
  score = Math.max(score, 50);
@@ -649,12 +652,20 @@ var LocalEvaluator = class {
649
652
  matchesAnyPattern(value, patterns) {
650
653
  return patterns.some((pattern) => this.matchGlob(value, pattern));
651
654
  }
652
- /**
653
- * Simple glob pattern matching.
654
- * Supports * (matches any characters including / and spaces) and ? (single char).
655
- * Uses the same approach as the backend's matchGlobPattern.
656
- */
655
+ homePrefix() {
656
+ if (this.cachedHomePrefix !== void 0) return this.cachedHomePrefix;
657
+ const env = typeof process !== "undefined" ? process.env : void 0;
658
+ const home = env?.HOME || env?.USERPROFILE;
659
+ this.cachedHomePrefix = home ? home.replace(/\\/g, "/") : null;
660
+ return this.cachedHomePrefix;
661
+ }
657
662
  matchGlob(value, pattern) {
663
+ value = value.replace(/\\/g, "/");
664
+ pattern = pattern.replace(/\\/g, "/");
665
+ if (pattern === "~" || pattern.startsWith("~/")) {
666
+ const home = this.homePrefix();
667
+ if (home) pattern = home + pattern.slice(1);
668
+ }
658
669
  if (pattern === value) return true;
659
670
  const starCount = (pattern.match(/\*/g) ?? []).length;
660
671
  if (starCount > 8) {
@@ -3927,7 +3938,7 @@ function getTrustLevel(score) {
3927
3938
  }
3928
3939
 
3929
3940
  // src/version.ts
3930
- var SDK_VERSION = "3.11.0";
3941
+ var SDK_VERSION = "3.12.0";
3931
3942
 
3932
3943
  // src/well-known.ts
3933
3944
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -4094,6 +4105,7 @@ function createGuidanceResponse(_config, reason, options = {}) {
4094
4105
  }
4095
4106
  ] : void 0,
4096
4107
  correlationId: options.correlationId,
4108
+ attemptId: options.attemptId,
4097
4109
  verifiedAt: /* @__PURE__ */ new Date()
4098
4110
  };
4099
4111
  }
@@ -4123,6 +4135,8 @@ async function callVerifyAccessAPI(config, request) {
4123
4135
  body.runtimeChallengeOptions = requestData.runtimeChallengeOptions;
4124
4136
  if (requestData.invocationProtocol) body.invocationProtocol = requestData.invocationProtocol;
4125
4137
  if (requestData.commerceArtifacts) body.commerceArtifacts = requestData.commerceArtifacts;
4138
+ if (requestData.attemptId) body.attemptId = requestData.attemptId;
4139
+ if (requestData.considerationSet) body.considerationSet = requestData.considerationSet;
4126
4140
  body.sdkVersion = SDK_VERSION;
4127
4141
  if (requestData.callerMetadata || requestData.clientIp || requestData.userAgent) {
4128
4142
  const meta = {
@@ -4150,6 +4164,9 @@ async function callVerifyAccessAPI(config, request) {
4150
4164
  if (response.status === 410) {
4151
4165
  return {
4152
4166
  success: true,
4167
+ // 3.12.0 (Trust Record): keep the server-echoed attempt handle even on
4168
+ // the synthesised 410 deny shape (echoed on every branch).
4169
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
4153
4170
  access: {
4154
4171
  allowed: false,
4155
4172
  accessLevel: "none",
@@ -4168,7 +4185,8 @@ async function callVerifyAccessAPI(config, request) {
4168
4185
  return {
4169
4186
  success: false,
4170
4187
  error: data.message || data.error || `API returned ${response.status}`,
4171
- correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0
4188
+ correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
4189
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
4172
4190
  };
4173
4191
  }
4174
4192
  return data;
@@ -4202,7 +4220,13 @@ async function verify(config, request, options) {
4202
4220
  if (mergedConfig.debug) {
4203
4221
  console.log("[VerificationGateway] Returning cached result");
4204
4222
  }
4205
- return cached;
4223
+ const hit = { ...cached };
4224
+ if (request.attemptId) {
4225
+ hit.attemptId = request.attemptId;
4226
+ } else {
4227
+ delete hit.attemptId;
4228
+ }
4229
+ return hit;
4206
4230
  }
4207
4231
  }
4208
4232
  const enrichedRequest = { ...request };
@@ -4222,6 +4246,7 @@ async function verify(config, request, options) {
4222
4246
  return createGuidanceResponse(mergedConfig, apiResponse.error, {
4223
4247
  source: "api_error",
4224
4248
  correlationId: apiResponse.correlationId,
4249
+ attemptId: apiResponse.attemptId,
4225
4250
  urls
4226
4251
  });
4227
4252
  }
@@ -4257,6 +4282,9 @@ async function verify(config, request, options) {
4257
4282
  // Anonymous traffic has no session → correlationId is the per-attempt
4258
4283
  // linking key (the sessionId-equivalent for anonymous callers).
4259
4284
  correlationId: apiResponse.correlationId,
4285
+ // 3.12.0 (Trust Record): server echoes the attempt-chain handle on
4286
+ // every branch — denials included (the funnel outlives one deny).
4287
+ attemptId: apiResponse.attemptId,
4260
4288
  recommendation: apiResponse.recommendation,
4261
4289
  recommendationReasons: apiResponse.recommendationReasons,
4262
4290
  stepUpApproval: apiResponse.stepUpApproval,
@@ -4308,6 +4336,8 @@ async function verify(config, request, options) {
4308
4336
  // v2.3.10 (defect #34, round-4): anonymous responses surface correlationId
4309
4337
  // (no session row exists for unverified callers).
4310
4338
  correlationId: apiResponse.correlationId,
4339
+ // 3.12.0 (Trust Record): server-echoed attempt-chain handle.
4340
+ attemptId: apiResponse.attemptId,
4311
4341
  runtimeChallenge: apiResponse.runtimeChallenge,
4312
4342
  tokenGuidance: apiResponse.tokenGuidance,
4313
4343
  recommendation: apiResponse.recommendation,
@@ -4691,13 +4721,29 @@ var TraceLogger = class {
4691
4721
  */
4692
4722
  logEvaluation(context, decision) {
4693
4723
  if (!this.enabled) return;
4694
- this.write({
4695
- id: this.generateId(),
4696
- timestamp: /* @__PURE__ */ new Date(),
4697
- type: "evaluation",
4698
- context: redactDeep(context),
4699
- decision: redactDeep(decision)
4700
- });
4724
+ let event;
4725
+ try {
4726
+ event = {
4727
+ id: this.generateId(),
4728
+ timestamp: /* @__PURE__ */ new Date(),
4729
+ type: "evaluation",
4730
+ context: redactDeep(context),
4731
+ decision: redactDeep(decision)
4732
+ };
4733
+ } catch (err) {
4734
+ event = {
4735
+ id: this.generateId(),
4736
+ timestamp: /* @__PURE__ */ new Date(),
4737
+ type: "error",
4738
+ metadata: {
4739
+ error: `trace redaction failed: ${err instanceof Error ? err.message : String(err)}`,
4740
+ recommendation: decision.recommendation,
4741
+ purpose: context.purpose,
4742
+ action: context.action
4743
+ }
4744
+ };
4745
+ }
4746
+ this.write(event);
4701
4747
  }
4702
4748
  /**
4703
4749
  * Log a tool call the guard saw but did not govern. One line per skip
@@ -4739,17 +4785,34 @@ var TraceLogger = class {
4739
4785
  });
4740
4786
  }
4741
4787
  write(event) {
4788
+ let line;
4789
+ try {
4790
+ line = JSON.stringify(event) + "\n";
4791
+ } catch {
4792
+ return;
4793
+ }
4742
4794
  try {
4743
4795
  const filePath = this.getCurrentFilePath();
4744
- const line = JSON.stringify(event) + "\n";
4745
4796
  if ((0, import_fs3.existsSync)(filePath)) {
4746
4797
  const stats = (0, import_fs3.statSync)(filePath);
4747
4798
  if (stats.size > MAX_FILE_SIZE) {
4748
4799
  this.rotate();
4749
4800
  }
4750
4801
  }
4751
- (0, import_fs3.appendFileSync)(filePath, line);
4802
+ for (let attempt = 0; attempt < 3; attempt++) {
4803
+ try {
4804
+ (0, import_fs3.appendFileSync)(filePath, line);
4805
+ return;
4806
+ } catch {
4807
+ }
4808
+ }
4809
+ const fallbackPath = filePath.replace(/\.jsonl$/, `-r${process.pid}.jsonl`);
4810
+ (0, import_fs3.appendFileSync)(fallbackPath, line);
4752
4811
  } catch {
4812
+ try {
4813
+ console.error(`[AstraSync TraceLogger] failed to persist trace line: ${line.trim()}`);
4814
+ } catch {
4815
+ }
4753
4816
  }
4754
4817
  }
4755
4818
  getCurrentFilePath() {