agent-inspect 6.25.0 → 6.26.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.
@@ -273,7 +273,7 @@ interface TraceContractControlRules {
273
273
  }
274
274
 
275
275
  /**
276
- * Retry / side-effect safety checks for TraceContract (6.23).
276
+ * Retry / side-effect safety checks for TraceContract (6.23+; corrected in 6.25.1).
277
277
  *
278
278
  * Uses explicit attempt identity from session workflow metadata.
279
279
  * AgentInspect evaluates; it does not perform retries.
@@ -284,7 +284,8 @@ interface TraceContractControlRules {
284
284
  interface TraceContractRetryRules {
285
285
  /**
286
286
  * Maximum attempts per `operationId` (or `attemptOf` grouping).
287
- * Counts distinct `attemptId` values when present, else finished tool/LLM events.
287
+ * Prefers distinct `attemptId` values, else validated contiguous `attemptNumber`s,
288
+ * else finished tool/LLM events in the operation.
288
289
  */
289
290
  maxAttempts?: number;
290
291
  /**
@@ -292,7 +293,8 @@ interface TraceContractRetryRules {
292
293
  */
293
294
  requireTerminalResult?: boolean;
294
295
  /**
295
- * Fail when `fallbackOf` is set without a prior error attempt for the referenced operation.
296
+ * Fail when `fallbackOf` is set without an earlier failure for the referenced operation
297
+ * that chronologically precedes the fallback event.
296
298
  */
297
299
  fallbackOnlyAfterFailure?: boolean;
298
300
  /**
@@ -301,11 +303,14 @@ interface TraceContractRetryRules {
301
303
  */
302
304
  nonIdempotentTools?: string[];
303
305
  /**
304
- * When true, retries require `idempotencyKey` or `attributes.noSideEffect === true`.
306
+ * When true, genuine retries (including error→success) require `idempotencyKey`
307
+ * or `attributes.noSideEffect === true` / `sideEffect === false`.
308
+ * A client key alone is not proof of exactly-once mutation.
305
309
  */
306
310
  requireIdempotencyEvidenceForRetry?: boolean;
307
311
  /**
308
- * When true, recovered success paths must still retain at least one error attempt event.
312
+ * When true, recovered success paths must retain an earlier error attempt in the
313
+ * same operation/retry chain (not merely coexistence of ok+error).
309
314
  */
310
315
  requireRecoveredFailureVisible?: boolean;
311
316
  }
@@ -273,7 +273,7 @@ interface TraceContractControlRules {
273
273
  }
274
274
 
275
275
  /**
276
- * Retry / side-effect safety checks for TraceContract (6.23).
276
+ * Retry / side-effect safety checks for TraceContract (6.23+; corrected in 6.25.1).
277
277
  *
278
278
  * Uses explicit attempt identity from session workflow metadata.
279
279
  * AgentInspect evaluates; it does not perform retries.
@@ -284,7 +284,8 @@ interface TraceContractControlRules {
284
284
  interface TraceContractRetryRules {
285
285
  /**
286
286
  * Maximum attempts per `operationId` (or `attemptOf` grouping).
287
- * Counts distinct `attemptId` values when present, else finished tool/LLM events.
287
+ * Prefers distinct `attemptId` values, else validated contiguous `attemptNumber`s,
288
+ * else finished tool/LLM events in the operation.
288
289
  */
289
290
  maxAttempts?: number;
290
291
  /**
@@ -292,7 +293,8 @@ interface TraceContractRetryRules {
292
293
  */
293
294
  requireTerminalResult?: boolean;
294
295
  /**
295
- * Fail when `fallbackOf` is set without a prior error attempt for the referenced operation.
296
+ * Fail when `fallbackOf` is set without an earlier failure for the referenced operation
297
+ * that chronologically precedes the fallback event.
296
298
  */
297
299
  fallbackOnlyAfterFailure?: boolean;
298
300
  /**
@@ -301,11 +303,14 @@ interface TraceContractRetryRules {
301
303
  */
302
304
  nonIdempotentTools?: string[];
303
305
  /**
304
- * When true, retries require `idempotencyKey` or `attributes.noSideEffect === true`.
306
+ * When true, genuine retries (including error→success) require `idempotencyKey`
307
+ * or `attributes.noSideEffect === true` / `sideEffect === false`.
308
+ * A client key alone is not proof of exactly-once mutation.
305
309
  */
306
310
  requireIdempotencyEvidenceForRetry?: boolean;
307
311
  /**
308
- * When true, recovered success paths must still retain at least one error attempt event.
312
+ * When true, recovered success paths must retain an earlier error attempt in the
313
+ * same operation/retry chain (not merely coexistence of ok+error).
309
314
  */
310
315
  requireRecoveredFailureVisible?: boolean;
311
316
  }
@@ -1,4 +1,4 @@
1
- export { buildTraceFacts, createBaselineRegressionRule, createDecisionRule, createGuardrailRule, createLlmUsageRule, createMaxStepDurationRule, createObservedOutcomeRule, createRequireCompletedRule, createRetrievalRule, createRunDepthRule, createRunDurationRule, createRunEventCountRule, createRunStatusRule, createSafetyOversizedAttributeRule, createSafetyRawContentRule, createSafetyRedactionRule, createSafetySecretPatternRule, createStallDetectionRule, createStructureCycleRule, createStructureIncompleteRule, createStructureOrphanRule, createStructureParallelWidthRule, createStructureRelationshipRule, createToolFailureRule, createToolOrderingRule, createToolUsageRule, defineTraceContract, deriveFailureFacts, deriveRelationshipFacts, evaluateToolArgumentValue, evaluateTraceContract, evaluateTraceContractRead, explainTraceContract, extractToolArgumentPayload, lintTraceContract, projectLogicalEvents, resolveCanonicalToolName, resolveJsonPointer, resolveTraceContractScope, runTraceChecks, summarizeSemanticParity, workflowMetadataForRun } from './chunk-VMDCDWBE.mjs';
1
+ export { buildTraceFacts, createBaselineRegressionRule, createDecisionRule, createGuardrailRule, createLlmUsageRule, createMaxStepDurationRule, createObservedOutcomeRule, createRequireCompletedRule, createRetrievalRule, createRunDepthRule, createRunDurationRule, createRunEventCountRule, createRunStatusRule, createSafetyOversizedAttributeRule, createSafetyRawContentRule, createSafetyRedactionRule, createSafetySecretPatternRule, createStallDetectionRule, createStructureCycleRule, createStructureIncompleteRule, createStructureOrphanRule, createStructureParallelWidthRule, createStructureRelationshipRule, createToolFailureRule, createToolOrderingRule, createToolUsageRule, defineTraceContract, deriveFailureFacts, deriveRelationshipFacts, evaluateToolArgumentValue, evaluateTraceContract, evaluateTraceContractRead, explainTraceContract, extractToolArgumentPayload, lintTraceContract, projectLogicalEvents, resolveCanonicalToolName, resolveJsonPointer, resolveTraceContractScope, runTraceChecks, summarizeSemanticParity, workflowMetadataForRun } from './chunk-LHSXRIYU.mjs';
2
2
  import './chunk-R4SSYU6D.mjs';
3
3
  import './chunk-U25RHRSU.mjs';
4
4
  import './chunk-VU6O5QAH.mjs';
@@ -3473,6 +3473,99 @@ function eventEvidence2(event) {
3473
3473
  status: event.status
3474
3474
  };
3475
3475
  }
3476
+ function eventTime(event) {
3477
+ return event.startedAt ?? event.timestamp ?? "";
3478
+ }
3479
+ function sortByTime(events) {
3480
+ return [...events].sort((a, b) => eventTime(a).localeCompare(eventTime(b)));
3481
+ }
3482
+ function attemptNumberOf(event) {
3483
+ const workflow = workflowFor(event);
3484
+ const value = workflow.attemptNumber ?? workflow.attempt;
3485
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3486
+ }
3487
+ function attemptIdOf(event) {
3488
+ const value = workflowFor(event).attemptId;
3489
+ return typeof value === "string" && value.trim() !== "" ? value : void 0;
3490
+ }
3491
+ function countOperationAttempts(members) {
3492
+ const attemptIds = new Set(
3493
+ members.map((event) => attemptIdOf(event)).filter((value) => value !== void 0)
3494
+ );
3495
+ if (attemptIds.size > 0) {
3496
+ return attemptIds.size;
3497
+ }
3498
+ const numbers = members.map((event) => attemptNumberOf(event)).filter((value) => value !== void 0).sort((a, b) => a - b);
3499
+ if (numbers.length > 0) {
3500
+ const unique = [...new Set(numbers)];
3501
+ const min = unique[0];
3502
+ const max = unique[unique.length - 1];
3503
+ const contiguous = unique.length === max - min + 1 && unique.every((value, index) => value === min + index);
3504
+ if (contiguous) {
3505
+ return unique.length;
3506
+ }
3507
+ return unique.length;
3508
+ }
3509
+ return members.filter((event) => event.kind === "TOOL" || event.kind === "LLM").length;
3510
+ }
3511
+ function classifyRetryAttempt(event, priorOrdered, allMembers) {
3512
+ const workflow = workflowFor(event);
3513
+ const attemptNumber = attemptNumberOf(event);
3514
+ const attemptId = attemptIdOf(event);
3515
+ const retryOf = workflow.retryOf;
3516
+ if (typeof retryOf === "string" && retryOf.trim() !== "") {
3517
+ const target = allMembers.find(
3518
+ (candidate) => candidate.eventId === retryOf || attemptIdOf(candidate) === retryOf || workflowFor(candidate).operationId === retryOf
3519
+ );
3520
+ if (!target) {
3521
+ return { kind: "unknown", reason: "retryOf target missing" };
3522
+ }
3523
+ if (eventTime(target) > eventTime(event)) {
3524
+ return { kind: "unknown", reason: "retryOf target does not precede retry" };
3525
+ }
3526
+ return { kind: "retry" };
3527
+ }
3528
+ if (attemptNumber !== void 0) {
3529
+ if (attemptNumber > 1) {
3530
+ return { kind: "retry" };
3531
+ }
3532
+ if (attemptNumber === 1) {
3533
+ return { kind: "first" };
3534
+ }
3535
+ }
3536
+ if (attemptId !== void 0) {
3537
+ const priorDistinct = priorOrdered.some((candidate) => {
3538
+ const priorId = attemptIdOf(candidate);
3539
+ return priorId !== void 0 && priorId !== attemptId;
3540
+ });
3541
+ if (priorDistinct) {
3542
+ return { kind: "retry" };
3543
+ }
3544
+ if (priorOrdered.length === 0) {
3545
+ return { kind: "first" };
3546
+ }
3547
+ }
3548
+ const priorFinished = priorOrdered.filter(
3549
+ (candidate) => candidate.status === "ok" || candidate.status === "error"
3550
+ );
3551
+ if (priorFinished.length > 0) {
3552
+ return { kind: "retry" };
3553
+ }
3554
+ if (priorOrdered.length === 0) {
3555
+ return { kind: "first" };
3556
+ }
3557
+ return { kind: "unknown", reason: "ambiguous attempt identity" };
3558
+ }
3559
+ function hasDuplicateAttemptIds(members) {
3560
+ const seen = /* @__PURE__ */ new Set();
3561
+ for (const event of members) {
3562
+ const id = attemptIdOf(event);
3563
+ if (!id) continue;
3564
+ if (seen.has(id)) return true;
3565
+ seen.add(id);
3566
+ }
3567
+ return false;
3568
+ }
3476
3569
  function evaluateRetrySafetyRules(events, rules, _runEvidence) {
3477
3570
  const findings = [];
3478
3571
  const byOperation = /* @__PURE__ */ new Map();
@@ -3486,11 +3579,18 @@ function evaluateRetrySafetyRules(events, rules, _runEvidence) {
3486
3579
  }
3487
3580
  if (rules.maxAttempts !== void 0) {
3488
3581
  for (const [operationId, members] of byOperation) {
3489
- const attemptIds = new Set(
3490
- members.map((event) => workflowFor(event).attemptId).filter((value) => typeof value === "string" && value.trim() !== "")
3491
- );
3492
- const attemptNumbers = members.map((event) => workflowFor(event).attemptNumber ?? workflowFor(event).attempt).filter((value) => typeof value === "number" && Number.isFinite(value));
3493
- const count = attemptIds.size > 0 ? attemptIds.size : attemptNumbers.length > 0 ? Math.max(...attemptNumbers) : members.filter((event) => event.kind === "TOOL" || event.kind === "LLM").length;
3582
+ if (hasDuplicateAttemptIds(members)) {
3583
+ findings.push(
3584
+ fail2(
3585
+ "contract.retry.duplicate-attempt-id",
3586
+ `Operation ${operationId} has duplicate attemptId values.`,
3587
+ members.slice(0, 4).map(eventEvidence2),
3588
+ "unique attemptId",
3589
+ "duplicate"
3590
+ )
3591
+ );
3592
+ }
3593
+ const count = countOperationAttempts(members);
3494
3594
  if (count > rules.maxAttempts) {
3495
3595
  findings.push(
3496
3596
  fail2(
@@ -3525,18 +3625,18 @@ function evaluateRetrySafetyRules(events, rules, _runEvidence) {
3525
3625
  const workflow = workflowFor(event);
3526
3626
  const fallbackOf = workflow.fallbackOf;
3527
3627
  if (!fallbackOf) continue;
3528
- const prior = byOperation.get(fallbackOf) ?? events.filter((candidate) => {
3529
- const meta = workflowFor(candidate);
3530
- return meta.operationId === fallbackOf || candidate.runId === fallbackOf;
3531
- });
3532
- const priorFailure = prior.some((candidate) => candidate.status === "error");
3533
- if (!priorFailure) {
3628
+ const prior = byOperation.get(fallbackOf) ?? [];
3629
+ const fallbackStart = eventTime(event);
3630
+ const earlierFailure = prior.some(
3631
+ (candidate) => candidate.status === "error" && eventTime(candidate) !== "" && fallbackStart !== "" && eventTime(candidate) < fallbackStart
3632
+ );
3633
+ if (!earlierFailure) {
3534
3634
  findings.push(
3535
3635
  fail2(
3536
3636
  "contract.retry.fallback-after-failure",
3537
- `Fallback for ${fallbackOf} appeared without a prior failure.`,
3637
+ `Fallback for ${fallbackOf} appeared without an earlier failure in the related operation.`,
3538
3638
  [eventEvidence2(event)],
3539
- "prior error attempt",
3639
+ "earlier error attempt",
3540
3640
  { fallbackOf }
3541
3641
  )
3542
3642
  );
@@ -3558,39 +3658,46 @@ function evaluateRetrySafetyRules(events, rules, _runEvidence) {
3558
3658
  byToolOp.set(key, list);
3559
3659
  }
3560
3660
  for (const [, members] of byToolOp) {
3561
- const ordered = [...members].sort((a, b) => {
3562
- const aTime = a.startedAt ?? a.timestamp ?? "";
3563
- const bTime = b.startedAt ?? b.timestamp ?? "";
3564
- return aTime.localeCompare(bTime);
3565
- });
3661
+ const ordered = sortByTime(members);
3566
3662
  let sawOk = false;
3567
3663
  let sawSideEffectOk = false;
3664
+ const prior = [];
3568
3665
  for (const event of ordered) {
3569
3666
  const name = resolveCanonicalToolName(event);
3570
- const isRetry = sawOk;
3571
- if (isRetry) {
3572
- if (rules.requireIdempotencyEvidenceForRetry && !hasIdempotencyEvidence(event)) {
3573
- findings.push(
3574
- fail2(
3575
- "contract.retry.idempotency-evidence",
3576
- `Retry of tool ${name} lacks idempotencyKey / noSideEffect evidence.`,
3577
- [eventEvidence2(event)],
3578
- "idempotencyKey|noSideEffect",
3579
- { code: "AI_CHECK_RETRY_EVIDENCE_UNAVAILABLE" }
3580
- )
3581
- );
3582
- }
3583
- if (nonIdempotent.has(name) && sawSideEffectOk && !hasIdempotencyEvidence(event)) {
3584
- findings.push(
3585
- fail2(
3586
- "contract.retry.non-idempotent-side-effect",
3587
- `Retry of non-idempotent tool ${name} after a confirmed ok side effect.`,
3588
- [eventEvidence2(event)],
3589
- "no retry after side effect",
3590
- name
3591
- )
3592
- );
3593
- }
3667
+ const classification = classifyRetryAttempt(event, prior, members);
3668
+ if (classification.kind === "unknown" && rules.requireIdempotencyEvidenceForRetry) {
3669
+ findings.push(
3670
+ fail2(
3671
+ "contract.retry.ambiguous-identity",
3672
+ `Ambiguous attempt identity for tool ${name}; strict retry evidence cannot pass.`,
3673
+ [eventEvidence2(event)],
3674
+ "explicit attempt identity",
3675
+ classification.reason
3676
+ )
3677
+ );
3678
+ }
3679
+ const isRetry = classification.kind === "retry";
3680
+ if (isRetry && rules.requireIdempotencyEvidenceForRetry && !hasIdempotencyEvidence(event)) {
3681
+ findings.push(
3682
+ fail2(
3683
+ "contract.retry.idempotency-evidence",
3684
+ `Retry of tool ${name} lacks idempotencyKey / noSideEffect evidence.`,
3685
+ [eventEvidence2(event)],
3686
+ "idempotencyKey|noSideEffect",
3687
+ { code: "AI_CHECK_RETRY_EVIDENCE_UNAVAILABLE" }
3688
+ )
3689
+ );
3690
+ }
3691
+ if (sawOk && nonIdempotent.has(name) && sawSideEffectOk && !hasIdempotencyEvidence(event)) {
3692
+ findings.push(
3693
+ fail2(
3694
+ "contract.retry.non-idempotent-side-effect",
3695
+ `Retry of non-idempotent tool ${name} after a confirmed ok side effect.`,
3696
+ [eventEvidence2(event)],
3697
+ "no retry after side effect",
3698
+ name
3699
+ )
3700
+ );
3594
3701
  }
3595
3702
  if (event.status === "ok") {
3596
3703
  sawOk = true;
@@ -3598,25 +3705,43 @@ function evaluateRetrySafetyRules(events, rules, _runEvidence) {
3598
3705
  sawSideEffectOk = true;
3599
3706
  }
3600
3707
  }
3708
+ prior.push(event);
3601
3709
  }
3602
3710
  }
3603
3711
  }
3604
3712
  if (rules.requireRecoveredFailureVisible) {
3605
3713
  for (const [operationId, members] of byOperation) {
3606
- const hasOk = members.some((event) => event.status === "ok");
3607
- const hasError = members.some((event) => event.status === "error");
3608
- const attemptish = members.some((event) => {
3714
+ const ordered = sortByTime(members);
3715
+ const toolish = ordered.filter(
3716
+ (event) => (event.kind === "TOOL" || event.kind === "LLM") && (event.status === "ok" || event.status === "error")
3717
+ );
3718
+ if (toolish.length < 2) continue;
3719
+ let sawEarlierError = false;
3720
+ let sawLaterOkAfterError = false;
3721
+ for (const event of toolish) {
3722
+ if (event.status === "error") {
3723
+ sawEarlierError = true;
3724
+ } else if (event.status === "ok" && sawEarlierError) {
3725
+ sawLaterOkAfterError = true;
3726
+ }
3727
+ }
3728
+ const hasOk = toolish.some((event) => event.status === "ok");
3729
+ const attemptish = toolish.some((event) => {
3609
3730
  const meta = workflowFor(event);
3610
- return meta.attemptId !== void 0 || meta.attemptNumber !== void 0 || meta.attempt !== void 0;
3611
- }) || members.length > 1;
3612
- if (hasOk && attemptish && !hasError) {
3731
+ return meta.attemptId !== void 0 || meta.attemptNumber !== void 0 || meta.attempt !== void 0 || meta.retryOf !== void 0;
3732
+ }) || toolish.length > 1;
3733
+ if (!attemptish || !hasOk) continue;
3734
+ if (!sawLaterOkAfterError) {
3613
3735
  findings.push(
3614
3736
  fail2(
3615
3737
  "contract.retry.recovered-failure-visible",
3616
- `Operation ${operationId} recovered without retaining a visible failure attempt.`,
3738
+ `Operation ${operationId} does not show an earlier error attempt before a later success in the same chain.`,
3617
3739
  members.slice(0, 4).map(eventEvidence2),
3618
- "error attempt retained",
3619
- { hasOk, hasError }
3740
+ "earlier error then later ok",
3741
+ {
3742
+ sawEarlierError,
3743
+ statuses: toolish.map((event) => event.status)
3744
+ }
3620
3745
  )
3621
3746
  );
3622
3747
  }
@@ -4610,5 +4735,5 @@ function explainTraceContract(contract) {
4610
4735
  }
4611
4736
 
4612
4737
  export { buildTraceFacts, createBaselineRegressionRule, createDecisionRule, createGuardrailRule, createLlmUsageRule, createMaxStepDurationRule, createObservedOutcomeRule, createRequireCompletedRule, createRetrievalRule, createRunDepthRule, createRunDurationRule, createRunEventCountRule, createRunStatusRule, createSafetyOversizedAttributeRule, createSafetyRawContentRule, createSafetyRedactionRule, createSafetySecretPatternRule, createStallDetectionRule, createStructureCycleRule, createStructureIncompleteRule, createStructureOrphanRule, createStructureParallelWidthRule, createStructureRelationshipRule, createToolFailureRule, createToolOrderingRule, createToolUsageRule, defineTraceContract, deriveFailureFacts, deriveRelationshipFacts, evaluateToolArgumentValue, evaluateTraceContract, evaluateTraceContractRead, explainTraceContract, extractSessionWorkflowMetadata, extractToolArgumentPayload, lintTraceContract, projectLogicalEvents, resolveCanonicalToolName, resolveJsonPointer, resolveTraceContractScope, runTraceChecks, sessionKeyForRun, summarizeSemanticParity, workflowMetadataForRun };
4613
- //# sourceMappingURL=chunk-VMDCDWBE.mjs.map
4614
- //# sourceMappingURL=chunk-VMDCDWBE.mjs.map
4738
+ //# sourceMappingURL=chunk-LHSXRIYU.mjs.map
4739
+ //# sourceMappingURL=chunk-LHSXRIYU.mjs.map