@volter/twin-sentry 0.1.37 → 1.0.1

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 (189) hide show
  1. package/README.md +28 -140
  2. package/api/spec/SOURCE.md +32 -0
  3. package/api/spec/doc-examples.json +12 -0
  4. package/api/spec/openapi.json.gz +0 -0
  5. package/api/spec/patches.json +5350 -0
  6. package/api/spec/sources.json +122 -0
  7. package/api/src/fetch.ts +18 -0
  8. package/api/src/generated/surface.gen.json +1 -0
  9. package/api/src/generated/ui.gen.json +1 -0
  10. package/api/src/index.ts +3 -0
  11. package/api/src/manifest.ts +137 -0
  12. package/api/src/semantics/api.ts +247 -0
  13. package/api/src/semantics/around.ts +36 -0
  14. package/api/src/semantics/doors.ts +76 -0
  15. package/api/src/semantics/index.ts +19 -0
  16. package/api/src/semantics/shared.ts +31 -0
  17. package/api/src/semantics/states.ts +17 -0
  18. package/dist/api/spec/SOURCE.md +32 -0
  19. package/dist/api/spec/doc-examples.json +12 -0
  20. package/dist/api/spec/openapi.json.gz +0 -0
  21. package/dist/api/spec/patches.json +5350 -0
  22. package/dist/api/spec/sources.json +122 -0
  23. package/dist/api/src/fetch.d.ts +2 -0
  24. package/dist/api/src/fetch.js +17 -0
  25. package/dist/api/src/fetch.ts +18 -0
  26. package/dist/api/src/generated/surface.gen.json +1 -0
  27. package/dist/api/src/generated/ui.gen.json +1 -0
  28. package/dist/api/src/index.d.ts +2 -0
  29. package/dist/api/src/index.js +3 -0
  30. package/dist/api/src/index.ts +3 -0
  31. package/dist/api/src/manifest.d.ts +2 -0
  32. package/dist/api/src/manifest.js +136 -0
  33. package/dist/api/src/manifest.ts +137 -0
  34. package/dist/api/src/semantics/api.d.ts +13 -0
  35. package/dist/api/src/semantics/api.js +269 -0
  36. package/dist/api/src/semantics/api.ts +247 -0
  37. package/dist/api/src/semantics/around.d.ts +2 -0
  38. package/dist/api/src/semantics/around.js +41 -0
  39. package/dist/api/src/semantics/around.ts +36 -0
  40. package/dist/api/src/semantics/doors.d.ts +5 -0
  41. package/dist/api/src/semantics/doors.js +71 -0
  42. package/dist/api/src/semantics/doors.ts +76 -0
  43. package/dist/api/src/semantics/index.d.ts +2 -0
  44. package/dist/api/src/semantics/index.js +15 -0
  45. package/dist/api/src/semantics/index.ts +19 -0
  46. package/dist/api/src/semantics/shared.d.ts +14 -0
  47. package/dist/api/src/semantics/shared.js +26 -0
  48. package/dist/api/src/semantics/shared.ts +31 -0
  49. package/dist/api/src/semantics/states.d.ts +24 -0
  50. package/dist/api/src/semantics/states.js +17 -0
  51. package/dist/api/src/semantics/states.ts +17 -0
  52. package/dist/ingest/spec/SOURCE.md +30 -0
  53. package/dist/ingest/spec/authentication.mdx.gz +0 -0
  54. package/dist/ingest/spec/compression.mdx.gz +0 -0
  55. package/dist/ingest/spec/doc-examples.json +216 -0
  56. package/dist/ingest/spec/envelope-items.mdx.gz +0 -0
  57. package/dist/ingest/spec/envelopes.mdx.gz +0 -0
  58. package/dist/ingest/spec/openapi.json +178 -0
  59. package/dist/ingest/spec/rate-limiting.mdx.gz +0 -0
  60. package/dist/ingest/spec/sources.json +25 -0
  61. package/dist/ingest/spec/transport.mdx.gz +0 -0
  62. package/dist/ingest/spec/write-spec.ts +192 -0
  63. package/dist/ingest/src/fetch.d.ts +2 -0
  64. package/dist/ingest/src/fetch.js +15 -0
  65. package/dist/ingest/src/fetch.ts +16 -0
  66. package/dist/ingest/src/generated/surface.gen.json +1 -0
  67. package/dist/ingest/src/generated/ui.gen.json +1 -0
  68. package/dist/ingest/src/index.d.ts +2 -0
  69. package/dist/ingest/src/index.js +3 -0
  70. package/dist/ingest/src/index.ts +3 -0
  71. package/dist/ingest/src/manifest.d.ts +2 -0
  72. package/dist/ingest/src/manifest.js +37 -0
  73. package/dist/ingest/src/manifest.ts +42 -0
  74. package/dist/ingest/src/semantics/api.d.ts +2 -0
  75. package/dist/ingest/src/semantics/api.js +240 -0
  76. package/dist/ingest/src/semantics/api.ts +188 -0
  77. package/dist/ingest/src/semantics/around.d.ts +2 -0
  78. package/dist/ingest/src/semantics/around.js +6 -0
  79. package/dist/ingest/src/semantics/around.ts +7 -0
  80. package/dist/ingest/src/semantics/index.d.ts +2 -0
  81. package/dist/ingest/src/semantics/index.js +4 -0
  82. package/dist/ingest/src/semantics/index.ts +8 -0
  83. package/dist/ingest/src/semantics/shared.d.ts +4 -0
  84. package/dist/ingest/src/semantics/shared.js +2 -0
  85. package/dist/ingest/src/semantics/shared.ts +7 -0
  86. package/dist/ingest/src/semantics/states.d.ts +1 -0
  87. package/dist/ingest/src/semantics/states.js +2 -0
  88. package/dist/ingest/src/semantics/states.ts +2 -0
  89. package/dist/journeys/coverage.json +205 -0
  90. package/dist/journeys/customer-life.json +921 -0
  91. package/dist/journeys/customer-life.outline.md +51 -0
  92. package/dist/journeys/decisions.json +170 -0
  93. package/dist/journeys/demand.json +92 -0
  94. package/dist/journeys/first-use.json +22 -0
  95. package/dist/journeys/score.json +231 -0
  96. package/dist/journeys/unreachable.json +29 -0
  97. package/dist/journeys/vendor-examples.json +474 -0
  98. package/dist/spec/sources.json +76 -0
  99. package/dist/src/cli.js +12 -27
  100. package/dist/src/fetch.d.ts +2 -0
  101. package/dist/src/fetch.js +9 -0
  102. package/dist/src/index.d.ts +5 -15
  103. package/dist/src/index.js +13 -75
  104. package/dist/src/manifest.d.ts +2 -0
  105. package/dist/src/manifest.js +35 -0
  106. package/dist/src/semantics/shared.d.ts +88 -0
  107. package/dist/src/semantics/shared.js +407 -0
  108. package/dist/src/server.d.ts +7 -0
  109. package/dist/src/server.js +7 -0
  110. package/generated/pack-facts.json +101 -0
  111. package/ingest/spec/SOURCE.md +30 -0
  112. package/ingest/spec/authentication.mdx.gz +0 -0
  113. package/ingest/spec/compression.mdx.gz +0 -0
  114. package/ingest/spec/doc-examples.json +216 -0
  115. package/ingest/spec/envelope-items.mdx.gz +0 -0
  116. package/ingest/spec/envelopes.mdx.gz +0 -0
  117. package/ingest/spec/openapi.json +178 -0
  118. package/ingest/spec/rate-limiting.mdx.gz +0 -0
  119. package/ingest/spec/sources.json +25 -0
  120. package/ingest/spec/transport.mdx.gz +0 -0
  121. package/ingest/spec/write-spec.ts +192 -0
  122. package/ingest/src/fetch.ts +16 -0
  123. package/ingest/src/generated/surface.gen.json +1 -0
  124. package/ingest/src/generated/ui.gen.json +1 -0
  125. package/ingest/src/index.ts +3 -0
  126. package/ingest/src/manifest.ts +42 -0
  127. package/ingest/src/semantics/api.ts +188 -0
  128. package/ingest/src/semantics/around.ts +7 -0
  129. package/ingest/src/semantics/index.ts +8 -0
  130. package/ingest/src/semantics/shared.ts +7 -0
  131. package/ingest/src/semantics/states.ts +2 -0
  132. package/journeys/coverage.json +205 -0
  133. package/journeys/customer-life.json +921 -0
  134. package/journeys/customer-life.outline.md +51 -0
  135. package/journeys/decisions.json +170 -0
  136. package/journeys/demand.json +92 -0
  137. package/journeys/first-use.json +22 -0
  138. package/journeys/score.json +231 -0
  139. package/journeys/unreachable.json +29 -0
  140. package/journeys/vendor-examples.json +474 -0
  141. package/package.json +22 -47
  142. package/spec/sources.json +76 -0
  143. package/src/cli.ts +12 -24
  144. package/src/fetch.ts +10 -0
  145. package/src/index.ts +13 -143
  146. package/src/manifest.ts +41 -0
  147. package/src/semantics/shared.ts +439 -0
  148. package/src/server.ts +8 -0
  149. package/client/sentry-mirror.css +0 -99
  150. package/client/sentry-mirror.tsx +0 -352
  151. package/dist/client/sentry-mirror.bundle.js +0 -388
  152. package/dist/client/sentry-mirror.css +0 -99
  153. package/dist/client/sentry-mirror.d.ts +0 -17
  154. package/dist/client/sentry-mirror.js +0 -156
  155. package/dist/client/sentry-mirror.tsx +0 -352
  156. package/dist/src/sentry-budget.d.ts +0 -50
  157. package/dist/src/sentry-budget.js +0 -145
  158. package/dist/src/sentry-capabilities.d.ts +0 -3
  159. package/dist/src/sentry-capabilities.js +0 -1180
  160. package/dist/src/sentry-conformance.d.ts +0 -22
  161. package/dist/src/sentry-conformance.js +0 -97
  162. package/dist/src/sentry-connector.d.ts +0 -77
  163. package/dist/src/sentry-connector.js +0 -226
  164. package/dist/src/sentry-events.d.ts +0 -54
  165. package/dist/src/sentry-events.js +0 -132
  166. package/dist/src/sentry-ingest.d.ts +0 -137
  167. package/dist/src/sentry-ingest.js +0 -387
  168. package/dist/src/sentry-mirror-ui.d.ts +0 -38
  169. package/dist/src/sentry-mirror-ui.js +0 -162
  170. package/dist/src/sentry-perform-harness.d.ts +0 -9
  171. package/dist/src/sentry-perform-harness.js +0 -20
  172. package/dist/src/sentry-server.d.ts +0 -14
  173. package/dist/src/sentry-server.js +0 -27
  174. package/dist/src/sentry-twin.d.ts +0 -17
  175. package/dist/src/sentry-twin.js +0 -1739
  176. package/dist/test-fixtures/sentry-openapi-operations.SOURCE.md +0 -24
  177. package/dist/test-fixtures/sentry-openapi-operations.json +0 -1837
  178. package/src/sentry-budget.ts +0 -171
  179. package/src/sentry-capabilities.ts +0 -1222
  180. package/src/sentry-conformance.ts +0 -110
  181. package/src/sentry-connector.ts +0 -260
  182. package/src/sentry-events.ts +0 -172
  183. package/src/sentry-ingest.ts +0 -471
  184. package/src/sentry-mirror-ui.ts +0 -162
  185. package/src/sentry-perform-harness.ts +0 -19
  186. package/src/sentry-server.ts +0 -35
  187. package/src/sentry-twin.ts +0 -1615
  188. package/test-fixtures/sentry-openapi-operations.SOURCE.md +0 -24
  189. package/test-fixtures/sentry-openapi-operations.json +0 -1837
@@ -1,171 +0,0 @@
1
- // Sentry's CLIENT-SIDE RATE BUDGET — the pack's DECLARATION (the numbers) plus the thin typed
2
- // bindings `liveSentryExecute` uses. The MECHANISM — the durable token-keyed ledger, the rolling
3
- // window, reserve-under-lock, the `Retry-After`/429 cooldown, fail-CLOSED on a corrupt ledger —
4
- // lives ONCE in the vendor-agnostic kernel (`@volter/world-core` → `rateBudget.ts`). Read that module's
5
- // header for the full rationale AND for the honest list of what the guard does not guarantee (an
6
- // injected clock or ledger path still defeats it — it guards carelessness, not malice).
7
- //
8
- // ── WHY THIS EXISTS ─────────────────────────────────────────────────────────────────────────
9
- // A real ~4.5-DAY vendor lockout (Figma, 2026-07-25) happened because raw API calls were made
10
- // outside the pack's connector — no cache, no batching, no ceiling. Discipline only binds the code
11
- // that follows it; a BUDGET binds the code that does not.
12
- //
13
- // ── HOW THE CEILING WAS CHOSEN: NO SCALAR IS PUBLISHED ──────────────────────────────────────
14
- // Sentry does NOT publish a scalar limit. From https://docs.sentry.io/api/ratelimits/ (read
15
- // 2026-07-26): every request is rate limited on TWO axes — a requests-per-second fixed window and a
16
- // concurrency cap — and "each endpoint has its own maximum number of requests and window size". No
17
- // figure is stated anywhere. (Plan-dependence is widely reported but is NOT on that page, so it is
18
- // not quoted as if it were — §9 removed an earlier quotation mark around it, 2026-07-26.) Over the limit you get
19
- // 429 + `Retry-After`, and every response carries `X-Sentry-Rate-Limit-Limit` / `-Remaining` /
20
- // `-Reset` / `-ConcurrentLimit` / `-ConcurrentRemaining`. The docs' own advice is to prefer
21
- // webhooks over polling.
22
- //
23
- // So this budget deliberately does NOT model the vendor's limit, and the numbers below are NOT
24
- // derived from one. Because there is no documented figure to justify going higher, the ceiling is
25
- // pinned at the kernel's own undeclared fallback in every dimension: 60 weighted units per 60s at
26
- // `defaultWeight` 2 — 30 calls a minute, exactly `DEFAULT_RATE_BUDGET`, with no endpoint priced
27
- // CHEAPER than the fallback would price it. What this declaration adds over the fallback is
28
- // therefore not headroom, it is RESOLUTION: the paginated firehose gets priced up.
29
- //
30
- // The one thing the guard genuinely buys here is the COOLDOWN: `X-Sentry-Rate-Limit-Remaining: 0`
31
- // and `Retry-After` are both read off the response and turn into a persisted refusal, so the
32
- // vendor's first "back off" becomes a hard client-side stop instead of a retry storm — which is the
33
- // only correct response to a limit whose real value you cannot know client-side.
34
- //
35
- // ── HOW THE WEIGHTS WERE CHOSEN (a judgement call, stated as one) ────────────────────────────
36
- // Since no per-endpoint figure is published, no weight here can claim to be arithmetic. The single
37
- // rule prices the ISSUE/EVENT list reads at 4 — double the default. They are the cursor-paginated
38
- // firehose (an org's whole event stream, page after page), they are the thing the docs single out
39
- // as "use webhooks instead of polling this", and they play exactly the role `/images` renders
40
- // played in the Figma lockout: the endpoint a pagination bug turns into hundreds of calls. At
41
- // weight 4 at most 15 land in a window.
42
- import {
43
- declareRateBudget,
44
- rateBudgetPath,
45
- rateBudgetWeight,
46
- RateBudget,
47
- type RateBudgetDeclaration,
48
- type RateBudgetOptions,
49
- type RateBudgetReservation,
50
- type RateBudgetSnapshot,
51
- } from '@volter/world-core';
52
-
53
- const VENDOR = 'sentry';
54
-
55
- /** Rolling window, in ms. Spend older than this is pruned. */
56
- export const SENTRY_BUDGET_WINDOW_MS = 60_000;
57
-
58
- /**
59
- * Weighted units allowed inside one window. 60/60s at `defaultWeight` 2 = 30 calls a minute —
60
- * EXACTLY the kernel's undeclared fallback, because Sentry publishes no scalar that would justify
61
- * more. See the header.
62
- */
63
- export const SENTRY_BUDGET_CEILING = 60;
64
-
65
- /** Seconds. A `Retry-After` above this means the token is throttled hard — fail loudly, don't sleep. */
66
- export const SENTRY_BUDGET_MAX_RETRY_AFTER_S = 300;
67
-
68
- /** Per-call cost, keyed by `"<METHOD> <path>"`. No figure here can claim to be published. */
69
- export const SENTRY_CALL_WEIGHTS = {
70
- /** `…/issues/` and `…/events/` list reads — the cursor-paginated firehose the docs say not to poll. */
71
- stream: 4,
72
- /** Everything else: project/release/issue reads, issue updates. */
73
- other: 2,
74
- } as const;
75
-
76
- /** THE PACK'S DECLARATION — pure data, the only Sentry-specific thing in the whole budget. */
77
- export const SENTRY_RATE_BUDGET: RateBudgetDeclaration = {
78
- windowMs: SENTRY_BUDGET_WINDOW_MS,
79
- ceiling: SENTRY_BUDGET_CEILING,
80
- defaultWeight: SENTRY_CALL_WEIGHTS.other,
81
- maxRetryAfterSeconds: SENTRY_BUDGET_MAX_RETRY_AFTER_S,
82
- rules: [
83
- // `(/|$)` rather than a bare trailing `/`: the pricer normalizes a trailing slash away (so
84
- // `…/issues/` and `…/issues` are one endpoint), and a rule insisting on the slash would miss both.
85
- // (§9 finding, 2026-07-26.)
86
- { match: '^GET /api/0/(projects|organizations)/[^/]+/[^/]+/(issues|events)(/|$)', weight: SENTRY_CALL_WEIGHTS.stream },
87
- ],
88
- reason:
89
- 'Sentry publishes NO scalar limit (docs.sentry.io/api/ratelimits, read 2026-07-26): every request ' +
90
- 'is limited on a requests-per-second fixed window AND a concurrency cap, "each endpoint has its ' +
91
- 'own maximum number of requests and window size", and no figure is stated anywhere. ' +
92
- 'Over it: 429 + Retry-After, with X-Sentry-Rate-Limit-Limit/-Remaining/-Reset/-ConcurrentLimit ' +
93
- 'headers on every response, and the docs advise webhooks over polling. Because no published ' +
94
- 'figure justifies going higher, the ceiling is pinned at the kernel fallback in EVERY dimension ' +
95
- '— 60 units / 60s at defaultWeight 2 = 30 calls/min, and no endpoint is priced cheaper than the ' +
96
- 'fallback would price it; the declaration buys resolution, not headroom. The issue/event LIST ' +
97
- 'reads cost 4 (the cursor-paginated firehose the docs single out, and the shape a pagination bug ' +
98
- 'turns into hundreds of calls) — a judgement call, not a published cost, since none exists. What ' +
99
- "the guard genuinely buys here is the cooldown: the vendor's first back-off signal becomes a " +
100
- 'persisted client-side stop instead of a retry storm.',
101
- };
102
-
103
- // Declared at module load, so merely importing this module (which `sentry-connector.ts` does) is
104
- // enough to arm the real ceiling. `RateBudget` reads its policy live precisely so this declaration
105
- // takes effect the moment it lands, and constructing through the subclass below (which imports this
106
- // module) is what makes the ordering a non-issue in practice.
107
- declareRateBudget(VENDOR, SENTRY_RATE_BUDGET);
108
-
109
- /**
110
- * Price one call. The key is `"<METHOD> <path>"` with the query string split off, so a rule can
111
- * price by method without the kernel knowing anything about Sentry. An unclassified endpoint still
112
- * costs `defaultWeight` — nothing is ever free.
113
- */
114
- export function sentryCallWeight(method: string, path: string): number {
115
- const { bare, query } = splitQuery(path);
116
- // UPPER-CASE the method: `fetch` normalizes a known lowercase method before sending, so
117
- // `execute('post', …)` really does issue a POST and must be priced as one.
118
- return rateBudgetWeight(VENDOR, `${String(method).toUpperCase()} ${bare}`, query);
119
- }
120
-
121
- /**
122
- * `/v1/x?a=1` -> `{ bare: '/v1/x', query: { a: '1' } }`. Rules match the path; `whenQuery*` the query.
123
- *
124
- * NORMALIZED, because the anchored rules are otherwise trivially evaded (§9 finding, 2026-07-26):
125
- * `fetch` upper-cases a known method before sending, so `execute('post', …)` issues a real WRITE
126
- * that a `^POST ` rule would price as a read; and a trailing slash makes a path miss a `$` anchor
127
- * while most routers treat it as the same endpoint. Both are input variations, not attacks, and
128
- * either one silently voids the "expensive endpoints are priced up" claim the ceiling rests on.
129
- */
130
- function splitQuery(path: string): { bare: string; query: Record<string, string> } {
131
- const at = path.indexOf('?');
132
- const query: Record<string, string> = {};
133
- if (at !== -1) for (const [k, v] of new URLSearchParams(path.slice(at + 1))) query[k] = v;
134
- const raw = at === -1 ? path : path.slice(0, at);
135
- // Collapse a trailing slash, but never turn the root path into the empty string.
136
- const bare = raw.length > 1 && raw.endsWith('/') ? raw.replace(/\/+$/, '') : raw;
137
- return { bare, query };
138
- }
139
-
140
- /** Where Sentry's ledger lives. Token-keyed and cwd-independent by default (limits attach to the
141
- * auth token's organization, so a cwd-scoped ledger would hand the same token a fresh allowance in
142
- * every checkout, worktree and CI matrix leg); pass `root` to opt into world-scoped accounting. */
143
- export function sentryBudgetPath(opts: { root?: string; token?: string } | string = {}): string {
144
- const o = typeof opts === 'string' ? { root: opts } : opts;
145
- // VENDOR spread LAST: a loosely-typed `{ vendor: 'other', … }` slipping through (TypeScript's
146
- // excess-property check only catches object literals) must not redirect this pack's ledger to
147
- // another vendor's file.
148
- return rateBudgetPath({ ...o, vendor: VENDOR });
149
- }
150
-
151
- /** Construction options for Sentry's budget. The vendor is fixed; everything else may only TIGHTEN. */
152
- export type SentryBudgetOptions = Omit<RateBudgetOptions, 'vendor'>;
153
-
154
- /**
155
- * Sentry's budget — the shared kernel guard bound to this vendor's declaration. A real subclass,
156
- * not an alias, so `budget instanceof SentryBudget` in `liveSentryExecute` means "a budget that
157
- * accounts against SENTRY's ledger under SENTRY's ceiling": another vendor's `RateBudget` (with its
158
- * own, possibly larger, ceiling) is NOT assignable there.
159
- */
160
- export class SentryBudget extends RateBudget {
161
- constructor(opts: SentryBudgetOptions = {}) {
162
- super({ ...opts, vendor: VENDOR });
163
- }
164
- }
165
-
166
- /** The typed refusal. One error class shared with every other vendor's budget; `err.vendor` says
167
- * which one refused, and `err.kind` says why. */
168
- export { RateBudgetError as SentryBudgetError } from '@volter/world-core';
169
- export type { RateBudgetErrorKind as SentryBudgetErrorKind } from '@volter/world-core';
170
- export type SentryBudgetReservation = RateBudgetReservation;
171
- export type SentryBudgetSnapshot = RateBudgetSnapshot;