fix: harden the legacy HTTP rollback path
N-06: export one idempotency-key authority from mutation-intent.ts and use it in the V2 client. A caller-supplied key is validated before credentials, timers and fetch, and an invalid value is rejected as VALIDATION_REJECTED / IDEMPOTENCY_KEY_INVALID rather than trimmed, regenerated or dropped, so a keyed command can no longer replay while sending no key. N-07: bound the legacy credential wait by the existing attempt controller, which already carries the total deadline and the caller signal, so a non-cooperative owner cannot hold the request open and no extra timer is introduced. The owner receives the operation context, and the failure follows ownership: deadline to REQUEST_TIMEOUT, caller to REQUEST_ABORTED, and only a genuine rejection to AUTH_INTEGRATION_FAILURE. None of these paths fetch. N-08: readBoundedJson delegates to the common bounded reader, so cancel and releaseLock throws stay isolated inside the closed result, and the V2 content-type mismatch now cancels the response body. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4fe924ee0f
commit
c9e820aed5
@@ -23,6 +23,39 @@ function validBoundedString(value: unknown, maxBytes: number): value is string {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* N-06. The single idempotency-key authority shared by the V2 compatibility
|
||||
* client and the V3 executor.
|
||||
*
|
||||
* A caller-supplied value is never trimmed, regenerated or silently dropped:
|
||||
* an invalid key is a contract violation, because replaying a keyed command
|
||||
* without its key is exactly the unsafe behaviour the key exists to prevent.
|
||||
*/
|
||||
export function isValidIdempotencyKey(value: unknown): value is string {
|
||||
if (
|
||||
!validBoundedString(
|
||||
value,
|
||||
MUTATION_INTENT_BOUNDS.idempotencyKeyMaxBytes,
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
for (const character of value) {
|
||||
const codePoint = character.codePointAt(0) ?? 0;
|
||||
if (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function defineIdempotencyKey(value: unknown): string {
|
||||
if (!isValidIdempotencyKey(value)) {
|
||||
throw new TypeError("Idempotency key is invalid.");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function defineMutationIntent(intent: MutationIntent): MutationIntent {
|
||||
if (
|
||||
!validBoundedString(
|
||||
|
||||
Reference in New Issue
Block a user