diff --git a/tools/v2/individual/deadline-detector/docs/SECURITY_AND_PERFORMANCE.md b/tools/v2/individual/deadline-detector/docs/SECURITY_AND_PERFORMANCE.md
new file mode 100644
index 00000000..a60bf096
--- /dev/null
+++ b/tools/v2/individual/deadline-detector/docs/SECURITY_AND_PERFORMANCE.md
@@ -0,0 +1,115 @@
+# Deadline Detector - Security and Performance Notes
+
+This document records the local security assumptions, unsafe input handling,
+and performance limits for the isolated Deadline Detector tool.
+
+## Scope
+
+All guidance applies only to:
+
+```text
+tools/v2/individual/deadline-detector/
+```
+
+The tool does not read a live inbox, write reminders, write calendars, send
+notifications, mutate messages, call providers, call AI services, persist data,
+or connect to the app shell. Future integration must add an explicit adapter
+issue before using real mailbox or calendar data.
+
+## Threat Assumptions
+
+- Message subjects, bodies, sender values, timestamps, and timezones are
+ untrusted input.
+- Attachment content is out of scope for this folder; only bounded metadata may
+ be passed to guard helpers.
+- Large inbox windows and long histories can create avoidable CPU and memory
+ work even when individual messages are valid.
+- Calendar, reminder, and notification writes are unsafe side effects unless a
+ later issue defines user confirmation, permissions, audit behavior, and
+ rollback handling.
+- Fixture data must remain synthetic and must not include production email
+ content, secrets, API keys, access tokens, wallet data, or provider
+ credentials.
+
+## Guard Helpers
+
+`guards/deadline-guards.mjs` owns folder-local validation and sanitization:
+
+- `sanitizeDeadlineText()` removes HTML-like tags, control characters,
+ zero-width characters, leading/trailing whitespace, and oversized text.
+- `validateMessageId()` rejects empty ids, path traversal, whitespace, and
+ unsupported characters.
+- `validateSourceType()` allowlists local source types.
+- `validateSender()` rejects malformed email-like sender values and CRLF/null
+ injection.
+- `validateIsoTimestamp()` rejects missing, malformed, or unparseable
+ timestamps.
+- `validateTimezone()` rejects empty, oversized, or control-character timezone
+ values.
+- `sanitizeDeadlineMessage()` returns a sanitized copy without mutating caller
+ input.
+- `guardDeadlineMessageBatch()` caps the number of messages processed in one
+ pass and sanitizes each message.
+- `guardAttachmentMetadata()` accepts metadata only and rejects embedded file
+ contents.
+- `guardHistoryWindow()` caps history scans.
+- `guardDetectionRequest()` validates a future adapter-shaped request with
+ messages and options.
+
+These guards are pure and synchronous. They do not perform network calls,
+storage writes, mailbox reads, calendar writes, reminder writes, or background
+jobs.
+
+## Unsafe Inputs
+
+| Input | Risk | Handling |
+| ---------------------------------- | -------------------------------------- | ----------------------------------- |
+| Path-like message ids | Path traversal or confusing audit ids | Rejected by `validateMessageId()` |
+| CRLF or null bytes in sender | Header/log injection | Rejected by `validateSender()` |
+| Control characters in subject/body | Hidden content or rendering surprises | Removed by `sanitizeDeadlineText()` |
+| HTML-like text in subject/body | Accidental markup or script display | Stripped before detection/review |
+| Invalid timestamps | Broken urgency calculations | Rejected before detection |
+| Oversized body text | Excess CPU/memory work | Truncated to the local body limit |
+| Oversized message batches | Slow full-inbox scans | Rejected with a pagination hint |
+| Attachment contents | File parsing, malware, memory pressure | Rejected; metadata only |
+| Oversized histories | Avoidable scan cost | Rejected with a smaller-window hint |
+
+## Performance Limits
+
+The current guard constants are intentionally conservative:
+
+| Limit | Value |
+| ------------------------ | ------------------------------- |
+| Message batch size | 100 messages |
+| Subject length | 998 characters |
+| Body length | 20,000 characters |
+| Attachment count | 25 metadata rows |
+| Attachment size metadata | 10,000,000 bytes per attachment |
+| History events | 500 rows |
+
+Future integrations should paginate large inboxes and histories before calling
+the detector. The detector should run on a bounded, user-confirmed slice rather
+than scanning full accounts, teams, or long-running sync histories.
+
+## Review Commands
+
+Run from the repository root:
+
+```bash
+node --test tools/v2/individual/deadline-detector/tests/deadline-guards.test.mjs
+node --test tools/v2/individual/deadline-detector/tests/deadline-fixtures.test.mjs
+```
+
+The guard test uses `fixtures/hostile-deadline-inputs.json` to verify malformed
+input rejection, text sanitization, attachment metadata boundaries, and
+oversized collection guards.
+
+## Future Adapter Checklist
+
+- Normalize real mailbox data into folder-local `DeadlineMessage` objects.
+- Run `guardDetectionRequest()` before calling `detectDeadlines()`.
+- Paginate inbox, team, attachment, and history data before scanning.
+- Keep reminder/calendar writes behind explicit user confirmation.
+- Avoid logging raw message bodies or private sender details.
+- Keep provider, AI, database, notification, wallet, and Stellar integrations
+ outside this folder unless a future issue explicitly allows them.
diff --git a/tools/v2/individual/deadline-detector/fixtures/hostile-deadline-inputs.json b/tools/v2/individual/deadline-detector/fixtures/hostile-deadline-inputs.json
new file mode 100644
index 00000000..46ebee84
--- /dev/null
+++ b/tools/v2/individual/deadline-detector/fixtures/hostile-deadline-inputs.json
@@ -0,0 +1,83 @@
+{
+ "tool": "deadline-detector",
+ "version": 1,
+ "validMessages": [
+ {
+ "id": "msg-safe-001",
+ "type": "email",
+ "sender": "sender@example.test",
+ "subject": "Submit report by 2026-06-20",
+ "body": "The report is due by 2026-06-20 at 15:00.",
+ "receivedAt": "2026-06-18T09:00:00Z",
+ "containsPersonalData": false,
+ "userTimezone": "UTC"
+ }
+ ],
+ "hostileMessages": [
+ {
+ "id": "bad-id-path",
+ "field": "id",
+ "reason": "path traversal in message id",
+ "patch": {
+ "id": "../../mailbox"
+ }
+ },
+ {
+ "id": "bad-type",
+ "field": "type",
+ "reason": "unsupported source type",
+ "patch": {
+ "type": "live-inbox"
+ }
+ },
+ {
+ "id": "bad-sender-header",
+ "field": "sender",
+ "reason": "CRLF sender injection",
+ "patch": {
+ "sender": "attacker@example.test\r\nBcc: victim@example.test"
+ }
+ },
+ {
+ "id": "bad-received-at",
+ "field": "receivedAt",
+ "reason": "invalid timestamp",
+ "patch": {
+ "receivedAt": "not-a-date"
+ }
+ },
+ {
+ "id": "bad-timezone",
+ "field": "userTimezone",
+ "reason": "timezone control characters",
+ "patch": {
+ "userTimezone": "UTC\r\nX-Timezone: injected"
+ }
+ },
+ {
+ "id": "bad-personal-flag",
+ "field": "containsPersonalData",
+ "reason": "personal data marker must be boolean",
+ "patch": {
+ "containsPersonalData": "false"
+ }
+ }
+ ],
+ "sanitizationCases": [
+ {
+ "field": "subject",
+ "input": " Renewal\u0000 due\u200b tomorrow ",
+ "expected": "alert(1) Renewal due tomorrow"
+ },
+ {
+ "field": "body",
+ "input": "Submit\u0007 by 2026-06-20",
+ "expected": "Submit by 2026-06-20"
+ },
+ {
+ "field": "attachmentName",
+ "input": "
proposal.pdf\u0000",
+ "expected": "proposal.pdf"
+ }
+ ]
+}
diff --git a/tools/v2/individual/deadline-detector/guards/deadline-guards.mjs b/tools/v2/individual/deadline-detector/guards/deadline-guards.mjs
new file mode 100644
index 00000000..785e9fc8
--- /dev/null
+++ b/tools/v2/individual/deadline-detector/guards/deadline-guards.mjs
@@ -0,0 +1,249 @@
+/**
+ * Security and performance guards for the Deadline Detector.
+ *
+ * These helpers are pure, synchronous, and folder-local. Future integration
+ * code should call them before running detection over mailbox, attachment, or
+ * history data. They do not read inbox state, write reminders, call providers,
+ * or persist anything.
+ */
+
+const ALLOWED_SOURCE_TYPES = new Set(["email", "calendar-forward", "invoice", "project-update"]);
+const MESSAGE_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
+const TIMEZONE_PATTERN = /^[A-Za-z0-9_+\-/.]+$/;
+
+export const DEADLINE_GUARD_LIMITS = {
+ MAX_MESSAGE_BATCH_SIZE: 100,
+ MAX_MESSAGE_ID_LENGTH: 96,
+ MAX_SENDER_LENGTH: 254,
+ MAX_SUBJECT_LENGTH: 998,
+ MAX_BODY_LENGTH: 20_000,
+ MAX_TIMEZONE_LENGTH: 80,
+ MAX_ATTACHMENT_COUNT: 25,
+ MAX_ATTACHMENT_NAME_LENGTH: 180,
+ MAX_ATTACHMENT_BYTES: 10_000_000,
+ MAX_HISTORY_EVENTS: 500,
+};
+
+const CONTROL_CHARACTERS = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g;
+const INVISIBLE_CHARACTERS = /[\u200b-\u200d\u2060\ufeff]/g;
+const HTML_TAGS = /<[^>]*>/g;
+
+export class DeadlineGuardError extends Error {
+ constructor(message, field) {
+ super(message);
+ this.name = "DeadlineGuardError";
+ this.field = field;
+ }
+}
+
+export function sanitizeDeadlineText(value, maxLength = DEADLINE_GUARD_LIMITS.MAX_BODY_LENGTH) {
+ if (typeof value !== "string") {
+ return "";
+ }
+ return value
+ .normalize("NFC")
+ .replace(HTML_TAGS, "")
+ .replace(CONTROL_CHARACTERS, "")
+ .replace(INVISIBLE_CHARACTERS, "")
+ .trim()
+ .slice(0, maxLength);
+}
+
+export function validateMessageId(id) {
+ if (typeof id !== "string" || id.length === 0) {
+ throw new DeadlineGuardError("message id must be a non-empty string", "id");
+ }
+ if (id.length > DEADLINE_GUARD_LIMITS.MAX_MESSAGE_ID_LENGTH) {
+ throw new DeadlineGuardError(
+ `message id exceeds ${DEADLINE_GUARD_LIMITS.MAX_MESSAGE_ID_LENGTH} characters`,
+ "id",
+ );
+ }
+ if (!MESSAGE_ID_PATTERN.test(id)) {
+ throw new DeadlineGuardError(
+ "message id may contain only letters, numbers, underscore, and dash",
+ "id",
+ );
+ }
+ return id;
+}
+
+export function validateSourceType(type) {
+ if (typeof type !== "string" || type.length === 0) {
+ throw new DeadlineGuardError("message type must be a non-empty string", "type");
+ }
+ if (!ALLOWED_SOURCE_TYPES.has(type)) {
+ throw new DeadlineGuardError(`unsupported message type: ${type}`, "type");
+ }
+ return type;
+}
+
+export function validateSender(sender) {
+ if (typeof sender !== "string" || sender.length === 0) {
+ throw new DeadlineGuardError("sender must be a non-empty string", "sender");
+ }
+ if (sender.length > DEADLINE_GUARD_LIMITS.MAX_SENDER_LENGTH) {
+ throw new DeadlineGuardError(
+ `sender exceeds ${DEADLINE_GUARD_LIMITS.MAX_SENDER_LENGTH} characters`,
+ "sender",
+ );
+ }
+ if (/[\r\n\0]/.test(sender)) {
+ throw new DeadlineGuardError("sender contains header injection characters", "sender");
+ }
+ const at = sender.lastIndexOf("@");
+ if (at < 1 || at === sender.length - 1) {
+ throw new DeadlineGuardError("sender must include local part and domain", "sender");
+ }
+ return sender;
+}
+
+export function validateIsoTimestamp(value, field = "timestamp") {
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T/.test(value)) {
+ throw new DeadlineGuardError(`${field} must be an ISO timestamp string`, field);
+ }
+ const parsed = Date.parse(value);
+ if (Number.isNaN(parsed)) {
+ throw new DeadlineGuardError(`${field} must be parseable as a date`, field);
+ }
+ return value;
+}
+
+export function validateTimezone(timezone) {
+ if (typeof timezone !== "string" || timezone.length === 0) {
+ throw new DeadlineGuardError("timezone must be a non-empty string", "userTimezone");
+ }
+ if (timezone.length > DEADLINE_GUARD_LIMITS.MAX_TIMEZONE_LENGTH) {
+ throw new DeadlineGuardError(
+ `timezone exceeds ${DEADLINE_GUARD_LIMITS.MAX_TIMEZONE_LENGTH} characters`,
+ "userTimezone",
+ );
+ }
+ if (!TIMEZONE_PATTERN.test(timezone)) {
+ throw new DeadlineGuardError("timezone contains unsupported characters", "userTimezone");
+ }
+ return timezone;
+}
+
+export function sanitizeDeadlineMessage(message) {
+ if (message === null || typeof message !== "object" || Array.isArray(message)) {
+ throw new DeadlineGuardError("deadline message must be a plain object", "message");
+ }
+
+ const subject = sanitizeDeadlineText(message.subject, DEADLINE_GUARD_LIMITS.MAX_SUBJECT_LENGTH);
+ const body = sanitizeDeadlineText(message.body, DEADLINE_GUARD_LIMITS.MAX_BODY_LENGTH);
+
+ if (subject.length === 0) {
+ throw new DeadlineGuardError("subject must contain visible text", "subject");
+ }
+ if (body.length === 0) {
+ throw new DeadlineGuardError("body must contain visible text", "body");
+ }
+ if (typeof message.containsPersonalData !== "boolean") {
+ throw new DeadlineGuardError("containsPersonalData must be boolean", "containsPersonalData");
+ }
+
+ return {
+ id: validateMessageId(message.id),
+ type: validateSourceType(message.type),
+ sender: validateSender(message.sender),
+ subject,
+ body,
+ receivedAt: validateIsoTimestamp(message.receivedAt, "receivedAt"),
+ containsPersonalData: message.containsPersonalData,
+ userTimezone: validateTimezone(message.userTimezone),
+ };
+}
+
+export function guardDeadlineMessageBatch(messages) {
+ if (!Array.isArray(messages)) {
+ throw new DeadlineGuardError("messages must be an array", "messages");
+ }
+ if (messages.length > DEADLINE_GUARD_LIMITS.MAX_MESSAGE_BATCH_SIZE) {
+ throw new DeadlineGuardError(
+ `message batch size ${messages.length} exceeds ${DEADLINE_GUARD_LIMITS.MAX_MESSAGE_BATCH_SIZE}; paginate before detecting`,
+ "messages",
+ );
+ }
+ return messages.map((message) => sanitizeDeadlineMessage(message));
+}
+
+export function guardAttachmentMetadata(attachments = []) {
+ if (!Array.isArray(attachments)) {
+ throw new DeadlineGuardError("attachments must be an array", "attachments");
+ }
+ if (attachments.length > DEADLINE_GUARD_LIMITS.MAX_ATTACHMENT_COUNT) {
+ throw new DeadlineGuardError(
+ `attachment count ${attachments.length} exceeds ${DEADLINE_GUARD_LIMITS.MAX_ATTACHMENT_COUNT}`,
+ "attachments",
+ );
+ }
+
+ return attachments.map((attachment, index) => {
+ if (attachment === null || typeof attachment !== "object" || Array.isArray(attachment)) {
+ throw new DeadlineGuardError(`attachment ${index} must be a plain object`, "attachments");
+ }
+ if ("content" in attachment || "bytes" in attachment || "buffer" in attachment) {
+ throw new DeadlineGuardError(
+ "attachment content is out of scope; pass metadata only",
+ "attachments",
+ );
+ }
+ const name = sanitizeDeadlineText(
+ attachment.name,
+ DEADLINE_GUARD_LIMITS.MAX_ATTACHMENT_NAME_LENGTH,
+ );
+ if (name.length === 0) {
+ throw new DeadlineGuardError("attachment name must contain visible text", "attachments");
+ }
+ if (!Number.isInteger(attachment.sizeBytes) || attachment.sizeBytes < 0) {
+ throw new DeadlineGuardError(
+ "attachment sizeBytes must be a non-negative integer",
+ "attachments",
+ );
+ }
+ if (attachment.sizeBytes > DEADLINE_GUARD_LIMITS.MAX_ATTACHMENT_BYTES) {
+ throw new DeadlineGuardError(
+ `attachment exceeds ${DEADLINE_GUARD_LIMITS.MAX_ATTACHMENT_BYTES} bytes`,
+ "attachments",
+ );
+ }
+ return {
+ name,
+ sizeBytes: attachment.sizeBytes,
+ };
+ });
+}
+
+export function guardHistoryWindow(events = []) {
+ if (!Array.isArray(events)) {
+ throw new DeadlineGuardError("history events must be an array", "history");
+ }
+ if (events.length > DEADLINE_GUARD_LIMITS.MAX_HISTORY_EVENTS) {
+ throw new DeadlineGuardError(
+ `history size ${events.length} exceeds ${DEADLINE_GUARD_LIMITS.MAX_HISTORY_EVENTS}; request a smaller window`,
+ "history",
+ );
+ }
+ return true;
+}
+
+export function guardDetectionRequest(request) {
+ if (request === null || typeof request !== "object" || Array.isArray(request)) {
+ throw new DeadlineGuardError("detection request must be a plain object", "request");
+ }
+
+ const messages = guardDeadlineMessageBatch(request.messages);
+ const options = {};
+
+ if (request.options?.now !== undefined) {
+ options.now = validateIsoTimestamp(request.options.now, "now");
+ }
+ if (request.options?.defaultTimezone !== undefined) {
+ options.defaultTimezone = validateTimezone(request.options.defaultTimezone);
+ }
+
+ return { messages, options };
+}
+
+export { ALLOWED_SOURCE_TYPES };
diff --git a/tools/v2/individual/deadline-detector/tests/deadline-guards.test.mjs b/tools/v2/individual/deadline-detector/tests/deadline-guards.test.mjs
new file mode 100644
index 00000000..78442d36
--- /dev/null
+++ b/tools/v2/individual/deadline-detector/tests/deadline-guards.test.mjs
@@ -0,0 +1,181 @@
+import assert from "node:assert/strict";
+import { readFileSync } from "node:fs";
+import { dirname, join } from "node:path";
+import { describe, it } from "node:test";
+import { fileURLToPath } from "node:url";
+
+import {
+ DEADLINE_GUARD_LIMITS,
+ DeadlineGuardError,
+ guardAttachmentMetadata,
+ guardDeadlineMessageBatch,
+ guardDetectionRequest,
+ guardHistoryWindow,
+ sanitizeDeadlineMessage,
+ sanitizeDeadlineText,
+ validateIsoTimestamp,
+ validateMessageId,
+ validateSender,
+ validateSourceType,
+ validateTimezone,
+} from "../guards/deadline-guards.mjs";
+
+const currentDir = dirname(fileURLToPath(import.meta.url));
+const fixture = JSON.parse(
+ readFileSync(join(currentDir, "..", "fixtures", "hostile-deadline-inputs.json"), "utf8"),
+);
+
+const baseMessage = fixture.validMessages[0];
+
+function assertGuardError(fn, field) {
+ assert.throws(
+ fn,
+ (error) => error instanceof DeadlineGuardError && error.field === field,
+ `expected DeadlineGuardError for ${field}`,
+ );
+}
+
+describe("Deadline Detector guards - text sanitization", () => {
+ for (const entry of fixture.sanitizationCases) {
+ it(`sanitizes ${entry.field}`, () => {
+ assert.equal(sanitizeDeadlineText(entry.input), entry.expected);
+ });
+ }
+
+ it("returns an empty string for non-string text", () => {
+ assert.equal(sanitizeDeadlineText(null), "");
+ assert.equal(sanitizeDeadlineText(42), "");
+ });
+
+ it("truncates text to the provided limit", () => {
+ assert.equal(sanitizeDeadlineText("abcdef", 3), "abc");
+ });
+});
+
+describe("Deadline Detector guards - field validators", () => {
+ it("accepts safe message ids", () => {
+ assert.equal(validateMessageId("msg-safe_001"), "msg-safe_001");
+ });
+
+ it("rejects path-like ids", () => {
+ assertGuardError(() => validateMessageId("../../mailbox"), "id");
+ });
+
+ it("accepts allowlisted source types", () => {
+ assert.equal(validateSourceType("email"), "email");
+ assert.equal(validateSourceType("project-update"), "project-update");
+ });
+
+ it("rejects unknown source types", () => {
+ assertGuardError(() => validateSourceType("live-inbox"), "type");
+ });
+
+ it("rejects sender CRLF injection", () => {
+ assertGuardError(
+ () => validateSender("sender@example.test\r\nBcc: victim@example.test"),
+ "sender",
+ );
+ });
+
+ it("rejects malformed timestamps", () => {
+ assertGuardError(() => validateIsoTimestamp("not-a-date", "receivedAt"), "receivedAt");
+ });
+
+ it("rejects unsafe timezone characters", () => {
+ assertGuardError(() => validateTimezone("UTC\r\nX-Timezone: injected"), "userTimezone");
+ });
+});
+
+describe("Deadline Detector guards - message validation", () => {
+ it("returns a sanitized copy without mutating input", () => {
+ const input = {
+ ...baseMessage,
+ subject: " Due\u0000 tomorrow ",
+ body: "Submit\u200b by tomorrow.",
+ };
+ const sanitized = sanitizeDeadlineMessage(input);
+
+ assert.equal(sanitized.subject, "Due tomorrow");
+ assert.equal(sanitized.body, "Submit by tomorrow.");
+ assert.equal(input.subject, " Due\u0000 tomorrow ");
+ });
+
+ for (const entry of fixture.hostileMessages) {
+ it(`rejects ${entry.id}: ${entry.reason}`, () => {
+ assertGuardError(
+ () => sanitizeDeadlineMessage({ ...baseMessage, ...entry.patch }),
+ entry.field,
+ );
+ });
+ }
+
+ it("rejects non-object messages", () => {
+ assertGuardError(() => sanitizeDeadlineMessage(null), "message");
+ assertGuardError(() => sanitizeDeadlineMessage([]), "message");
+ });
+
+ it("guards and sanitizes a bounded message batch", () => {
+ const result = guardDeadlineMessageBatch([baseMessage]);
+ assert.equal(result.length, 1);
+ assert.equal(result[0].id, baseMessage.id);
+ });
+
+ it("rejects oversized message batches before detection", () => {
+ const messages = Array.from(
+ { length: DEADLINE_GUARD_LIMITS.MAX_MESSAGE_BATCH_SIZE + 1 },
+ (_, index) => ({ ...baseMessage, id: `msg-${index}` }),
+ );
+
+ assertGuardError(() => guardDeadlineMessageBatch(messages), "messages");
+ });
+});
+
+describe("Deadline Detector guards - request and collection limits", () => {
+ it("validates a future adapter request shape", () => {
+ const request = {
+ messages: [baseMessage],
+ options: {
+ now: "2026-06-18T09:00:00Z",
+ defaultTimezone: "UTC",
+ },
+ };
+
+ const guarded = guardDetectionRequest(request);
+ assert.equal(guarded.messages.length, 1);
+ assert.equal(guarded.options.now, "2026-06-18T09:00:00Z");
+ assert.equal(guarded.options.defaultTimezone, "UTC");
+ });
+
+ it("rejects attachment content and accepts metadata", () => {
+ const metadata = guardAttachmentMetadata([
+ {
+ name: fixture.sanitizationCases[2].input,
+ sizeBytes: 1024,
+ },
+ ]);
+
+ assert.equal(metadata[0].name, fixture.sanitizationCases[2].expected);
+ assertGuardError(
+ () => guardAttachmentMetadata([{ name: "invoice.pdf", sizeBytes: 10, content: "raw" }]),
+ "attachments",
+ );
+ });
+
+ it("rejects oversized attachment lists", () => {
+ const attachments = Array.from(
+ { length: DEADLINE_GUARD_LIMITS.MAX_ATTACHMENT_COUNT + 1 },
+ (_, index) => ({ name: `file-${index}.txt`, sizeBytes: 100 }),
+ );
+
+ assertGuardError(() => guardAttachmentMetadata(attachments), "attachments");
+ });
+
+ it("rejects oversized history windows", () => {
+ const history = Array.from(
+ { length: DEADLINE_GUARD_LIMITS.MAX_HISTORY_EVENTS + 1 },
+ (_, index) => ({ id: `event-${index}` }),
+ );
+
+ assertGuardError(() => guardHistoryWindow(history), "history");
+ });
+});