@tangle-network/agent-app 0.60.215 → 0.60.216

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 (34) hide show
  1. package/dist/chunk-CD2LQXY6.js +306 -0
  2. package/dist/chunk-CD2LQXY6.js.map +1 -0
  3. package/dist/{chunk-KDPTXXOQ.js → chunk-J3N6RNMH.js} +456 -75
  4. package/dist/chunk-J3N6RNMH.js.map +1 -0
  5. package/dist/chunk-LRYY3OX7.js +105 -0
  6. package/dist/chunk-LRYY3OX7.js.map +1 -0
  7. package/dist/{chunk-LLO436QO.js → chunk-QM7RTTDY.js} +27 -86
  8. package/dist/chunk-QM7RTTDY.js.map +1 -0
  9. package/dist/{chunk-ZRLKB4KB.js → chunk-YWLZOPDS.js} +10 -11
  10. package/dist/{chunk-ZRLKB4KB.js.map → chunk-YWLZOPDS.js.map} +1 -1
  11. package/dist/launch-invariants/catalog.d.ts +16 -0
  12. package/dist/launch-invariants/cli.js +5 -2
  13. package/dist/launch-invariants/cli.js.map +1 -1
  14. package/dist/launch-invariants/index.d.ts +1 -1
  15. package/dist/launch-invariants/index.js +13 -5
  16. package/dist/launch-invariants/index.js.map +1 -1
  17. package/dist/launch-invariants/limit-alarms.d.ts +19 -1
  18. package/dist/launch-invariants/testing.d.ts +0 -3
  19. package/dist/launch-invariants/testing.js +5 -2
  20. package/dist/signoff/app-defaults.d.ts +65 -0
  21. package/dist/signoff/cli.js +71 -4
  22. package/dist/signoff/cli.js.map +1 -1
  23. package/dist/signoff/defaults-cli.d.ts +1 -0
  24. package/dist/signoff/index.js +2 -1
  25. package/dist/signoff/types.d.ts +6 -0
  26. package/dist/signoff/worker-defaults.d.ts +8 -0
  27. package/dist/turn-health/index.js +12 -291
  28. package/dist/turn-health/index.js.map +1 -1
  29. package/dist/turn-trace/index.js +20 -3
  30. package/dist/turn-trace/index.js.map +1 -1
  31. package/dist/turn-trace/otel.d.ts +11 -4
  32. package/package.json +1 -1
  33. package/dist/chunk-KDPTXXOQ.js.map +0 -1
  34. package/dist/chunk-LLO436QO.js.map +0 -1
@@ -0,0 +1,105 @@
1
+ // src/launch-invariants/catalog.ts
2
+ var LAUNCH_BUDGETS = {
3
+ /** Most bytes one query may return in a scheduled job or report route: one sized batch with room for its listing. */
4
+ queryBytes: 8 * 1024 * 1024,
5
+ /** Most the live heap may grow while one scheduled job runs on a day-sized fixture. */
6
+ heapGrowthBytes: 40 * 1024 * 1024,
7
+ /** Most bytes of a large column one sized read returns; a single larger row is read alone. */
8
+ sizedReadBytes: 4 * 1024 * 1024,
9
+ /** Most stored events one long turn may write. */
10
+ turnEvents: 5e3,
11
+ /** A turn whose owner stopped reaches its terminal state within this long. */
12
+ settleWithinMs: 15 * 6e4,
13
+ /** Turn recovery and orphan settlement run at least this often. */
14
+ recoveryEveryMs: 15 * 6e4,
15
+ /** Longest a cached auth lookup may be served while the store cannot answer. */
16
+ authCacheTtlMs: 6e4,
17
+ /** A platform-limit alarm fires at this share of the limit. */
18
+ alarmRatio: 0.8,
19
+ /** A Worker isolate's memory. */
20
+ workerMemoryBytes: 128 * 1024 * 1024,
21
+ /** Rows one D1 query may read before an alarm; a sized read stays far below it (GTM's 1.63 M-event scan overloaded D1). */
22
+ d1RowsPerQuery: 25e4,
23
+ /** A workspace box's disk: the Sandbox SDK's default tier (10 GB). */
24
+ sandboxDiskBytes: 10 * 1024 ** 3,
25
+ /** Platform snapshots per box; the cap GTM hit on 2026-10-10. */
26
+ snapshotCount: 25,
27
+ /** Operator key requests per minute. */
28
+ keyRatePerMinute: 60,
29
+ /** Hours one resource's limit alarm waits before it repeats. */
30
+ alarmRepeatHours: 6,
31
+ /** Cloudflare's gzip limit on a Worker's Total Upload on the paid plan, in KiB; Legal deployed 9,659 KiB (94%) on 2026-10-19. */
32
+ workerUploadGzipKiB: 10 * 1024,
33
+ /** Cloudflare's raw limit on a Worker's Total Upload, in KiB. */
34
+ workerUploadRawKiB: 64 * 1024
35
+ };
36
+ var LAUNCH_INVARIANTS = [
37
+ {
38
+ id: "bounded-reads",
39
+ title: "Bounded reads",
40
+ rule: "No query in a scheduled job or report route returns more than 8 MB; large columns are read in sized batches of at most 4 MB.",
41
+ incident: "GTM turn health read 500 replies with their parts (122 MB of a 128 MB Worker); the reliability report scanned 1.63 M events and overloaded D1."
42
+ },
43
+ {
44
+ id: "memory-budget",
45
+ title: "Memory budget",
46
+ rule: "Every scheduled job runs on a day-sized fixture with live-heap growth of at most 40 MB, and every scheduled job is covered.",
47
+ incident: "GTM sweeps exceeded the Worker's 128 MB six times; a regex slice kept in a Map pinned whole tool outputs."
48
+ },
49
+ {
50
+ id: "isolated-jobs",
51
+ title: "Isolated scheduled jobs",
52
+ rule: "Each scheduled invocation runs exactly one job, and turn recovery and orphan settlement run at least every 15 minutes.",
53
+ incident: "At 21:00Z turn health ran out of memory while the hourly sweeps shared one run, and none of the eight queued after it ran."
54
+ },
55
+ {
56
+ id: "honest-settlement",
57
+ title: "Honest turn settlement",
58
+ rule: "Every turn reaches exactly one terminal state (an answer, a typed failure with Retry, or a Stop counted on its own) within 15 minutes of its owner stopping; failures are attributed to the request time, and a completed run with an aborted tool is a failure.",
59
+ incident: "Stale running rows (51 settled by hand), failure notices 8\u201311 h late, and completions with aborted tools counted as answers."
60
+ },
61
+ {
62
+ id: "coalesced-events",
63
+ title: "Coalesced turn events",
64
+ rule: "A recorded long turn stores at most 5,000 events: text and reasoning deltas are merged before storage.",
65
+ incident: "A reasoning model wrote 59,659 events in one turn and 1.63 M in a day."
66
+ },
67
+ {
68
+ id: "auth-survives-d1-stall",
69
+ title: "Auth survives a D1 stall",
70
+ rule: "A key or session verified within 60 s keeps working while D1 errors or stalls; the cache is used only then, never past revoke or expiry, and is keyed by SHA-256.",
71
+ incident: "An overloaded D1 failed sign-in and API-key checks for credentials that were valid moments before."
72
+ },
73
+ {
74
+ id: "authorize-before-stream",
75
+ title: "Authorization before any stream",
76
+ rule: "A refused request gets a plain response; no stream opens and no product state is touched before authorization resolves.",
77
+ incident: "agent-app 0.60.48\u20130.60.49 opened a progress-first stream before authorize ran."
78
+ },
79
+ {
80
+ id: "limit-alarms",
81
+ title: "Platform-limit alarms at 80%",
82
+ rule: "Worker memory, D1 rows per query, sandbox disk, snapshot count and key rate each have a declared limit and an alarm that fires at 80%.",
83
+ incident: "19 platform-limit events on 2026-10-10, each found after it failed a customer."
84
+ },
85
+ {
86
+ id: "pasted-code-reaches-chat",
87
+ title: "Pasted code reaches the chat",
88
+ rule: "An authenticated chat message carrying pasted code (shell, SQL, markup) gets the route's normal response; no app filter, sanitizer or edge rule refuses it.",
89
+ incident: "On 2026-10-11 a chat message containing `cat /etc/passwd` got Cloudflare's 403 block page from gtm.tangle.tools instead of reaching the Worker."
90
+ }
91
+ ];
92
+ var LAUNCH_INVARIANT_IDS = LAUNCH_INVARIANTS.map((invariant) => invariant.id);
93
+ function describeVerdict(verdict) {
94
+ const head = `${verdict.pass ? "PASS" : "FAIL"} ${verdict.invariant} \u2014 ${verdict.subject}`;
95
+ return verdict.details.length ? `${head}
96
+ ${verdict.details.join("\n ")}` : head;
97
+ }
98
+
99
+ export {
100
+ LAUNCH_BUDGETS,
101
+ LAUNCH_INVARIANTS,
102
+ LAUNCH_INVARIANT_IDS,
103
+ describeVerdict
104
+ };
105
+ //# sourceMappingURL=chunk-LRYY3OX7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/launch-invariants/catalog.ts"],"sourcesContent":["/**\n * The launch invariants every agent app holds, and the budgets they are held to.\n *\n * Each one is a failure GTM shipped to production on 2026-10-10 and fixed by\n * hand. They are listed here once so every app checks the same rule with the\n * same numbers, and `agent-app-invariants` reports each by its id.\n */\n\n/** Budgets the invariants are measured against. */\nexport const LAUNCH_BUDGETS = {\n /** Most bytes one query may return in a scheduled job or report route: one sized batch with room for its listing. */\n queryBytes: 8 * 1024 * 1024,\n /** Most the live heap may grow while one scheduled job runs on a day-sized fixture. */\n heapGrowthBytes: 40 * 1024 * 1024,\n /** Most bytes of a large column one sized read returns; a single larger row is read alone. */\n sizedReadBytes: 4 * 1024 * 1024,\n /** Most stored events one long turn may write. */\n turnEvents: 5_000,\n /** A turn whose owner stopped reaches its terminal state within this long. */\n settleWithinMs: 15 * 60_000,\n /** Turn recovery and orphan settlement run at least this often. */\n recoveryEveryMs: 15 * 60_000,\n /** Longest a cached auth lookup may be served while the store cannot answer. */\n authCacheTtlMs: 60_000,\n /** A platform-limit alarm fires at this share of the limit. */\n alarmRatio: 0.8,\n /** A Worker isolate's memory. */\n workerMemoryBytes: 128 * 1024 * 1024,\n /** Rows one D1 query may read before an alarm; a sized read stays far below it (GTM's 1.63 M-event scan overloaded D1). */\n d1RowsPerQuery: 250_000,\n /** A workspace box's disk: the Sandbox SDK's default tier (10 GB). */\n sandboxDiskBytes: 10 * 1024 ** 3,\n /** Platform snapshots per box; the cap GTM hit on 2026-10-10. */\n snapshotCount: 25,\n /** Operator key requests per minute. */\n keyRatePerMinute: 60,\n /** Hours one resource's limit alarm waits before it repeats. */\n alarmRepeatHours: 6,\n /** Cloudflare's gzip limit on a Worker's Total Upload on the paid plan, in KiB; Legal deployed 9,659 KiB (94%) on 2026-10-19. */\n workerUploadGzipKiB: 10 * 1024,\n /** Cloudflare's raw limit on a Worker's Total Upload, in KiB. */\n workerUploadRawKiB: 64 * 1024,\n} as const\n\nexport type LaunchInvariantId =\n | 'bounded-reads'\n | 'memory-budget'\n | 'isolated-jobs'\n | 'honest-settlement'\n | 'coalesced-events'\n | 'auth-survives-d1-stall'\n | 'authorize-before-stream'\n | 'limit-alarms'\n | 'pasted-code-reaches-chat'\n\nexport interface LaunchInvariant {\n id: LaunchInvariantId\n title: string\n /** The rule, as the conformance report states it. */\n rule: string\n /** The production failure the rule answers. */\n incident: string\n}\n\nexport const LAUNCH_INVARIANTS: readonly LaunchInvariant[] = [\n {\n id: 'bounded-reads',\n title: 'Bounded reads',\n rule: 'No query in a scheduled job or report route returns more than 8 MB; large columns are read in sized batches of at most 4 MB.',\n incident: 'GTM turn health read 500 replies with their parts (122 MB of a 128 MB Worker); the reliability report scanned 1.63 M events and overloaded D1.',\n },\n {\n id: 'memory-budget',\n title: 'Memory budget',\n rule: 'Every scheduled job runs on a day-sized fixture with live-heap growth of at most 40 MB, and every scheduled job is covered.',\n incident: 'GTM sweeps exceeded the Worker\\'s 128 MB six times; a regex slice kept in a Map pinned whole tool outputs.',\n },\n {\n id: 'isolated-jobs',\n title: 'Isolated scheduled jobs',\n rule: 'Each scheduled invocation runs exactly one job, and turn recovery and orphan settlement run at least every 15 minutes.',\n incident: 'At 21:00Z turn health ran out of memory while the hourly sweeps shared one run, and none of the eight queued after it ran.',\n },\n {\n id: 'honest-settlement',\n title: 'Honest turn settlement',\n rule: 'Every turn reaches exactly one terminal state (an answer, a typed failure with Retry, or a Stop counted on its own) within 15 minutes of its owner stopping; failures are attributed to the request time, and a completed run with an aborted tool is a failure.',\n incident: 'Stale running rows (51 settled by hand), failure notices 8–11 h late, and completions with aborted tools counted as answers.',\n },\n {\n id: 'coalesced-events',\n title: 'Coalesced turn events',\n rule: 'A recorded long turn stores at most 5,000 events: text and reasoning deltas are merged before storage.',\n incident: 'A reasoning model wrote 59,659 events in one turn and 1.63 M in a day.',\n },\n {\n id: 'auth-survives-d1-stall',\n title: 'Auth survives a D1 stall',\n rule: 'A key or session verified within 60 s keeps working while D1 errors or stalls; the cache is used only then, never past revoke or expiry, and is keyed by SHA-256.',\n incident: 'An overloaded D1 failed sign-in and API-key checks for credentials that were valid moments before.',\n },\n {\n id: 'authorize-before-stream',\n title: 'Authorization before any stream',\n rule: 'A refused request gets a plain response; no stream opens and no product state is touched before authorization resolves.',\n incident: 'agent-app 0.60.48–0.60.49 opened a progress-first stream before authorize ran.',\n },\n {\n id: 'limit-alarms',\n title: 'Platform-limit alarms at 80%',\n rule: 'Worker memory, D1 rows per query, sandbox disk, snapshot count and key rate each have a declared limit and an alarm that fires at 80%.',\n incident: '19 platform-limit events on 2026-10-10, each found after it failed a customer.',\n },\n {\n id: 'pasted-code-reaches-chat',\n title: 'Pasted code reaches the chat',\n rule: 'An authenticated chat message carrying pasted code (shell, SQL, markup) gets the route\\'s normal response; no app filter, sanitizer or edge rule refuses it.',\n incident: 'On 2026-10-11 a chat message containing `cat /etc/passwd` got Cloudflare\\'s 403 block page from gtm.tangle.tools instead of reaching the Worker.',\n },\n]\n\n/** The invariant ids in report order. */\nexport const LAUNCH_INVARIANT_IDS: readonly LaunchInvariantId[] = LAUNCH_INVARIANTS.map((invariant) => invariant.id)\n\n/** One checked outcome of one invariant, for one subject (a job, a route, a scenario). */\nexport interface InvariantVerdict {\n invariant: LaunchInvariantId\n /** What was checked: a job name, a route, a scenario. */\n subject: string\n pass: boolean\n /** Findings, one line each. On a pass, what was proven. */\n details: string[]\n /** Measurements behind the verdict, for the report. */\n data?: Record<string, unknown>\n /**\n * Set when the app recorded this failure as known, with why and who owns the\n * fix. The report counts it as not holding; it does not fail the suite.\n */\n knownFailing?: string\n}\n\n/** A verdict whose message names every finding, for a test runner's failure output. */\nexport function describeVerdict(verdict: InvariantVerdict): string {\n const head = `${verdict.pass ? 'PASS' : 'FAIL'} ${verdict.invariant} — ${verdict.subject}`\n return verdict.details.length ? `${head}\\n ${verdict.details.join('\\n ')}` : head\n}\n"],"mappings":";AASO,IAAM,iBAAiB;AAAA;AAAA,EAE5B,YAAY,IAAI,OAAO;AAAA;AAAA,EAEvB,iBAAiB,KAAK,OAAO;AAAA;AAAA,EAE7B,gBAAgB,IAAI,OAAO;AAAA;AAAA,EAE3B,YAAY;AAAA;AAAA,EAEZ,gBAAgB,KAAK;AAAA;AAAA,EAErB,iBAAiB,KAAK;AAAA;AAAA,EAEtB,gBAAgB;AAAA;AAAA,EAEhB,YAAY;AAAA;AAAA,EAEZ,mBAAmB,MAAM,OAAO;AAAA;AAAA,EAEhC,gBAAgB;AAAA;AAAA,EAEhB,kBAAkB,KAAK,QAAQ;AAAA;AAAA,EAE/B,eAAe;AAAA;AAAA,EAEf,kBAAkB;AAAA;AAAA,EAElB,kBAAkB;AAAA;AAAA,EAElB,qBAAqB,KAAK;AAAA;AAAA,EAE1B,oBAAoB,KAAK;AAC3B;AAsBO,IAAM,oBAAgD;AAAA,EAC3D;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AACF;AAGO,IAAM,uBAAqD,kBAAkB,IAAI,CAAC,cAAc,UAAU,EAAE;AAoB5G,SAAS,gBAAgB,SAAmC;AACjE,QAAM,OAAO,GAAG,QAAQ,OAAO,SAAS,MAAM,IAAI,QAAQ,SAAS,WAAM,QAAQ,OAAO;AACxF,SAAO,QAAQ,QAAQ,SAAS,GAAG,IAAI;AAAA,IAAO,QAAQ,QAAQ,KAAK,MAAM,CAAC,KAAK;AACjF;","names":[]}
@@ -1,84 +1,13 @@
1
- // src/launch-invariants/catalog.ts
2
- var LAUNCH_BUDGETS = {
3
- /** Most bytes one query may return in a scheduled job or report route: one sized batch with room for its listing. */
4
- queryBytes: 8 * 1024 * 1024,
5
- /** Most the live heap may grow while one scheduled job runs on a day-sized fixture. */
6
- heapGrowthBytes: 40 * 1024 * 1024,
7
- /** Most bytes of a large column one sized read returns; a single larger row is read alone. */
8
- sizedReadBytes: 4 * 1024 * 1024,
9
- /** Most stored events one long turn may write. */
10
- turnEvents: 5e3,
11
- /** A turn whose owner stopped reaches its terminal state within this long. */
12
- settleWithinMs: 15 * 6e4,
13
- /** Turn recovery and orphan settlement run at least this often. */
14
- recoveryEveryMs: 15 * 6e4,
15
- /** Longest a cached auth lookup may be served while the store cannot answer. */
16
- authCacheTtlMs: 6e4,
17
- /** A platform-limit alarm fires at this share of the limit. */
18
- alarmRatio: 0.8
19
- };
20
- var LAUNCH_INVARIANTS = [
21
- {
22
- id: "bounded-reads",
23
- title: "Bounded reads",
24
- rule: "No query in a scheduled job or report route returns more than 8 MB; large columns are read in sized batches of at most 4 MB.",
25
- incident: "GTM turn health read 500 replies with their parts (122 MB of a 128 MB Worker); the reliability report scanned 1.63 M events and overloaded D1."
26
- },
27
- {
28
- id: "memory-budget",
29
- title: "Memory budget",
30
- rule: "Every scheduled job runs on a day-sized fixture with live-heap growth of at most 40 MB, and every scheduled job is covered.",
31
- incident: "GTM sweeps exceeded the Worker's 128 MB six times; a regex slice kept in a Map pinned whole tool outputs."
32
- },
33
- {
34
- id: "isolated-jobs",
35
- title: "Isolated scheduled jobs",
36
- rule: "Each scheduled invocation runs exactly one job, and turn recovery and orphan settlement run at least every 15 minutes.",
37
- incident: "At 21:00Z turn health ran out of memory while the hourly sweeps shared one run, and none of the eight queued after it ran."
38
- },
39
- {
40
- id: "honest-settlement",
41
- title: "Honest turn settlement",
42
- rule: "Every turn reaches exactly one terminal state (an answer, a typed failure with Retry, or a Stop counted on its own) within 15 minutes of its owner stopping; failures are attributed to the request time, and a completed run with an aborted tool is a failure.",
43
- incident: "Stale running rows (51 settled by hand), failure notices 8\u201311 h late, and completions with aborted tools counted as answers."
44
- },
45
- {
46
- id: "coalesced-events",
47
- title: "Coalesced turn events",
48
- rule: "A recorded long turn stores at most 5,000 events: text and reasoning deltas are merged before storage.",
49
- incident: "A reasoning model wrote 59,659 events in one turn and 1.63 M in a day."
50
- },
51
- {
52
- id: "auth-survives-d1-stall",
53
- title: "Auth survives a D1 stall",
54
- rule: "A key or session verified within 60 s keeps working while D1 errors or stalls; the cache is used only then, never past revoke or expiry, and is keyed by SHA-256.",
55
- incident: "An overloaded D1 failed sign-in and API-key checks for credentials that were valid moments before."
56
- },
57
- {
58
- id: "authorize-before-stream",
59
- title: "Authorization before any stream",
60
- rule: "A refused request gets a plain response; no stream opens and no product state is touched before authorization resolves.",
61
- incident: "agent-app 0.60.48\u20130.60.49 opened a progress-first stream before authorize ran."
62
- },
63
- {
64
- id: "limit-alarms",
65
- title: "Platform-limit alarms at 80%",
66
- rule: "Worker memory, D1 rows per query, sandbox disk, snapshot count and key rate each have a declared limit and an alarm that fires at 80%.",
67
- incident: "19 platform-limit events on 2026-10-10, each found after it failed a customer."
68
- },
69
- {
70
- id: "pasted-code-reaches-chat",
71
- title: "Pasted code reaches the chat",
72
- rule: "An authenticated chat message carrying pasted code (shell, SQL, markup) gets the route's normal response; no app filter, sanitizer or edge rule refuses it.",
73
- incident: "On 2026-10-11 a chat message containing `cat /etc/passwd` got Cloudflare's 403 block page from gtm.tangle.tools instead of reaching the Worker."
74
- }
75
- ];
76
- var LAUNCH_INVARIANT_IDS = LAUNCH_INVARIANTS.map((invariant) => invariant.id);
77
- function describeVerdict(verdict) {
78
- const head = `${verdict.pass ? "PASS" : "FAIL"} ${verdict.invariant} \u2014 ${verdict.subject}`;
79
- return verdict.details.length ? `${head}
80
- ${verdict.details.join("\n ")}` : head;
81
- }
1
+ import {
2
+ LAUNCH_BUDGETS
3
+ } from "./chunk-LRYY3OX7.js";
4
+ import {
5
+ createConsoleAlertSink,
6
+ createMemoryThrottleStore,
7
+ createMultiAlertSink,
8
+ createThrottledAlertSink,
9
+ createWebhookAlertSink
10
+ } from "./chunk-CD2LQXY6.js";
82
11
 
83
12
  // src/launch-invariants/schedule.ts
84
13
  function createScheduledDispatch(options) {
@@ -397,6 +326,20 @@ function formatAmount(value, resource) {
397
326
  if (UNITS[resource] === "bytes") return `${(value / (1024 * 1024)).toFixed(1)} MB`;
398
327
  return Math.round(value).toLocaleString("en-US");
399
328
  }
329
+ var DEFAULT_LIMIT_BUDGETS = {
330
+ "worker-memory": LAUNCH_BUDGETS.workerMemoryBytes,
331
+ "d1-rows-per-query": LAUNCH_BUDGETS.d1RowsPerQuery,
332
+ "sandbox-disk": LAUNCH_BUDGETS.sandboxDiskBytes,
333
+ "snapshot-count": LAUNCH_BUDGETS.snapshotCount,
334
+ "key-rate": LAUNCH_BUDGETS.keyRatePerMinute
335
+ };
336
+ function createProductLimitAlarms(options) {
337
+ const sink = options.sink ?? createThrottledAlertSink(createMultiAlertSink([
338
+ createConsoleAlertSink((message) => console.error(message)),
339
+ ...options.webhookUrl ? [createWebhookAlertSink({ webhookUrl: options.webhookUrl })] : []
340
+ ]), { store: createMemoryThrottleStore(), windowMs: LAUNCH_BUDGETS.alarmRepeatHours * 60 * 60 * 1e3 });
341
+ return createLimitAlarms({ product: options.product, budgets: { ...DEFAULT_LIMIT_BUDGETS, ...options.overrides }, sink });
342
+ }
400
343
  function withD1LimitAlarms(d1, alarms, options = {}) {
401
344
  const observe = (sql, result) => {
402
345
  const meta = result?.meta;
@@ -477,10 +420,6 @@ async function isEdgeBlock(response) {
477
420
  }
478
421
 
479
422
  export {
480
- LAUNCH_BUDGETS,
481
- LAUNCH_INVARIANTS,
482
- LAUNCH_INVARIANT_IDS,
483
- describeVerdict,
484
423
  createScheduledDispatch,
485
424
  slotsOf,
486
425
  cronDailyFirings,
@@ -489,9 +428,11 @@ export {
489
428
  checkIsolatedJobs,
490
429
  LIMIT_RESOURCES,
491
430
  createLimitAlarms,
431
+ DEFAULT_LIMIT_BUDGETS,
432
+ createProductLimitAlarms,
492
433
  withD1LimitAlarms,
493
434
  estimatedBytes,
494
435
  PASTED_CODE_SAMPLES,
495
436
  isEdgeBlock
496
437
  };
497
- //# sourceMappingURL=chunk-LLO436QO.js.map
438
+ //# sourceMappingURL=chunk-QM7RTTDY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/launch-invariants/schedule.ts","../src/launch-invariants/limit-alarms.ts","../src/launch-invariants/pasted-code.ts"],"sourcesContent":["/**\n * Scheduled jobs that cannot take each other down.\n *\n * On 2026-10-10 at 21:00Z GTM's turn health ran out of memory while the hourly\n * sweeps shared one scheduled run, and none of the eight queued after it ran.\n * Here each scheduled invocation runs exactly one job. A cron can carry a\n * minute list (`0,1,2 * * * *`) with one job per minute, so a Worker keeps a\n * few trigger expressions while every job gets its own invocation, its own\n * memory and its own failure.\n *\n * Cloudflare hands the handler the configured string verbatim as `event.cron`,\n * so dispatch is exact-string; a tick no entry handles is reported as\n * unhandled instead of silently running nothing. {@link checkIsolatedJobs}\n * holds the table against the deployment's configured crons.\n */\nimport { LAUNCH_BUDGETS, type InvariantVerdict } from './catalog.js'\n\n/** One cron expression and the job it runs, or one job per listed minute. */\nexport type ScheduleEntry =\n | { cron: string; job: string }\n | { cron: string; minutes: Readonly<Record<number, string>> }\n\n/** One scheduled invocation: the cron, the minute for a minute-list entry, and the job it runs. */\nexport interface ScheduleSlot {\n cron: string\n minute?: number\n job: string\n}\n\nexport interface ScheduledJobInput<Ctx> {\n cron: string\n scheduledTime: Date\n context: Ctx\n /** 1 for the first try. */\n attempt: number\n}\n\nexport type ScheduledJob<Ctx> = (input: ScheduledJobInput<Ctx>) => Promise<unknown>\n\nexport interface ScheduledDispatchOptions<Ctx> {\n entries: readonly ScheduleEntry[]\n jobs: Readonly<Record<string, ScheduledJob<Ctx>>>\n /** Waits before each retry of a failed job; empty (the default) runs a job once. Jobs that retry must be idempotent. */\n retryDelaysMs?: readonly number[]\n log?: {\n info(message: string, detail?: Record<string, unknown>): void\n error(message: string, detail?: Record<string, unknown>): void\n }\n /** Tests pass a no-op. */\n sleep?: (ms: number) => Promise<void>\n}\n\nexport interface ScheduledJobOutcome {\n job: string\n ok: boolean\n attempts: number\n durationMs: number\n error?: string\n}\n\nexport interface ScheduledDispatchResult {\n handled: boolean\n job: string | null\n /** Settles when the job's last attempt ends; never rejects. */\n done: Promise<ScheduledJobOutcome | null>\n}\n\nexport interface ScheduledDispatch<Ctx> {\n readonly entries: readonly ScheduleEntry[]\n /** Every invocation the table can make. */\n slots(): ScheduleSlot[]\n /** The job a tick runs, or null when no entry handles it. */\n jobFor(cron: string, scheduledTime: number | Date): string | null\n /**\n * Run the tick's one job. With `waitUntil` the job runs in the background and\n * this returns at once; without it, this waits for the job.\n */\n dispatch(\n event: { cron: string; scheduledTime: number | Date },\n context: Ctx,\n waitUntil?: (promise: Promise<unknown>) => void,\n ): Promise<ScheduledDispatchResult>\n}\n\nexport function createScheduledDispatch<Ctx>(options: ScheduledDispatchOptions<Ctx>): ScheduledDispatch<Ctx> {\n const problems = validateEntries(options.entries, Object.keys(options.jobs))\n if (problems.length) throw new Error(`scheduled dispatch: ${problems.join('; ')}`)\n const byCron = new Map(options.entries.map((entry) => [entry.cron, entry]))\n const retryDelaysMs = options.retryDelaysMs ?? []\n const log = options.log ?? {\n info: (message: string, detail?: Record<string, unknown>) => console.log(message, detail ?? ''),\n error: (message: string, detail?: Record<string, unknown>) => console.error(message, detail ?? ''),\n }\n const sleep = options.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)))\n\n function jobFor(cron: string, scheduledTime: number | Date): string | null {\n const entry = byCron.get(cron)\n if (!entry) return null\n if ('job' in entry) return entry.job\n const minute = new Date(scheduledTime).getUTCMinutes()\n return entry.minutes[minute] ?? null\n }\n\n async function runJob(job: string, cron: string, scheduledTime: Date, context: Ctx): Promise<ScheduledJobOutcome> {\n const run = options.jobs[job]!\n const started = Date.now()\n let lastError = ''\n for (let attempt = 1; attempt <= retryDelaysMs.length + 1; attempt += 1) {\n try {\n await run({ cron, scheduledTime, context, attempt })\n return { job, ok: true, attempts: attempt, durationMs: Date.now() - started }\n } catch (error) {\n lastError = error instanceof Error ? error.message : String(error)\n const delay = retryDelaysMs[attempt - 1]\n if (delay === undefined) break\n log.error(`[scheduled] ${job} failed; retrying`, { attempt, error: lastError })\n await sleep(delay)\n }\n }\n log.error(`[scheduled] ${job} failed`, { attempts: retryDelaysMs.length + 1, error: lastError })\n return { job, ok: false, attempts: retryDelaysMs.length + 1, durationMs: Date.now() - started, error: lastError }\n }\n\n return {\n entries: options.entries,\n slots: () => slotsOf(options.entries),\n jobFor,\n async dispatch(event, context, waitUntil) {\n const scheduledTime = new Date(event.scheduledTime)\n const job = jobFor(event.cron, scheduledTime)\n if (!job) {\n log.error(`[scheduled] no job handles cron \"${event.cron}\" at minute ${scheduledTime.getUTCMinutes()}; the configured crons and the schedule disagree`)\n return { handled: false, job: null, done: Promise.resolve(null) }\n }\n const done = runJob(job, event.cron, scheduledTime, context)\n if (waitUntil) waitUntil(done)\n else await done\n return { handled: true, job, done }\n },\n }\n}\n\n/** Every invocation a schedule table can make. */\nexport function slotsOf(entries: readonly ScheduleEntry[]): ScheduleSlot[] {\n return entries.flatMap((entry) => 'job' in entry\n ? [{ cron: entry.cron, job: entry.job }]\n : Object.entries(entry.minutes).map(([minute, job]) => ({ cron: entry.cron, minute: Number(minute), job })))\n}\n\nfunction validateEntries(entries: readonly ScheduleEntry[], jobNames: readonly string[]): string[] {\n const problems: string[] = []\n const known = new Set(jobNames)\n const seen = new Set<string>()\n for (const entry of entries) {\n if (seen.has(entry.cron)) problems.push(`cron \"${entry.cron}\" is listed twice`)\n seen.add(entry.cron)\n const fields = entry.cron.trim().split(/\\s+/)\n if (fields.length !== 5) problems.push(`cron \"${entry.cron}\" does not have five fields`)\n for (const slot of slotsOf([entry])) {\n if (!known.has(slot.job)) problems.push(`cron \"${entry.cron}\" names job \"${slot.job}\", which is not defined`)\n }\n if ('minutes' in entry) {\n const listed = minuteList(fields[0] ?? '')\n const mapped = Object.keys(entry.minutes).map(Number).sort((a, b) => a - b)\n if (!listed) problems.push(`cron \"${entry.cron}\" maps jobs by minute, so its minute field must be a list of minutes`)\n else if (listed.join(',') !== mapped.join(',')) {\n problems.push(`cron \"${entry.cron}\" lists minutes ${listed.join(',')} but maps jobs to ${mapped.join(',')}`)\n }\n }\n }\n return problems\n}\n\n/** The minutes of a plain minute list (`5` or `0,15,30`), or null for any other form. */\nfunction minuteList(field: string): number[] | null {\n if (!/^\\d{1,2}(,\\d{1,2})*$/.test(field)) return null\n const minutes = field.split(',').map(Number)\n if (minutes.some((minute) => minute > 59)) return null\n return [...new Set(minutes)].sort((a, b) => a - b)\n}\n\n// ── cron analysis ────────────────────────────────────────────────────────────\n\nfunction expandField(field: string, min: number, max: number): Set<number> | null {\n const values = new Set<number>()\n for (const part of field.split(',')) {\n const match = /^(\\*|\\d+(?:-\\d+)?)(?:\\/(\\d+))?$/.exec(part)\n if (!match) return null\n const [, range, stepText] = match\n const step = stepText ? Number(stepText) : 1\n if (!(step >= 1)) return null\n let lo = min\n let hi = max\n if (range !== '*') {\n const [a, b] = range!.split('-').map(Number)\n lo = a!\n hi = b ?? (stepText ? max : a!)\n }\n if (lo < min || hi > max || lo > hi) return null\n for (let value = lo; value <= hi; value += step) values.add(value)\n }\n return values\n}\n\n/**\n * The minutes of the day (0–1439, UTC) a cron fires, or null when it does not\n * fire every day (a day-of-month, month or weekday restriction) or cannot be read.\n */\nexport function cronDailyFirings(cron: string): Set<number> | null {\n const fields = cron.trim().split(/\\s+/)\n if (fields.length !== 5) return null\n const [minuteField, hourField, dayField, monthField, weekdayField] = fields as [string, string, string, string, string]\n if (dayField !== '*' || monthField !== '*' || (weekdayField !== '*' && weekdayField !== '?')) return null\n const minutes = expandField(minuteField, 0, 59)\n const hours = expandField(hourField, 0, 23)\n if (!minutes || !hours) return null\n const firings = new Set<number>()\n for (const hour of hours) for (const minute of minutes) firings.add(hour * 60 + minute)\n return firings\n}\n\n/** The longest wait, in minutes, between consecutive firings over a day, or null when they do not fire daily. */\nexport function longestGapMinutes(firings: Iterable<number>): number | null {\n const sorted = [...new Set(firings)].sort((a, b) => a - b)\n if (sorted.length === 0) return null\n let gap = sorted[0]! + 1440 - sorted[sorted.length - 1]!\n for (let index = 1; index < sorted.length; index += 1) gap = Math.max(gap, sorted[index]! - sorted[index - 1]!)\n return gap\n}\n\n/** The cron expressions a wrangler config declares, top level and per environment. */\nexport interface WranglerCrons {\n top: string[]\n envs: Record<string, string[]>\n}\n\n/** Read `[triggers] crons` (and `[env.<name>.triggers]`) from wrangler.toml, or `triggers.crons` from wrangler.json(c). */\nexport function parseWranglerCrons(source: string, format: 'toml' | 'json'): WranglerCrons {\n return format === 'json' ? parseJsonCrons(source) : parseTomlCrons(source)\n}\n\nfunction parseTomlCrons(source: string): WranglerCrons {\n const result: WranglerCrons = { top: [], envs: {} }\n let table = ''\n let collecting: string[] | null = null\n let buffer = ''\n for (const raw of source.split('\\n')) {\n const line = stripTomlComment(raw)\n if (collecting) {\n buffer += ` ${line}`\n if (line.includes(']')) {\n collecting.push(...quoted(buffer))\n collecting = null\n buffer = ''\n }\n continue\n }\n const header = /^\\s*\\[([^\\[\\]]+)\\]\\s*$/.exec(line)\n if (header) {\n table = header[1]!.trim()\n continue\n }\n const assignment = /^\\s*crons\\s*=\\s*(.*)$/.exec(line)\n if (!assignment) continue\n const envName = /^env\\.([^.]+)\\.triggers$/.exec(table)?.[1]\n const target = table === 'triggers' ? result.top : envName ? (result.envs[envName] ??= []) : null\n if (!target) continue\n const rest = assignment[1]!\n if (rest.includes(']')) target.push(...quoted(rest))\n else {\n collecting = target\n buffer = rest\n }\n }\n return result\n}\n\nfunction stripTomlComment(line: string): string {\n let quote: string | null = null\n for (let index = 0; index < line.length; index += 1) {\n const char = line[index]!\n if (quote) {\n if (char === quote) quote = null\n } else if (char === '\"' || char === \"'\") quote = char\n else if (char === '#') return line.slice(0, index)\n }\n return line\n}\n\nfunction quoted(text: string): string[] {\n return [...text.matchAll(/\"([^\"]*)\"|'([^']*)'/g)].map((match) => match[1] ?? match[2] ?? '')\n}\n\nfunction parseJsonCrons(source: string): WranglerCrons {\n const config = JSON.parse(stripJsonComments(source)) as {\n triggers?: { crons?: string[] }\n env?: Record<string, { triggers?: { crons?: string[] } }>\n }\n return {\n top: [...(config.triggers?.crons ?? [])],\n envs: Object.fromEntries(Object.entries(config.env ?? {})\n .filter(([, env]) => Array.isArray(env.triggers?.crons))\n .map(([name, env]) => [name, [...env.triggers!.crons!]])),\n }\n}\n\nfunction stripJsonComments(source: string): string {\n let out = ''\n let inString = false\n for (let index = 0; index < source.length; index += 1) {\n const char = source[index]!\n const next = source[index + 1]\n if (inString) {\n out += char\n if (char === '\\\\') out += source[++index] ?? ''\n else if (char === '\"') inString = false\n continue\n }\n if (char === '\"') {\n inString = true\n out += char\n } else if (char === '/' && next === '/') {\n while (index < source.length && source[index] !== '\\n') index += 1\n out += '\\n'\n } else if (char === '/' && next === '*') {\n index += 2\n while (index < source.length && !(source[index] === '*' && source[index + 1] === '/')) index += 1\n index += 1\n } else out += char\n }\n return out.replace(/,(\\s*[}\\]])/g, '$1')\n}\n\n// ── the invariant ────────────────────────────────────────────────────────────\n\n/** What one scheduled invocation was observed to run. */\nexport interface ObservedSlot {\n cron: string\n minute?: number\n /** The jobs the invocation ran; exactly one passes. */\n jobs: readonly string[]\n}\n\nexport interface IsolatedJobsInput {\n /** Every cron the deployment configures, from each wrangler environment. */\n configuredCrons: readonly string[]\n /** What each invocation runs: from {@link slotsOf} for a {@link createScheduledDispatch} table, or observed by driving the app's own handler. */\n slots: readonly ObservedSlot[]\n /** Jobs that recover and settle turns; each must run at least every 15 minutes. */\n recoveryJobs: readonly string[]\n /** Default {@link LAUNCH_BUDGETS.recoveryEveryMs}. */\n everyMs?: number\n}\n\n/** One job per invocation, the table and the deployment agree, and recovery runs at least every 15 minutes. */\nexport function checkIsolatedJobs(input: IsolatedJobsInput): InvariantVerdict {\n const everyMinutes = (input.everyMs ?? LAUNCH_BUDGETS.recoveryEveryMs) / 60_000\n const findings: string[] = []\n const configured = new Set(input.configuredCrons)\n const dispatched = new Set(input.slots.map((slot) => slot.cron))\n for (const cron of configured) if (!dispatched.has(cron)) findings.push(`configured cron \"${cron}\" runs no job: no schedule entry matches it`)\n for (const cron of dispatched) if (!configured.has(cron)) findings.push(`schedule cron \"${cron}\" is not configured, so it never runs`)\n for (const slot of input.slots) {\n const where = `cron \"${slot.cron}\"${slot.minute !== undefined ? ` at minute ${slot.minute}` : ''}`\n if (slot.jobs.length !== 1) findings.push(`${where} runs ${slot.jobs.length} jobs in one invocation (${slot.jobs.join(', ') || 'none'}); one failure ends the rest`)\n }\n const byCron = new Map<string, ObservedSlot[]>()\n for (const slot of input.slots) byCron.set(slot.cron, [...(byCron.get(slot.cron) ?? []), slot])\n for (const [cron, slots] of byCron) {\n const minuteSlots = slots.filter((slot) => slot.minute !== undefined)\n if (minuteSlots.length === 0) continue\n const listed = minuteList(cron.trim().split(/\\s+/)[0] ?? '')\n const mapped = [...new Set(minuteSlots.map((slot) => slot.minute!))].sort((a, b) => a - b)\n if (!listed || listed.join(',') !== mapped.join(',')) {\n findings.push(`cron \"${cron}\" fires at minutes ${listed?.join(',') ?? '(not a minute list)'} but jobs are mapped to ${mapped.join(',')}`)\n }\n }\n if (input.recoveryJobs.length === 0) findings.push('no turn recovery or orphan settlement job is declared')\n const gaps: Record<string, number | null> = {}\n for (const job of input.recoveryJobs) {\n const firings = new Set<number>()\n let daily = false\n for (const slot of input.slots) {\n if (slot.jobs.length !== 1 || slot.jobs[0] !== job) continue\n const fires = cronDailyFirings(slot.cron)\n if (!fires) continue\n daily = true\n for (const minuteOfDay of fires) if (slot.minute === undefined || minuteOfDay % 60 === slot.minute) firings.add(minuteOfDay)\n }\n const gap = daily ? longestGapMinutes(firings) : null\n gaps[job] = gap\n if (gap === null) findings.push(`recovery job \"${job}\" is not scheduled to run every day on its own`)\n else if (gap > everyMinutes) findings.push(`recovery job \"${job}\" can wait ${gap} minutes between runs; the most is ${everyMinutes}`)\n }\n return {\n invariant: 'isolated-jobs',\n subject: 'schedule',\n pass: findings.length === 0,\n details: findings.length ? findings : [\n `${input.slots.length} scheduled invocations, one job each, across ${configured.size} configured crons`,\n ...input.recoveryJobs.map((job) => `${job} runs at least every ${gaps[job]} minutes`),\n ],\n data: { configuredCrons: [...configured], slots: input.slots.length, recoveryGapsMinutes: gaps },\n }\n}\n","/**\n * Alarms that fire at 80% of a platform limit, before the limit fails a customer.\n *\n * On 2026-10-10 GTM recorded 19 platform-limit events (Worker memory, D1,\n * sandbox disk, snapshot count, key rate) and learned of each one after it had\n * failed someone. Each resource here has a declared limit; an observation at\n * or above 80% of it delivers a warning, and at or above the limit a critical\n * alert, through the same {@link AlertSink} the turn-health sweep uses (wrap it\n * in `createThrottledAlertSink` so a hot loop pages once).\n *\n * Where each observation comes from:\n * - `d1-rows-per-query`: {@link withD1LimitAlarms} reads `meta.rows_read` on every query.\n * - `worker-memory`: workerd exposes no heap reading, so the D1 wrapper observes\n * the bytes each response returns (the reads that overran memory today), and a\n * tail consumer reports an `exceededMemory` outcome at the full limit.\n * - `sandbox-disk` and `snapshot-count`: the Sandbox SDK's box usage and snapshot list, read on a schedule.\n * - `key-rate`: the per-key request counter the operator API keeps.\n */\nimport {\n createConsoleAlertSink,\n createMemoryThrottleStore,\n createMultiAlertSink,\n createThrottledAlertSink,\n createWebhookAlertSink,\n type AlertSink,\n type TurnHealthAlert,\n} from '../turn-health/sink.js'\nimport { LAUNCH_BUDGETS } from './catalog.js'\n\nexport type LimitResource = 'worker-memory' | 'd1-rows-per-query' | 'sandbox-disk' | 'snapshot-count' | 'key-rate'\n\nexport const LIMIT_RESOURCES: readonly LimitResource[] = [\n 'worker-memory',\n 'd1-rows-per-query',\n 'sandbox-disk',\n 'snapshot-count',\n 'key-rate',\n]\n\n/** The limit of each resource, in its own unit: bytes, rows, bytes, snapshots, requests per minute. */\nexport type LimitBudgets = Readonly<Record<LimitResource, number>>\n\nconst UNITS: Readonly<Record<LimitResource, string>> = {\n 'worker-memory': 'bytes',\n 'd1-rows-per-query': 'rows',\n 'sandbox-disk': 'bytes',\n 'snapshot-count': 'snapshots',\n 'key-rate': 'requests/min',\n}\n\nexport type LimitLevel = 'ok' | 'warning' | 'critical'\n\nexport interface LimitObservation {\n resource: LimitResource\n used: number\n limit: number\n /** used / limit. */\n share: number\n level: LimitLevel\n}\n\nexport interface LimitAlarms {\n readonly budgets: LimitBudgets\n /** Record one reading; a warning or critical reading is delivered before this resolves. */\n observe(resource: LimitResource, used: number, context?: { subject?: string; detail?: string }): Promise<LimitObservation>\n}\n\nexport interface LimitAlarmOptions {\n product: string\n budgets: LimitBudgets\n sink: AlertSink\n /** Default {@link LAUNCH_BUDGETS.alarmRatio}; a product cannot raise it past the limit itself. */\n ratio?: number\n now?: () => number\n}\n\nexport function createLimitAlarms(options: LimitAlarmOptions): LimitAlarms {\n const ratio = Math.min(options.ratio ?? LAUNCH_BUDGETS.alarmRatio, 1)\n const now = options.now ?? (() => Date.now())\n for (const resource of LIMIT_RESOURCES) {\n const limit = options.budgets[resource]\n if (!(typeof limit === 'number' && limit > 0 && Number.isFinite(limit))) {\n throw new Error(`limit alarms: ${resource} needs a positive limit`)\n }\n }\n return {\n budgets: options.budgets,\n async observe(resource, used, context) {\n const limit = options.budgets[resource]\n const share = used / limit\n const level: LimitLevel = share >= 1 ? 'critical' : share >= ratio ? 'warning' : 'ok'\n const observation = { resource, used, limit, share, level }\n if (level === 'ok') return observation\n const alert: TurnHealthAlert = {\n product: options.product,\n severity: level,\n // Keyed by product and resource only, so a throttled sink pages once per resource.\n key: `limit:${options.product}:${resource}`,\n title: `${options.product}: ${resource} at ${Math.round(share * 100)}% of its limit`,\n details: [\n `${formatAmount(used, resource)} of ${formatAmount(limit, resource)} ${UNITS[resource]}`,\n ...(context?.subject ? [`in ${context.subject}`] : []),\n ...(context?.detail ? [context.detail] : []),\n ],\n data: { resource, used, limit, share, ...(context?.subject ? { subject: context.subject } : {}) },\n at: now(),\n }\n await options.sink.deliver(alert)\n return observation\n },\n }\n}\n\nfunction formatAmount(value: number, resource: LimitResource): string {\n if (UNITS[resource] === 'bytes') return `${(value / (1024 * 1024)).toFixed(1)} MB`\n return Math.round(value).toLocaleString('en-US')\n}\n\n/** The budgets every product starts from: {@link LAUNCH_BUDGETS}' platform limits. */\nexport const DEFAULT_LIMIT_BUDGETS: LimitBudgets = {\n 'worker-memory': LAUNCH_BUDGETS.workerMemoryBytes,\n 'd1-rows-per-query': LAUNCH_BUDGETS.d1RowsPerQuery,\n 'sandbox-disk': LAUNCH_BUDGETS.sandboxDiskBytes,\n 'snapshot-count': LAUNCH_BUDGETS.snapshotCount,\n 'key-rate': LAUNCH_BUDGETS.keyRatePerMinute,\n}\n\nexport interface ProductLimitAlarmOptions {\n /** The product's name in alerts, e.g. `legal`. */\n product: string\n /** The ops webhook; without one alarms go to the console only. */\n webhookUrl?: string\n /** The few budgets this product measured differently, e.g. a larger sandbox disk. */\n overrides?: Partial<LimitBudgets>\n /** Replace the default console-and-webhook sink (tests, or an app with its own alert transport). */\n sink?: AlertSink\n}\n\n/**\n * A product's limit alarms with every default in place: {@link DEFAULT_LIMIT_BUDGETS}, the console and optional webhook\n * sink, and one page per resource every {@link LAUNCH_BUDGETS.alarmRepeatHours} hours. A product passes only what it\n * measured differently; Legal, Creative, Tax and GTM each kept a 40-line copy of this before.\n */\nexport function createProductLimitAlarms(options: ProductLimitAlarmOptions): LimitAlarms {\n const sink = options.sink ?? createThrottledAlertSink(createMultiAlertSink([\n createConsoleAlertSink((message) => console.error(message)),\n ...(options.webhookUrl ? [createWebhookAlertSink({ webhookUrl: options.webhookUrl })] : []),\n ]), { store: createMemoryThrottleStore(), windowMs: LAUNCH_BUDGETS.alarmRepeatHours * 60 * 60 * 1000 })\n return createLimitAlarms({ product: options.product, budgets: { ...DEFAULT_LIMIT_BUDGETS, ...options.overrides }, sink })\n}\n\n/** The D1 surface the wrapper measures; Cloudflare `D1Database` satisfies it. */\ninterface D1Measurable {\n prepare(sql: string): unknown\n}\n\n/**\n * The D1 binding with each query's rows read and response size observed.\n * Rows read come from `meta.rows_read` (the rows D1 scanned, not only those\n * returned). The response size is estimated from three sampled rows, so the\n * wrapper never serializes a whole response: that copy would double the memory\n * of the very read it watches.\n */\nexport function withD1LimitAlarms<D extends D1Measurable>(d1: D, alarms: LimitAlarms, options: { subject?: string } = {}): D {\n const observe = (sql: string, result: unknown) => {\n const meta = (result as { meta?: { rows_read?: unknown } } | null)?.meta\n const rowsRead = typeof meta?.rows_read === 'number' ? meta.rows_read : rowsOf(result)\n const pending: Promise<unknown>[] = []\n if (rowsRead !== null) pending.push(alarms.observe('d1-rows-per-query', rowsRead, { subject: options.subject, detail: sql }))\n const results = (result as { results?: unknown[] } | null)?.results ?? (Array.isArray(result) ? result : null)\n if (results && results.length > 0) {\n pending.push(alarms.observe('worker-memory', estimatedBytes(results), { subject: options.subject, detail: `one response: ${sql}` }))\n }\n // An alarm delivery failure must not fail the query that triggered it.\n return Promise.all(pending).catch(() => undefined)\n }\n // D1's batch() takes only its own statements, so a batch is given the unwrapped ones.\n const unwrapped = new WeakMap<object, object>()\n const statement = (target: object, sql: string): object => {\n const wrapped = new Proxy(target, {\n get(stmt, key) {\n const value = Reflect.get(stmt, key) as unknown\n if (typeof value !== 'function') return value\n if (key === 'bind') return (...params: unknown[]) => statement(value.apply(stmt, params) as object, sql)\n if (key === 'all' || key === 'raw' || key === 'run' || key === 'first') {\n return async (...args: unknown[]) => {\n const result: unknown = await value.apply(stmt, args)\n if (key !== 'first') await observe(sql, result)\n return result\n }\n }\n return value.bind(stmt)\n },\n })\n unwrapped.set(wrapped, target)\n return wrapped\n }\n return new Proxy(d1, {\n get(binding, key) {\n const value = Reflect.get(binding, key) as unknown\n if (key === 'prepare') {\n return (sql: string) => statement((value as (sql: string) => object).call(binding, sql), sql.replace(/\\s+/g, ' ').slice(0, 160))\n }\n if (key === 'batch' && typeof value === 'function') {\n return async (statements: object[]) => {\n const results = await (value as (s: object[]) => Promise<unknown[]>).call(binding, statements.map((stmt) => unwrapped.get(stmt) ?? stmt))\n for (const result of results ?? []) await observe('batch', result)\n return results\n }\n }\n return typeof value === 'function' ? value.bind(binding) : value\n },\n })\n}\n\n/** The serialized size of a result set, from its first, middle and last rows. */\nexport function estimatedBytes(results: readonly unknown[]): number {\n const picks = [...new Set([0, Math.floor(results.length / 2), results.length - 1])]\n const sampled = picks.reduce((sum, index) => sum + (JSON.stringify(results[index]) ?? '').length, 0)\n return Math.round((sampled / picks.length) * results.length)\n}\n\nfunction rowsOf(result: unknown): number | null {\n if (Array.isArray(result)) return result.length\n const results = (result as { results?: unknown } | null)?.results\n return Array.isArray(results) ? results.length : null\n}\n","/**\n * Pasted code reaches the chat.\n *\n * People paste commands, SQL and markup into a chat with an agent that runs\n * code; that is the product. On 2026-10-11 a chat message containing\n * `cat /etc/passwd` sent to gtm.tangle.tools got Cloudflare's 403 \"Attention\n * Required\" page instead of the app's own answer: a managed WAF rule matched\n * the text before the request reached the Worker. These samples are what a\n * person pastes; {@link isEdgeBlock} tells an edge block from the app's own\n * response, so an in-process check (`checkChatAcceptsCode`) and a live probe\n * against the deployed route judge the same thing.\n */\n\n/** Text people paste into a chat with an agent, each of which a WAF rule may match. */\nexport const PASTED_CODE_SAMPLES: readonly { name: string; text: string }[] = [\n { name: 'file read', text: 'cat /etc/passwd' },\n { name: 'install script', text: 'curl -fsSL https://example.com/i.sh | bash' },\n { name: 'command substitution', text: 'echo $(whoami) && rm -rf /tmp/x' },\n { name: 'SQL', text: 'SELECT * FROM users WHERE id=1 OR 1=1; --' },\n { name: 'markup', text: '<script>alert(1)</script>' },\n { name: 'fenced bash block', text: 'Why does this fail?\\n\\n```bash\\nset -euo pipefail\\nsudo cat /etc/shadow | grep root\\nwget -qO- http://127.0.0.1:8080/admin\\n```' },\n { name: 'path traversal', text: 'open ../../../../etc/hosts and tell me what it maps' },\n]\n\n/**\n * Whether a response is an edge block rather than the app's own answer: a\n * `cf-mitigated` header, or Cloudflare's HTML block page (\"Attention Required\",\n * \"Sorry, you have been blocked\"). Reads a clone, so the caller keeps the body.\n */\nexport async function isEdgeBlock(response: Response): Promise<boolean> {\n if (response.headers.has('cf-mitigated')) return true\n const type = response.headers.get('content-type') ?? ''\n const server = response.headers.get('server') ?? ''\n if (!/text\\/html/i.test(type) || !/cloudflare/i.test(server)) return false\n const body = await response.clone().text().catch(() => '')\n return /Attention Required|Sorry, you have been blocked/i.test(body)\n}\n"],"mappings":"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","names":[]}
@@ -1,12 +1,14 @@
1
1
  import {
2
- LAUNCH_BUDGETS,
3
- LAUNCH_INVARIANTS,
4
2
  LIMIT_RESOURCES,
5
3
  PASTED_CODE_SAMPLES,
6
- describeVerdict,
7
4
  isEdgeBlock,
8
5
  parseWranglerCrons
9
- } from "./chunk-LLO436QO.js";
6
+ } from "./chunk-QM7RTTDY.js";
7
+ import {
8
+ LAUNCH_BUDGETS,
9
+ LAUNCH_INVARIANTS,
10
+ describeVerdict
11
+ } from "./chunk-LRYY3OX7.js";
10
12
 
11
13
  // src/launch-invariants/testing.ts
12
14
  import { appendFileSync } from "fs";
@@ -495,12 +497,9 @@ async function checkCoalescedTurn(options) {
495
497
  };
496
498
  }
497
499
  var PLATFORM_LIMITS = {
498
- /** A Worker isolate's memory. */
499
- "worker-memory": 128 * MB,
500
- /** Platform snapshots per sandbox; the cap GTM hit on 2026-10-10. */
501
- "snapshot-count": 25,
502
- /** Requests per minute per operator key. */
503
- "key-rate": 60
500
+ "worker-memory": LAUNCH_BUDGETS.workerMemoryBytes,
501
+ "snapshot-count": LAUNCH_BUDGETS.snapshotCount,
502
+ "key-rate": LAUNCH_BUDGETS.keyRatePerMinute
504
503
  };
505
504
  async function checkLimitAlarms(options) {
506
505
  const findings = [];
@@ -548,4 +547,4 @@ export {
548
547
  PLATFORM_LIMITS,
549
548
  checkLimitAlarms
550
549
  };
551
- //# sourceMappingURL=chunk-ZRLKB4KB.js.map
550
+ //# sourceMappingURL=chunk-YWLZOPDS.js.map