@pome-sh/checks 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +88 -0
  3. package/dist/_types/sdk/check-discrimination.d.ts +7 -0
  4. package/dist/_types/sdk/check-state-path.d.ts +71 -0
  5. package/dist/_types/sdk/checks.d.ts +93 -0
  6. package/dist/_types/sdk/db.d.ts +44 -0
  7. package/dist/_types/sdk/failure-injection-rules.d.ts +32 -0
  8. package/dist/_types/twin-github/src/check-kind.d.ts +3 -0
  9. package/dist/_types/twin-github/src/check-state.d.ts +77 -0
  10. package/dist/_types/twin-github/src/checks.d.ts +50 -0
  11. package/dist/_types/twin-github/src/seed.d.ts +92 -0
  12. package/dist/_types/twin-github/src/types.d.ts +244 -0
  13. package/dist/_types/twin-gmail/src/check-kind.d.ts +3 -0
  14. package/dist/_types/twin-gmail/src/check-state.d.ts +138 -0
  15. package/dist/_types/twin-gmail/src/checks.d.ts +19 -0
  16. package/dist/_types/twin-gmail/src/faults.d.ts +21 -0
  17. package/dist/_types/twin-gmail/src/seed.d.ts +247 -0
  18. package/dist/_types/twin-gmail/src/types.d.ts +150 -0
  19. package/dist/_types/twin-linear/src/check-kind.d.ts +3 -0
  20. package/dist/_types/twin-linear/src/check-state.d.ts +123 -0
  21. package/dist/_types/twin-linear/src/checks.d.ts +29 -0
  22. package/dist/_types/twin-linear/src/seed.d.ts +166 -0
  23. package/dist/_types/twin-linear/src/types.d.ts +366 -0
  24. package/dist/_types/twin-slack/src/check-kind.d.ts +3 -0
  25. package/dist/_types/twin-slack/src/check-state.d.ts +107 -0
  26. package/dist/_types/twin-slack/src/checks.d.ts +14 -0
  27. package/dist/_types/twin-slack/src/seed.d.ts +62 -0
  28. package/dist/_types/twin-slack/src/types.d.ts +182 -0
  29. package/dist/_types/twin-stripe/src/check-kind.d.ts +3 -0
  30. package/dist/_types/twin-stripe/src/check-state.d.ts +107 -0
  31. package/dist/_types/twin-stripe/src/checks.d.ts +22 -0
  32. package/dist/_types/twin-stripe/src/seed.d.ts +213 -0
  33. package/dist/_types/twin-stripe/src/types.d.ts +252 -0
  34. package/dist/_types/wire/index.d.ts +23 -0
  35. package/dist/_types/wire/otel/event-schema.d.ts +424 -0
  36. package/dist/_types/wire/otel/index.d.ts +25 -0
  37. package/dist/_types/wire/otel/legacy-shim.d.ts +105 -0
  38. package/dist/_types/wire/otel/map-span.d.ts +64 -0
  39. package/dist/_types/wire/otel/nano.d.ts +26 -0
  40. package/dist/_types/wire/otel/project.d.ts +42 -0
  41. package/dist/_types/wire/otel/semconv.d.ts +57 -0
  42. package/dist/_types/wire/otel/span-event.d.ts +198 -0
  43. package/dist/_types/wire/recorder-events.d.ts +526 -0
  44. package/dist/_types/wire/redaction.d.ts +2 -0
  45. package/dist/chunk-4WXX5VPA.js +238 -0
  46. package/dist/chunk-5SJ4PVO5.js +981 -0
  47. package/dist/chunk-GYFGMULG.js +1138 -0
  48. package/dist/chunk-IVENH4KX.js +814 -0
  49. package/dist/chunk-NORTPYDQ.js +1015 -0
  50. package/dist/chunk-SJ6SVRAA.js +432 -0
  51. package/dist/chunk-ZXE6LAM3.js +1 -0
  52. package/dist/dsl.d.ts +11 -0
  53. package/dist/dsl.js +2 -0
  54. package/dist/github.d.ts +2 -0
  55. package/dist/github.js +2 -0
  56. package/dist/gmail.d.ts +2 -0
  57. package/dist/gmail.js +2 -0
  58. package/dist/index.d.ts +163 -0
  59. package/dist/index.js +24 -0
  60. package/dist/linear.d.ts +2 -0
  61. package/dist/linear.js +2 -0
  62. package/dist/slack.d.ts +2 -0
  63. package/dist/slack.js +2 -0
  64. package/dist/stripe.d.ts +2 -0
  65. package/dist/stripe.js +2 -0
  66. package/package.json +93 -0
@@ -0,0 +1,814 @@
1
+ import { oneOf, statePath, defineCheck, VACUITY_SENTINEL_NUMBER, VACUITY_SENTINEL, childStatePath } from './chunk-4WXX5VPA.js';
2
+ import { z } from 'zod';
3
+
4
+ // ../twin-stripe/dist/src/check-params.js
5
+ var chargeId = {
6
+ name: "charge",
7
+ pattern: "[A-Za-z0-9_-]+",
8
+ example: "ch_test_200",
9
+ render: (value) => value,
10
+ parse: (raw) => raw
11
+ };
12
+ var minorUnitAmount = {
13
+ name: "amount",
14
+ pattern: "\\d+",
15
+ example: "10000",
16
+ render: (value) => value,
17
+ parse: (raw) => raw
18
+ };
19
+ var rowCount = {
20
+ name: "count",
21
+ pattern: "\\d+",
22
+ example: "1",
23
+ render: (value) => value,
24
+ parse: (raw) => raw
25
+ };
26
+ var PAYMENT_INTENT_STATUSES = [
27
+ "requires_payment_method",
28
+ "requires_confirmation",
29
+ "requires_action",
30
+ "processing",
31
+ "requires_capture",
32
+ "canceled",
33
+ "succeeded"
34
+ ];
35
+ var paymentIntentStatus = oneOf("status", PAYMENT_INTENT_STATUSES, "succeeded");
36
+ var CHARGE_STATUSES = [
37
+ "pending",
38
+ "succeeded",
39
+ "failed"
40
+ ];
41
+ var chargeStatus = oneOf("status", CHARGE_STATUSES, "succeeded");
42
+ var EVENT_TYPES = [
43
+ "payment_intent.created",
44
+ "payment_intent.requires_action",
45
+ "payment_intent.processing",
46
+ "payment_intent.succeeded",
47
+ "payment_intent.payment_failed",
48
+ "payment_intent.canceled",
49
+ "charge.succeeded",
50
+ "charge.failed",
51
+ "charge.refunded",
52
+ "refund.created",
53
+ "customer.created",
54
+ "customer.updated",
55
+ "customer.deleted",
56
+ "payment_method.attached",
57
+ "payment_method.detached"
58
+ ];
59
+ var stripeEventType = oneOf("event_type", EVENT_TYPES, "payment_intent.succeeded");
60
+ var ERROR_TYPES = [
61
+ "invalid_request_error",
62
+ "api_error",
63
+ "card_error",
64
+ "idempotency_error",
65
+ "rate_limit_error"
66
+ ];
67
+ var stripeErrorType = oneOf("error_type", ERROR_TYPES, "invalid_request_error");
68
+
69
+ // ../twin-stripe/dist/src/check-state.js
70
+ var PAYMENT_INTENTS_PATH = statePath("payment_intents");
71
+ var CHARGES_PATH = statePath("charges");
72
+ var EVENTS_PATH = statePath("events");
73
+ var REFUNDS_PATH = statePath("refunds");
74
+ function missSkip(miss) {
75
+ const outcome = { passed: false, status: "skipped", reason: miss.missing };
76
+ if (miss.searched === void 0)
77
+ return outcome;
78
+ return { ...outcome, evidenceStatePaths: [miss.searched] };
79
+ }
80
+ function resolveCharge(state, chargeId2) {
81
+ if (state.charges == null)
82
+ return { missing: "state_incomplete" };
83
+ const index = state.charges.findIndex((charge2) => charge2.id === chargeId2);
84
+ if (index < 0)
85
+ return { missing: `charge_not_found ("${chargeId2}")`, searched: CHARGES_PATH };
86
+ return { found: state.charges[index], path: childStatePath(CHARGES_PATH, index) };
87
+ }
88
+ function refundsOnCharge(state, chargeId2) {
89
+ const charge2 = resolveCharge(state, chargeId2);
90
+ if ("missing" in charge2)
91
+ return charge2;
92
+ if (state.refunds == null)
93
+ return { missing: "state_incomplete" };
94
+ return {
95
+ found: state.refunds.filter((refund2) => refund2.charge === chargeId2),
96
+ path: REFUNDS_PATH
97
+ };
98
+ }
99
+
100
+ // ../twin-stripe/dist/src/check-worlds.js
101
+ function stripeState(overrides = {}) {
102
+ return {
103
+ payment_intents: [],
104
+ charges: [],
105
+ balance_transactions: [],
106
+ events: [],
107
+ refunds: [],
108
+ ...overrides
109
+ };
110
+ }
111
+ function finalWorld(final) {
112
+ return { seed: null, final, tape: null };
113
+ }
114
+ function tapeWorld(tape) {
115
+ return { seed: null, final: stripeState(), tape };
116
+ }
117
+ function paymentIntent(overrides = {}) {
118
+ return {
119
+ id: "pi_test_200",
120
+ amount: 2e4,
121
+ currency: "usd",
122
+ status: "succeeded",
123
+ latest_charge: "ch_test_200",
124
+ ...overrides
125
+ };
126
+ }
127
+ function charge(overrides = {}) {
128
+ return {
129
+ id: "ch_test_200",
130
+ amount: 2e4,
131
+ amount_refunded: 0,
132
+ currency: "usd",
133
+ status: "succeeded",
134
+ refunded: false,
135
+ paid: true,
136
+ payment_intent: "pi_test_200",
137
+ balance_transaction: "txn_test_200",
138
+ ...overrides
139
+ };
140
+ }
141
+ function event(type, overrides = {}) {
142
+ return { id: `evt_${type.replace(/\W/g, "_")}`, type, data: { object: null }, created: 17e8, ...overrides };
143
+ }
144
+ function refund(overrides = {}) {
145
+ return {
146
+ id: "re_test_200",
147
+ amount: 7500,
148
+ currency: "usd",
149
+ status: "succeeded",
150
+ charge: "ch_test_200",
151
+ payment_intent: "pi_test_200",
152
+ ...overrides
153
+ };
154
+ }
155
+ function call(overrides = {}) {
156
+ return {
157
+ twin: "stripe",
158
+ method: "GET",
159
+ path: "/s/test-session/v1/refunds",
160
+ status: 200,
161
+ request_body: null,
162
+ tool: null,
163
+ event_id: "evt_call",
164
+ ...overrides
165
+ };
166
+ }
167
+ function x402Leg(overrides = {}) {
168
+ return {
169
+ twin: "stripe",
170
+ method: "GET",
171
+ path: "/s/test-session/x402/protected-resource",
172
+ status: 402,
173
+ request_body: null,
174
+ request_headers: { accept: "application/json" },
175
+ tool: null,
176
+ event_id: "evt_x402",
177
+ ...overrides
178
+ };
179
+ }
180
+
181
+ // ../twin-stripe/dist/src/check-payments.js
182
+ var STATE_INCOMPLETE = { passed: false, status: "skipped", reason: "state_incomplete" };
183
+ function requireList(list) {
184
+ return list == null ? null : list;
185
+ }
186
+ var paymentIntentAmount = defineCheck({
187
+ id: "stripe.payment-intent-amount",
188
+ description: "Asserts SOME PaymentIntent in the account carries this exact amount, in the currency's minor unit (cents for USD) \u2014 the integer the `amount` field holds, never a formatted figure. It asserts nothing about that intent's status, its currency, or how many others exist. An absent `payment_intents` key is a SKIP: a positive criterion must not fail a correct agent over state nobody uploaded.",
189
+ template: "A PaymentIntent exists with amount {amount}",
190
+ params: { amount: minorUnitAmount },
191
+ substrate: "final",
192
+ polarity: () => "positive",
193
+ // The amount is COMPARED to a numeric field, not hunted for inside prose, so
194
+ // no redactor can silently delete it. `subject` is omitted rather than
195
+ // returning null for the same reason twin-github omits it on structural
196
+ // checks: absent means "nothing a redactor could reach", which is the truth.
197
+ vacuityMutant: (args) => ({ ...args, amount: String(VACUITY_SENTINEL_NUMBER) }),
198
+ discriminatingWorlds: ({ amount }) => ({
199
+ passing: finalWorld(stripeState({ payment_intents: [paymentIntent({ amount: Number(amount) })] })),
200
+ // A PaymentIntent that EXISTS and holds a different amount, not an empty
201
+ // list: the reason then names the amounts it scanned and cannot be confused
202
+ // with the one an absent collection produces.
203
+ failing: finalWorld(stripeState({ payment_intents: [paymentIntent({ amount: Number(amount) + 1 })] }))
204
+ }),
205
+ evaluate({ amount }, { final }) {
206
+ const pis = requireList(final.payment_intents);
207
+ if (pis === null)
208
+ return STATE_INCOMPLETE;
209
+ const wanted = Number(amount);
210
+ const found = pis.some((pi) => pi.amount === wanted);
211
+ return {
212
+ passed: found,
213
+ reason: found ? `a PaymentIntent exists with amount ${wanted}` : `no PaymentIntent has amount ${wanted} (amounts: [${pis.map((pi) => pi.amount ?? "?").join(", ")}])`,
214
+ // The COLLECTION, not the matching row. Every check in this file
215
+ // scans a whole collection and answers a question about the set — "does
216
+ // one exist with…" — so the set is what was read. Citing the hit on a pass
217
+ // and the collection on a fail would make the pointer's shape track the
218
+ // verdict, and a reader would learn to read it as one.
219
+ evidenceStatePaths: [PAYMENT_INTENTS_PATH]
220
+ };
221
+ }
222
+ });
223
+ var paymentIntentStatusIs = defineCheck({
224
+ id: "stripe.payment-intent-status",
225
+ description: "Asserts THE PaymentIntent \u2014 singular \u2014 is in this status. When the account holds more than one it returns `unmatched` rather than a verdict, because scanning all of them would let a wrong agent pass on an unrelated intent the seed left in the wanted state. Use `A PaymentIntent exists with status \u2026` when several are expected. An absent `payment_intents` key is a SKIP.",
226
+ template: "The PaymentIntent status is {status}",
227
+ params: { status: paymentIntentStatus },
228
+ substrate: "final",
229
+ polarity: () => "positive",
230
+ // Null, and admitted in `HONEST_NULL_MUTANTS`. The only slot is a closed set,
231
+ // so no member is guaranteed false — a mutant naming a different status
232
+ // asserts something that may also be true — and a value outside the set does
233
+ // not re-bind at all, which reads as "the verdict moved" and blesses the very
234
+ // criterion the probe exists to catch. Task 10 carries this criterion AND
235
+ // `payment-intent-amount`, whose numeric slot keeps a real mutant, so the twin
236
+ // does not go dark in the probe.
237
+ vacuityMutant: () => null,
238
+ discriminatingWorlds: ({ status }) => ({
239
+ // Exactly ONE intent in both worlds. Two would return `unmatched`, which is
240
+ // neither a real pass nor a real fail, and would break arms 1 and 2.
241
+ passing: finalWorld(stripeState({ payment_intents: [paymentIntent({ status })] })),
242
+ failing: finalWorld(stripeState({
243
+ payment_intents: [paymentIntent({ status: status === "canceled" ? "processing" : "canceled" })]
244
+ }))
245
+ }),
246
+ evaluate({ status }, { final }) {
247
+ const pis = requireList(final.payment_intents);
248
+ if (pis === null)
249
+ return STATE_INCOMPLETE;
250
+ if (pis.length > 1) {
251
+ return {
252
+ passed: false,
253
+ status: "unmatched",
254
+ reason: `ambiguous: ${pis.length} payment_intents and this sentence names one`,
255
+ // The ambiguity IS the finding, and the collection is where a reader
256
+ // sees it: several intents where the sentence says "the".
257
+ evidenceStatePaths: [PAYMENT_INTENTS_PATH]
258
+ };
259
+ }
260
+ const found = pis.some((pi) => pi.status === status);
261
+ return {
262
+ passed: found,
263
+ reason: found ? `the PaymentIntent has status "${status}"` : `the PaymentIntent does not have status "${status}" (statuses: [${pis.map((pi) => pi.status ?? "?").join(", ")}])`,
264
+ evidenceStatePaths: [PAYMENT_INTENTS_PATH]
265
+ };
266
+ }
267
+ });
268
+ var paymentIntentWithStatusExists = defineCheck({
269
+ id: "stripe.payment-intent-with-status-exists",
270
+ description: "Asserts AT LEAST ONE PaymentIntent in the account reached this status, whichever one and however many others exist. This is the check for a flow that mints intents the author cannot name in advance \u2014 x402 creates a fresh one per challenge leg \u2014 where naming `the` PaymentIntent would be ambiguous by construction. An absent `payment_intents` key is a SKIP.",
271
+ template: 'A PaymentIntent exists with status "{status}"',
272
+ params: { status: paymentIntentStatus },
273
+ substrate: "final",
274
+ polarity: () => "positive",
275
+ // Closed set — see `stripe.payment-intent-status` above, same argument.
276
+ vacuityMutant: () => null,
277
+ discriminatingWorlds: ({ status }) => {
278
+ const other = status === "canceled" ? "processing" : "canceled";
279
+ return {
280
+ // Two intents in each world, because that plurality is the whole reason
281
+ // this check exists beside the definite one: a fixture with a single
282
+ // intent would pass identically under both and prove nothing about the
283
+ // difference.
284
+ passing: finalWorld(stripeState({
285
+ payment_intents: [
286
+ paymentIntent({ id: "pi_a", status: other }),
287
+ paymentIntent({ id: "pi_b", status })
288
+ ]
289
+ })),
290
+ failing: finalWorld(stripeState({
291
+ payment_intents: [
292
+ paymentIntent({ id: "pi_a", status: other }),
293
+ paymentIntent({ id: "pi_b", status: other })
294
+ ]
295
+ }))
296
+ };
297
+ },
298
+ evaluate({ status }, { final }) {
299
+ const pis = requireList(final.payment_intents);
300
+ if (pis === null)
301
+ return STATE_INCOMPLETE;
302
+ const hits = pis.filter((pi) => pi.status === status);
303
+ return {
304
+ passed: hits.length > 0,
305
+ reason: hits.length > 0 ? `${hits.length} of ${pis.length} PaymentIntent(s) have status "${status}"` : `no PaymentIntent has status "${status}" (statuses: [${pis.map((pi) => pi.status ?? "?").join(", ")}])`,
306
+ evidenceStatePaths: [PAYMENT_INTENTS_PATH]
307
+ };
308
+ }
309
+ });
310
+ var chargeWithStatusExists = defineCheck({
311
+ id: "stripe.charge-exists-with-status",
312
+ description: "Asserts AT LEAST ONE charge in the account is in this status. A charge is created already settled or already declined, so `succeeded` here means money moved \u2014 the twin writes the charge and its balance transaction inside one SQLite transaction, on both the card and the x402 crypto rails, so a charge in this state always has its ledger entry and asserting the balance transaction separately would assert a twin invariant rather than anything an examinee did. An absent `charges` key is a SKIP.",
313
+ template: 'A charge exists with status "{status}"',
314
+ params: { status: chargeStatus },
315
+ substrate: "final",
316
+ polarity: () => "positive",
317
+ // Closed set of three — same argument as the PaymentIntent status slots.
318
+ vacuityMutant: () => null,
319
+ discriminatingWorlds: ({ status }) => {
320
+ const other = status === "failed" ? "succeeded" : "failed";
321
+ return {
322
+ passing: finalWorld(stripeState({ charges: [charge({ status })] })),
323
+ failing: finalWorld(stripeState({ charges: [charge({ status: other })] }))
324
+ };
325
+ },
326
+ evaluate({ status }, { final }) {
327
+ const charges = requireList(final.charges);
328
+ if (charges === null)
329
+ return STATE_INCOMPLETE;
330
+ const hits = charges.filter((row) => row.status === status);
331
+ return {
332
+ passed: hits.length > 0,
333
+ reason: hits.length > 0 ? `${hits.length} of ${charges.length} charge(s) have status "${status}"` : `no charge has status "${status}" (statuses: [${charges.map((row) => row.status ?? "?").join(", ")}])`,
334
+ evidenceStatePaths: [CHARGES_PATH]
335
+ };
336
+ }
337
+ });
338
+ var eventEmitted = defineCheck({
339
+ id: "stripe.event-emitted",
340
+ description: "Asserts the account's event log contains at least one event of this type. The twin delivers no webhooks in v1 \u2014 an examinee observes events by polling `GET /v1/events` \u2014 so this reads the log the twin appended, not anything the examinee received. It asserts nothing about WHICH resource the event names: `payment_intent.succeeded` is emitted by both the card confirm path and the x402 crypto-deposit settlement, and this check deliberately does not distinguish them, because the criterion asking for it is about the outcome rather than the rail. An absent `events` key is a SKIP.",
341
+ // No quotes, and that is load-bearing: the corpus already says
342
+ // `payment_intent.succeeded is emitted`, so this template re-renders that task's
343
+ // existing criterion byte-identically and the migration rewrites it not at
344
+ // all. The dot lives inside the closed set's members, which `oneOf` escapes,
345
+ // so it matches a dot rather than any character.
346
+ template: "{event_type} is emitted",
347
+ params: { event_type: stripeEventType },
348
+ substrate: "final",
349
+ polarity: () => "positive",
350
+ // Closed set of fifteen — the twin's own `EventType` union — so no member is
351
+ // guaranteed false. Admitted in `HONEST_NULL_MUTANTS`.
352
+ vacuityMutant: () => null,
353
+ discriminatingWorlds: ({ event_type }) => {
354
+ const other = event_type === "payment_intent.created" ? "payment_intent.canceled" : "payment_intent.created";
355
+ return {
356
+ passing: finalWorld(stripeState({ events: [event(other), event(event_type)] })),
357
+ // A log that EXISTS and holds a different event, so the reason names what
358
+ // was emitted instead and cannot be confused with an absent log.
359
+ failing: finalWorld(stripeState({ events: [event(other)] }))
360
+ };
361
+ },
362
+ evaluate({ event_type }, { final }) {
363
+ const events = requireList(final.events);
364
+ if (events === null)
365
+ return STATE_INCOMPLETE;
366
+ const hits = events.filter((row) => row.type === event_type);
367
+ return {
368
+ passed: hits.length > 0,
369
+ reason: hits.length > 0 ? `${hits.length} \`${event_type}\` event(s) among ${events.length} emitted` : `no \`${event_type}\` event among ${events.length} emitted ([${events.map((row) => row.type ?? "?").join(", ")}])`,
370
+ // The event LOG. This check deliberately says nothing about which resource
371
+ // an event names, so the log is exactly the width of what it read.
372
+ evidenceStatePaths: [EVENTS_PATH]
373
+ };
374
+ }
375
+ });
376
+
377
+ // ../twin-stripe/dist/src/check-refunds.js
378
+ var refundExists = defineCheck({
379
+ id: "stripe.refund-exists",
380
+ description: "Resolves the named charge, then asserts at least one row in the account's `refunds` collection references it. It reads refund ROWS, not the charge's `amount_refunded`, and not `charge.refunded` \u2014 that flag is true only when a charge is FULLY refunded, so a partial refund leaves it false and a check reading it would miss every partial. It asserts nothing about the refunded amount or the refund's own status. A charge the account does not hold is a SKIP, not a fail: we cannot attest a positive over state we do not have.",
381
+ template: 'A refund exists on charge "{charge}"',
382
+ params: { charge: chargeId },
383
+ substrate: "final",
384
+ polarity: () => "positive",
385
+ // Selector, not a scanned literal — see the header.
386
+ subject: () => null,
387
+ vacuityMutant: () => null,
388
+ discriminatingWorlds: (args) => ({
389
+ passing: finalWorld(stripeState({
390
+ charges: [charge({ id: args.charge })],
391
+ refunds: [refund({ charge: args.charge })]
392
+ })),
393
+ // The charge RESOLVES in both worlds and only the refund list moves. A world
394
+ // without the charge would skip the way an empty one does.
395
+ failing: finalWorld(stripeState({ charges: [charge({ id: args.charge })], refunds: [] }))
396
+ }),
397
+ evaluate(args, { final }) {
398
+ const rows = refundsOnCharge(final, args.charge);
399
+ if ("missing" in rows)
400
+ return missSkip(rows);
401
+ return {
402
+ passed: rows.found.length > 0,
403
+ reason: rows.found.length > 0 ? `charge "${args.charge}" has ${rows.found.length} refund row(s)` : `charge "${args.charge}" has no refund rows`,
404
+ // The refund COLLECTION. Rows are not nested under their charge —
405
+ // they carry a `charge` wire field — so the per-charge list this counted
406
+ // exists nowhere in the tree, and on the zero side there is no row to point
407
+ // at at all, which is precisely the arm a reader wants to open.
408
+ evidenceStatePaths: [rows.path]
409
+ };
410
+ }
411
+ });
412
+ var refundCount = defineCheck({
413
+ id: "stripe.refund-count",
414
+ description: "Resolves the named charge and asserts the account holds EXACTLY this many refund rows against it. This is the over-refund assertion: a lost-response retry that re-issues the same logical refund lands a second row, and only a count can see it \u2014 the amount is right on each row individually and wrong in aggregate. It asserts nothing about the amounts, so two rows fail it whether they total the intended refund or double it. A charge the account does not hold is a SKIP.",
415
+ template: 'The number of refunds on charge "{charge}" is {count}',
416
+ params: { charge: chargeId, count: rowCount },
417
+ substrate: "final",
418
+ // A function of the args, the way the GitHub PR check's is: `… is 0` is a
419
+ // prohibition the seed already satisfies and only the examinee can break,
420
+ // while any other count is something that must come to exist.
421
+ polarity: ({ count }) => Number(count) === 0 ? "negative" : "positive",
422
+ subject: () => null,
423
+ // The charge SELECTS and the count IS the assertion. Neither is a literal
424
+ // hunted for in the state, so no substitution falsifies a trigger clause:
425
+ // mutating the charge skips, and mutating the count asserts a different thing
426
+ // rather than falsifying this one. Admitted in `HONEST_NULL_MUTANTS`.
427
+ vacuityMutant: () => null,
428
+ discriminatingWorlds: (args) => {
429
+ const wanted = Number(args.count);
430
+ const rows = (n) => Array.from({ length: n }, (_unused, index) => refund({ id: `re_${index}`, charge: args.charge }));
431
+ return {
432
+ passing: finalWorld(stripeState({ charges: [charge({ id: args.charge })], refunds: rows(wanted) })),
433
+ // One MORE, never one fewer: the double-refund is the failure this check
434
+ // exists for, and a fixture that under-shot would demonstrate the
435
+ // uninteresting half.
436
+ failing: finalWorld(stripeState({ charges: [charge({ id: args.charge })], refunds: rows(wanted + 1) }))
437
+ };
438
+ },
439
+ evaluate(args, { final }) {
440
+ const rows = refundsOnCharge(final, args.charge);
441
+ if ("missing" in rows)
442
+ return missSkip(rows);
443
+ const wanted = Number(args.count);
444
+ return {
445
+ passed: rows.found.length === wanted,
446
+ reason: `charge "${args.charge}" has ${rows.found.length} refund row(s), wanted ${wanted}` + (rows.found.length > wanted ? ` \u2014 ${rows.found.length - wanted} more than one refund per logical transaction` : ""),
447
+ // The over-refund this check exists to catch is a second ROW, and the
448
+ // collection is where a reader can count them.
449
+ evidenceStatePaths: [rows.path]
450
+ };
451
+ }
452
+ });
453
+
454
+ // ../twin-stripe/dist/src/check-tape.js
455
+ var TAPE_MISSING = { passed: false, status: "skipped", reason: "tape_missing" };
456
+ function citations(events) {
457
+ return events.map((event2) => event2.event_id).filter((id) => typeof id === "string" && id !== "");
458
+ }
459
+ function withCitations(outcome, events) {
460
+ const ids = citations(events);
461
+ return ids.length > 0 ? { ...outcome, evidenceEventIds: ids } : outcome;
462
+ }
463
+ function recordedErrorType(body) {
464
+ const parsed = typeof body === "string" ? (() => {
465
+ try {
466
+ return JSON.parse(body);
467
+ } catch {
468
+ return null;
469
+ }
470
+ })() : body;
471
+ if (parsed === null || typeof parsed !== "object")
472
+ return null;
473
+ const error = parsed.error;
474
+ if (error === null || typeof error !== "object")
475
+ return null;
476
+ const type = error.type;
477
+ return typeof type === "string" ? type : null;
478
+ }
479
+ function x402Legs(tape) {
480
+ return tape.filter((event2) => (event2.path ?? "").includes("/x402/"));
481
+ }
482
+ var noRefundOnCharge = defineCheck({
483
+ id: "stripe.no-refund-on-charge",
484
+ description: "Scans the recorded call tape for a `POST /v1/refunds` whose request body names this charge, and fails if one exists. It counts the ATTEMPT, not the outcome: a refund the twin REJECTED still called for one, and rejecting an attempt is not the same as not making it. That is the whole reason this reads the tape \u2014 the twin refuses a second refund on an already-refunded charge, so no examinee behaviour can put a matching row in `state.refunds`, and read against state the criterion could never fail. A GET that merely LISTS refunds is not an attempt and does not fail it.",
485
+ template: 'No refund was attempted on charge "{charge}"',
486
+ params: { charge: chargeId },
487
+ substrate: "tape",
488
+ polarity: () => "negative",
489
+ // The charge id IS the scanned literal here — hunted for inside a recorded
490
+ // request body, with nothing to resolve it against. That is what earns it both
491
+ // a real `subject` and a real mutant, where the state-reading refund checks
492
+ // (`check-refunds.ts`) get neither.
493
+ subject: (args) => args.charge,
494
+ vacuityMutant: (args) => ({ ...args, charge: VACUITY_SENTINEL }),
495
+ discriminatingWorlds: (args) => ({
496
+ // A non-empty PASSING tape, on purpose: an empty tape passes too, and a
497
+ // fixture that used one would demonstrate nothing about the check. This one
498
+ // shows the distinction the description makes — a GET that lists refunds for
499
+ // the very same charge is not an attempt.
500
+ passing: tapeWorld([
501
+ call({ method: "GET", path: `/s/test-session/v1/refunds?charge=${args.charge}`, status: 200 })
502
+ ]),
503
+ failing: tapeWorld([
504
+ call({
505
+ method: "POST",
506
+ path: "/s/test-session/v1/refunds",
507
+ status: 400,
508
+ request_body: JSON.stringify({ charge: args.charge, amount: 2e4 }),
509
+ event_id: "evt_attempt"
510
+ })
511
+ ])
512
+ }),
513
+ evaluate(args, { tape }) {
514
+ if (tape === null)
515
+ return TAPE_MISSING;
516
+ const attempts = tape.filter((event2) => {
517
+ if ((event2.method ?? "").toUpperCase() !== "POST")
518
+ return false;
519
+ if (!(event2.path ?? "").includes("/v1/refunds"))
520
+ return false;
521
+ const body = event2.request_body;
522
+ const haystack = typeof body === "string" ? body : JSON.stringify(body ?? "");
523
+ return haystack.includes(args.charge);
524
+ });
525
+ if (attempts.length === 0) {
526
+ return {
527
+ passed: true,
528
+ reason: `no refund was attempted on charge "${args.charge}" (${tape.length} call(s) inspected)`
529
+ };
530
+ }
531
+ return withCitations({
532
+ passed: false,
533
+ reason: `${attempts.length} refund attempt(s) on charge "${args.charge}" (statuses: [${attempts.map((event2) => event2.status ?? "?").join(", ")}]) \u2014 the twin rejecting an attempt is not the same as not attempting one`
534
+ }, attempts);
535
+ }
536
+ });
537
+ var requestRejectedWithError = defineCheck({
538
+ id: "stripe.request-rejected-with-error",
539
+ description: "Scans the recorded call tape for a response carrying Stripe's error envelope with this `error.type`, and passes if one exists. It asserts the agent PROVOKED and received that class of error \u2014 which mutates nothing, so the end state cannot show it \u2014 and asserts nothing about whether the agent then handled it, or about the error's `code`. It reads `error.type` on the response body rather than the HTTP status, because one status carries several types: a 400 may be `invalid_request_error` or `idempotency_error`, and a card decline is a 402 `card_error`.",
540
+ template: 'A request was rejected with a Stripe "{error_type}" error',
541
+ params: { error_type: stripeErrorType },
542
+ substrate: "tape",
543
+ polarity: () => "positive",
544
+ // Closed set of five — the twin's own `StripeErrorType` union — so no member
545
+ // is guaranteed false. Admitted in `HONEST_NULL_MUTANTS`.
546
+ vacuityMutant: () => null,
547
+ discriminatingWorlds: ({ error_type }) => {
548
+ const other = error_type === "card_error" ? "api_error" : "card_error";
549
+ return {
550
+ passing: tapeWorld([
551
+ call({ method: "POST", path: "/s/test-session/v1/payment_intents", status: 200 }),
552
+ call({
553
+ method: "POST",
554
+ path: "/s/test-session/v1/payment_intents",
555
+ status: 400,
556
+ response_body: { error: { type: error_type, code: "parameter_invalid" } },
557
+ event_id: "evt_rejected"
558
+ })
559
+ ]),
560
+ // A request that WAS rejected, with a different type. A clean tape would
561
+ // fail for the reason an empty one does; this fails because the agent
562
+ // provoked the wrong error, which is the assertion.
563
+ failing: tapeWorld([
564
+ call({
565
+ method: "POST",
566
+ path: "/s/test-session/v1/payment_intents",
567
+ status: 400,
568
+ response_body: { error: { type: other, code: "parameter_invalid" } }
569
+ })
570
+ ])
571
+ };
572
+ },
573
+ evaluate({ error_type }, { tape }) {
574
+ if (tape === null)
575
+ return TAPE_MISSING;
576
+ const typed = tape.map((event2) => ({ event: event2, type: recordedErrorType(event2.response_body) })).filter((row) => row.type !== null);
577
+ const hits = typed.filter((row) => row.type === error_type);
578
+ if (hits.length === 0) {
579
+ return {
580
+ passed: false,
581
+ reason: `no recorded call was rejected with a Stripe "${error_type}" error (${tape.length} call(s) inspected; error types seen: [${typed.map((row) => row.type).join(", ")}])`
582
+ };
583
+ }
584
+ return withCitations({
585
+ passed: true,
586
+ reason: `${hits.length} call(s) were rejected with a Stripe "${error_type}" error (${tape.length} call(s) inspected)`
587
+ }, hits.map((row) => row.event));
588
+ }
589
+ });
590
+ var x402FirstRequestChallenged = defineCheck({
591
+ id: "stripe.x402-first-request-challenged",
592
+ description: "Finds the recorded calls to the x402 protected resource, in order, and asserts the FIRST one was answered 402 Payment Required. It is about the challenge leg specifically, not about any 402 anywhere \u2014 a card decline is also a 402 and does not satisfy it. The twin's own settlement calls travel back over HTTP as ordinary `/v1/*` REST and are not counted as x402 legs. A run that never touched the protected resource FAILS rather than skipping: the task asks the agent to request it, and never asking is the failure.",
593
+ // The sentence names x402 where the corpus said only "The first request".
594
+ // A rendered sentence must not be wider than the predicate — the predicate is
595
+ // scoped to one surface and the old wording was scoped to nothing, which is
596
+ // the readable-surface-hides-the-disagreement defect the contract test's
597
+ // description arm exists for.
598
+ template: "The first x402 request returns 402 Payment Required",
599
+ params: {},
600
+ substrate: "tape",
601
+ // Nothing in the seed satisfies it and only the examinee acting can, so it
602
+ // should FAIL on the seed.
603
+ polarity: () => "positive",
604
+ // No slots at all, so there is no literal to falsify — the trigger is a status
605
+ // code on a path, which lives on the tape and not in the sentence. Reported as
606
+ // `no_trigger`, never as clean.
607
+ subject: () => null,
608
+ vacuityMutant: () => null,
609
+ discriminatingWorlds: () => ({
610
+ passing: tapeWorld([x402Leg({ status: 402, event_id: "evt_challenge" }), x402Leg({ status: 200 })]),
611
+ // The resource WAS requested and answered 200 first — an ungated surface.
612
+ // A tape with no x402 leg at all would fail the way an empty one does.
613
+ failing: tapeWorld([x402Leg({ status: 200 }), x402Leg({ status: 200 })])
614
+ }),
615
+ evaluate(_args, { tape }) {
616
+ if (tape === null)
617
+ return TAPE_MISSING;
618
+ const legs = x402Legs(tape);
619
+ if (legs.length === 0) {
620
+ return {
621
+ passed: false,
622
+ reason: `no request to the x402 protected resource was recorded (${tape.length} call(s) inspected)`
623
+ };
624
+ }
625
+ const first = legs[0];
626
+ const challenged = first.status === 402;
627
+ return withCitations({
628
+ passed: challenged,
629
+ reason: challenged ? `the first of ${legs.length} x402 request(s) returned 402 Payment Required` : `the first of ${legs.length} x402 request(s) returned ${first.status ?? "?"}, not 402`
630
+ }, [first]);
631
+ }
632
+ });
633
+ var x402RetryIncludesPayment = defineCheck({
634
+ id: "stripe.x402-retry-includes-payment",
635
+ description: "Asserts some recorded request carried an `X-PAYMENT` header AND was answered 200 \u2014 the agent constructed a payment for the advertised challenge and the resource unlocked. Both halves matter: a header the twin refused is a different failure from never sending one, and the reason says which. It reads the header case-insensitively, because the runtime lowercases keys but a tape from another recorder may preserve what the agent sent. A recording made before headers existed is a SKIP, never a verdict.",
636
+ template: "The retry includes X-PAYMENT and returns 200",
637
+ params: {},
638
+ substrate: "tape",
639
+ polarity: () => "positive",
640
+ // The scanned literal is the HEADER NAME, fixed by the sentence rather than
641
+ // supplied by an author — there is no slot to falsify. Nothing here is a
642
+ // caller-supplied literal a redactor could delete either. (The header VALUE is
643
+ // base64 JSON, always beginning `eyJ` and one character class away from the
644
+ // JWT scrubber; that it survives redaction is asserted in
645
+ // `packages/sdk/test/redaction.test.ts`, because a scrubbed subject would make
646
+ // this permanently unanswerable in a way nothing here could detect.)
647
+ subject: () => null,
648
+ vacuityMutant: () => null,
649
+ discriminatingWorlds: () => ({
650
+ passing: tapeWorld([
651
+ x402Leg({ status: 402 }),
652
+ x402Leg({
653
+ status: 200,
654
+ request_headers: { "x-payment": "eyJzY2hlbWUiOiJleGFjdCJ9" },
655
+ event_id: "evt_paid"
656
+ })
657
+ ]),
658
+ // A payment the twin REFUSED. The agent did construct one, so this is not
659
+ // the never-tried world an empty tape gives.
660
+ failing: tapeWorld([
661
+ x402Leg({ status: 402 }),
662
+ x402Leg({ status: 402, request_headers: { "x-payment": "eyJzY2hlbWUiOiJ3cm9uZyJ9" } })
663
+ ])
664
+ }),
665
+ evaluate(_args, { tape }) {
666
+ if (tape === null)
667
+ return TAPE_MISSING;
668
+ if (tape.length > 0 && !tape.some((event2) => event2.request_headers !== void 0)) {
669
+ return { passed: false, status: "skipped", reason: "headers_not_recorded" };
670
+ }
671
+ const carriesPayment = (event2) => Object.keys(event2.request_headers ?? {}).some((key) => key.toLowerCase() === "x-payment");
672
+ const unlocked = tape.filter((event2) => carriesPayment(event2) && event2.status === 200);
673
+ if (unlocked.length === 0) {
674
+ const attempted = tape.filter(carriesPayment);
675
+ const detail = attempted.length === 0 ? "no recorded request carried an X-PAYMENT header" : `${attempted.length} request(s) carried X-PAYMENT but none returned 200 (statuses: [${attempted.map((event2) => event2.status ?? "?").join(", ")}])`;
676
+ return { passed: false, reason: `${detail} (${tape.length} call(s) inspected)` };
677
+ }
678
+ return withCitations({
679
+ passed: true,
680
+ reason: `${unlocked.length} request(s) carried X-PAYMENT and returned 200 (${tape.length} call(s) inspected)`
681
+ }, unlocked);
682
+ }
683
+ });
684
+
685
+ // ../twin-stripe/dist/src/checks.js
686
+ var STRIPE_CHECKS = [
687
+ paymentIntentAmount,
688
+ paymentIntentStatusIs,
689
+ paymentIntentWithStatusExists,
690
+ chargeWithStatusExists,
691
+ eventEmitted,
692
+ refundExists,
693
+ refundCount,
694
+ // The tape half. Last, because reaching for one means the final state could
695
+ // not answer the question — which is the rarer case and the one that needs
696
+ // the author to have read the check's description.
697
+ noRefundOnCharge,
698
+ requestRejectedWithError,
699
+ x402FirstRequestChallenged,
700
+ x402RetryIncludesPayment
701
+ ];
702
+ var failureInjectionRuleSchema = z.object({
703
+ method: z.string().min(1).transform((s) => s.toUpperCase()),
704
+ path: z.string().min(1),
705
+ attempt: z.number().int().positive(),
706
+ mode: z.enum(["before_handler", "after_handler"]).default("after_handler"),
707
+ status: z.number().int().min(100).max(599),
708
+ body: z.unknown()
709
+ });
710
+
711
+ // ../twin-stripe/dist/src/seed.js
712
+ var DEFAULT_SID = "default";
713
+ var DEFAULT_API_KEY = "sk_test_pome_default";
714
+ var PI_STATUSES = [
715
+ "requires_payment_method",
716
+ "requires_confirmation",
717
+ "requires_action",
718
+ "processing",
719
+ "requires_capture",
720
+ "canceled",
721
+ "succeeded"
722
+ ];
723
+ var CHARGE_STATUSES2 = ["pending", "succeeded", "failed"];
724
+ var REFUND_STATUSES = ["succeeded", "pending", "failed", "canceled"];
725
+ var BALANCE_TX_STATUSES = ["pending", "available"];
726
+ var paymentIntentSeedSchema = z.object({
727
+ id: z.string().min(1),
728
+ account_id: z.string().min(1),
729
+ amount: z.number().int(),
730
+ currency: z.string().min(1),
731
+ status: z.enum(PI_STATUSES),
732
+ payment_method_types: z.array(z.string()).default(["crypto"]),
733
+ next_action: z.unknown().nullable().optional(),
734
+ latest_charge_id: z.string().nullable().optional(),
735
+ capture_method: z.string().default("automatic"),
736
+ confirmation_method: z.string().default("automatic"),
737
+ idempotency_key: z.string().nullable().optional(),
738
+ metadata: z.record(z.string(), z.string()).default({}),
739
+ crypto_deposit: z.unknown().nullable().optional(),
740
+ client_secret: z.string().min(1),
741
+ created: z.number().int(),
742
+ updated: z.number().int(),
743
+ canceled_at: z.number().int().nullable().optional(),
744
+ captured_at: z.number().int().nullable().optional()
745
+ });
746
+ var chargeSeedSchema = z.object({
747
+ id: z.string().min(1),
748
+ account_id: z.string().min(1),
749
+ payment_intent_id: z.string().min(1),
750
+ amount: z.number().int(),
751
+ amount_captured: z.number().int().default(0),
752
+ amount_refunded: z.number().int().default(0),
753
+ status: z.enum(CHARGE_STATUSES2),
754
+ balance_transaction_id: z.string().nullable().optional(),
755
+ captured: z.boolean().default(true),
756
+ currency: z.string().min(1),
757
+ created: z.number().int()
758
+ });
759
+ var refundSeedSchema = z.object({
760
+ id: z.string().min(1),
761
+ account_id: z.string().min(1),
762
+ charge_id: z.string().min(1),
763
+ payment_intent_id: z.string().min(1),
764
+ amount: z.number().int(),
765
+ currency: z.string().min(1),
766
+ status: z.enum(REFUND_STATUSES),
767
+ reason: z.string().nullable().optional(),
768
+ balance_transaction_id: z.string().nullable().optional(),
769
+ idempotency_key: z.string().nullable().optional(),
770
+ created: z.number().int()
771
+ });
772
+ var balanceTransactionSeedSchema = z.object({
773
+ id: z.string().min(1),
774
+ account_id: z.string().min(1),
775
+ type: z.string().min(1),
776
+ amount: z.number().int(),
777
+ fee: z.number().int().default(0),
778
+ net: z.number().int(),
779
+ currency: z.string().min(1),
780
+ source_id: z.string().nullable().optional(),
781
+ source_type: z.string().nullable().optional(),
782
+ available_on: z.number().int(),
783
+ status: z.enum(BALANCE_TX_STATUSES).default("available"),
784
+ created: z.number().int()
785
+ });
786
+ var seedSchema = z.object({
787
+ api_keys: z.array(z.object({
788
+ key: z.string().min(1),
789
+ sid: z.string().min(1),
790
+ account_id: z.string().min(1).optional()
791
+ })).default([]),
792
+ failure_injection: z.array(failureInjectionRuleSchema).default([]),
793
+ payment_intents: z.array(paymentIntentSeedSchema).default([]),
794
+ charges: z.array(chargeSeedSchema).default([]),
795
+ refunds: z.array(refundSeedSchema).default([]),
796
+ balance_transactions: z.array(balanceTransactionSeedSchema).default([])
797
+ });
798
+ function parseSeed(input) {
799
+ return seedSchema.parse(input);
800
+ }
801
+ function defaultSeed() {
802
+ return {
803
+ api_keys: [
804
+ { key: DEFAULT_API_KEY, sid: DEFAULT_SID, account_id: `acct_${DEFAULT_SID}` }
805
+ ],
806
+ failure_injection: [],
807
+ payment_intents: [],
808
+ charges: [],
809
+ refunds: [],
810
+ balance_transactions: []
811
+ };
812
+ }
813
+
814
+ export { STRIPE_CHECKS, defaultSeed, parseSeed, seedSchema };