@voeu/cli 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.
- package/LICENSE +21 -0
- package/README.md +362 -0
- package/dist/build-info.json +8 -0
- package/dist/voeu.js +6188 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Solus-QE
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# voeu
|
|
2
|
+
|
|
3
|
+
Keep integrations working when providers change — and prove it.
|
|
4
|
+
|
|
5
|
+
A **contract pack** is one provider's rules, written down claim by claim, encoded in a simulator,
|
|
6
|
+
and turned into scenarios your application has to survive. Shopify's expiring offline access
|
|
7
|
+
tokens are the first pack and the one this page is mostly about; Stripe idempotency is the
|
|
8
|
+
second — see [Contract packs](#contract-packs) for what each one covers and what it does not.
|
|
9
|
+
|
|
10
|
+
Every public Shopify app that calls the GraphQL Admin API must use expiring offline access tokens
|
|
11
|
+
[from 1 January 2027](https://shopify.dev/changelog/expiring-offline-access-tokens-required-for-all-public-apps-as-of-january-1-2027).
|
|
12
|
+
Custom and merchant-created apps are exempt. After that date, apps still presenting a non-expiring
|
|
13
|
+
token get authentication errors, which one developer on Shopify's own thread described as
|
|
14
|
+
"you've essentially uninstalled the app from every one of your customer's stores."
|
|
15
|
+
|
|
16
|
+
The mechanical part of that migration is documented and mostly not the problem. What the docs
|
|
17
|
+
cannot tell you is whether **your** app survives the new lifecycle — your session storage, your
|
|
18
|
+
background jobs, your worker topology, your failure handling. Those are runtime properties.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx @voeu/cli inspect --repo . # read the source. no credentials, no execution.
|
|
22
|
+
npx @voeu/cli verify --reference # see what verify does, with no setup
|
|
23
|
+
npx @voeu/cli init --repo . # scaffold the adapter verify drives
|
|
24
|
+
npx @voeu/cli verify --repo . # run YOUR application against a simulated Shopify
|
|
25
|
+
npx @voeu/cli fix --repo . # emit a patch for the deterministic findings
|
|
26
|
+
npx @voeu/cli watch --record # record today, then run it nightly
|
|
27
|
+
npx @voeu/cli contract # print every rule this encodes, and its source
|
|
28
|
+
|
|
29
|
+
npx @voeu/cli verify --provider stripe --reference # the second pack: idempotency
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Why not a mock server
|
|
33
|
+
|
|
34
|
+
Pointing a mock or a sandbox at your application gives you a substrate: somewhere to make calls
|
|
35
|
+
that will not touch production. You still have to know which calls to make, what should happen,
|
|
36
|
+
and whether what happened was right. That is the part voeu does, and it is three things a mock
|
|
37
|
+
does not have.
|
|
38
|
+
|
|
39
|
+
**A verdict, not a transcript.** Fourteen scenarios ask questions with a right answer — does a job
|
|
40
|
+
enqueued before expiry still work when the queue reaches it, can an older session write overwrite
|
|
41
|
+
newer credentials — and each returns `passed`, `failed`, `not_applicable` or `not_verified` with
|
|
42
|
+
the consequence spelled out. A mock replies to requests; it has no opinion about whether your
|
|
43
|
+
handling of the reply was correct.
|
|
44
|
+
|
|
45
|
+
**A contract you can audit.** Every rule the simulator encodes is written down as data: the
|
|
46
|
+
statement, the Shopify document it came from, the constant it became, and the checks that stop
|
|
47
|
+
being believable if it moves. Run `voeu contract` and argue with it in five minutes instead of
|
|
48
|
+
reading a state machine. A mock's behaviour is whatever somebody typed into it, with no record of
|
|
49
|
+
why.
|
|
50
|
+
|
|
51
|
+
**An alarm when the rule moves.** Shopify changed the refresh recovery window from 60 minutes to
|
|
52
|
+
30 days on 28 August 2026 — no migration required, nothing to do, and no reason for anybody to
|
|
53
|
+
notice that results verified the week before were verified against a rule that no longer existed.
|
|
54
|
+
`voeu watch` re-reads every cited document nightly and downgrades the checks that rested on a
|
|
55
|
+
phrase which has disappeared. A mock configured against the old rule keeps passing, confidently,
|
|
56
|
+
forever.
|
|
57
|
+
|
|
58
|
+
The first is why a run is worth reading. The third is why it is worth keeping.
|
|
59
|
+
|
|
60
|
+
## `inspect`
|
|
61
|
+
|
|
62
|
+
Static. Reads source only — never executes your code, never calls a model, never needs a
|
|
63
|
+
credential. Runs in seconds on a repository it has never seen.
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
voeu inspect — acme/inventory-sync
|
|
67
|
+
|
|
68
|
+
stack @shopify/shopify-app-remix ^5.0.1 · prisma sessions · distribution unknown
|
|
69
|
+
contract shopify-token-lifecycle@0.1.0
|
|
70
|
+
|
|
71
|
+
FAILED Session model is missing refreshToken and refreshTokenExpires
|
|
72
|
+
prisma/schema.prisma:10
|
|
73
|
+
FAILED Credential enqueued into deferred work via syncQueue.add
|
|
74
|
+
app/jobs/sync.server.ts:12:41
|
|
75
|
+
UNPROVEN No recognised per-shop serialisation around token refresh
|
|
76
|
+
Procfile:1
|
|
77
|
+
|
|
78
|
+
4 failed · 2 unproven
|
|
79
|
+
|
|
80
|
+
Static analysis only. Nothing in this repository was executed.
|
|
81
|
+
Run `voeu verify` to decide the unproven checks.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Because it reads source only, `inspect` is safe to run on a fork-originated pull request.
|
|
85
|
+
|
|
86
|
+
## `verify`
|
|
87
|
+
|
|
88
|
+
Executes your application against a simulator that encodes Shopify's token lifecycle — 60-minute
|
|
89
|
+
access tokens, 90-day single-use refresh tokens, the
|
|
90
|
+
[2026-08-28 recovery window](https://shopify.dev/changelog/more-resilient-refreshes-for-expiring-offline-access-tokens),
|
|
91
|
+
and the failure modes you cannot provoke on your own: 5xx, rate limits, timeouts, and **lost
|
|
92
|
+
responses**, where the provider commits and your code never learns what happened.
|
|
93
|
+
|
|
94
|
+
Fourteen scenarios ask questions source cannot answer:
|
|
95
|
+
|
|
96
|
+
| Family | What the Shopify pack asks |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `credential-lifecycle` | Does the pair survive expiry, restart, a transient 5xx, and a lost refresh response? |
|
|
99
|
+
| `deferred-execution` | Does a job enqueued before expiry still work when the queue reaches it? |
|
|
100
|
+
| `concurrency` | Do two processes refreshing one shop converge on one live pair — and can an older session write overwrite newer credentials? |
|
|
101
|
+
| `partial-failure` | Does a failed session write strand the shop? Is an ambiguous conversion replayed? |
|
|
102
|
+
| `tenant-isolation` | Can one merchant's credential serve another? |
|
|
103
|
+
|
|
104
|
+
Those five are the families that apply to Shopify's token lifecycle. The taxonomy has a sixth —
|
|
105
|
+
`retry-identity` — which does not, and is reported as not applicable rather than silently absent.
|
|
106
|
+
It is the main one the Stripe pack asks about, where the equivalent failure is a retry that mints
|
|
107
|
+
a fresh idempotency key and bills the customer twice.
|
|
108
|
+
|
|
109
|
+
`verify` needs one thing from you: an adapter. `voeu init` writes a starter one, already wired to
|
|
110
|
+
whatever `inspect` could find:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
$ npx @voeu/cli init --repo .
|
|
114
|
+
wrote voeu.config.json points verify at the adapter below
|
|
115
|
+
wrote voeu/adapter.ts wired to your Prisma session storage; queue methods left as TODO
|
|
116
|
+
|
|
117
|
+
Before `voeu verify` will run:
|
|
118
|
+
- enqueueJob() and runJobs() must call your real queue. Left as TODO because a
|
|
119
|
+
guess here would make the deferred-execution scenarios test the guess.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The adapter is yours from the moment it exists — checked in, never regenerated, never overwritten.
|
|
123
|
+
That seam is deliberate: a harness that supplies its own session store proves only that the store
|
|
124
|
+
it supplied works. A partly-filled adapter still gives honest answers, because scenarios it cannot
|
|
125
|
+
exercise report UNPROVEN rather than passing.
|
|
126
|
+
|
|
127
|
+
`npx @voeu/cli verify --reference` runs the whole suite against a built-in application, so you can see
|
|
128
|
+
the output before writing anything.
|
|
129
|
+
|
|
130
|
+
## `watch`
|
|
131
|
+
|
|
132
|
+
A verified result decays, for three reasons that have nothing to do with each other.
|
|
133
|
+
Your code changes. **The provider changes.** Time passes. Only the first is visible in
|
|
134
|
+
your repository, and the second is the one that took Shopify's own developers by
|
|
135
|
+
surprise: the refresh recovery window changed on
|
|
136
|
+
[28 August 2026](https://shopify.dev/changelog/more-resilient-refreshes-for-expiring-offline-access-tokens)
|
|
137
|
+
with no migration required and no reason for anybody to notice.
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npx @voeu/cli watch --record --repo . # once. commit voeu/baseline.json
|
|
141
|
+
npx @voeu/cli watch --repo . # nightly, in CI
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`watch` re-inspects your tree, re-reads every provider document this contract cites,
|
|
145
|
+
and reports only what moved.
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
voeu watch — acme/inventory-sync
|
|
149
|
+
|
|
150
|
+
Something moved: 4 check(s) went stale.
|
|
151
|
+
|
|
152
|
+
contract shopify-token-lifecycle@0.1.0
|
|
153
|
+
deadline 114 days to 1 January 2027
|
|
154
|
+
|
|
155
|
+
SAME shopify.dev/docs/.../migrate-to-expiring-offline-access-tokens
|
|
156
|
+
DRIFTED shopify.dev/changelog/more-resilient-refreshes-...
|
|
157
|
+
Text this contract depends on is no longer in the document:
|
|
158
|
+
"limited to 30 days from the original refresh token".
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Two strengths of alarm, and only one of them is loud. **EDITED** means the page changed
|
|
162
|
+
but every phrase the contract depends on is still there — worth a read, not evidence
|
|
163
|
+
that a rule moved. **DRIFTED** means a phrase this contract rests on is gone, and every
|
|
164
|
+
check resting on it is downgraded from passed to unverified until a human looks.
|
|
165
|
+
|
|
166
|
+
Three rules make this usable rather than noisy:
|
|
167
|
+
|
|
168
|
+
- **A phrase is only armed if it was seen to match when you recorded.** A guessed phrase
|
|
169
|
+
that never matched cannot tell drift from a bad guess, so it is reported as an unwatched
|
|
170
|
+
claim instead of firing on day one. When this file was first written it claimed the
|
|
171
|
+
migration guide said "60 minutes" and "90 days". It says neither — both numbers live in
|
|
172
|
+
the changelogs — and `--record` caught it in under a minute.
|
|
173
|
+
- **A document that could not be read is never reported as unchanged.** It gets its own
|
|
174
|
+
state, because "we could not check" and "nothing changed" are different sentences.
|
|
175
|
+
- **Nothing re-records itself.** A watch that acknowledged the drift it found would be a
|
|
176
|
+
green light wired to its own alarm.
|
|
177
|
+
|
|
178
|
+
A finding that was already in your baseline does not fail the job. `watch` answers "has
|
|
179
|
+
anything changed?", not "is this broken?" — a nightly job that is red every night is a
|
|
180
|
+
nightly job that gets switched off.
|
|
181
|
+
|
|
182
|
+
## `contract`
|
|
183
|
+
|
|
184
|
+
The simulator is only worth trusting if you can check it, so every rule it encodes is
|
|
185
|
+
written down as data: the statement, the Shopify document it came from, the constant it
|
|
186
|
+
became, and the checks that stop being believable if it moves.
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
npx @voeu/cli contract # all of it
|
|
190
|
+
npx @voeu/cli contract --open # only where the documentation is ambiguous
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The `--open` list is the part nobody volunteers. Three of the claims rest on a reading
|
|
194
|
+
rather than a rule — including the status code Shopify returns for a rejected refresh,
|
|
195
|
+
which no page states and which applications branch on. Each one names the request
|
|
196
|
+
sequence that would settle it against a real store.
|
|
197
|
+
|
|
198
|
+
## `differential`
|
|
199
|
+
|
|
200
|
+
The one command that can catch **Voeu** being wrong.
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
npx @voeu/cli differential --self-check # no credentials. proves the comparison works.
|
|
204
|
+
npx @voeu/cli differential --live # a development store you own. proves the contract.
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Every `verify` result rests on the simulator being a faithful account of Shopify, and
|
|
208
|
+
until it has been compared against a real store that is an assertion rather than
|
|
209
|
+
evidence. `--live` runs the contract's open questions against a development store and
|
|
210
|
+
reports where the two disagree — as a finding **about the simulator**, never about your
|
|
211
|
+
application.
|
|
212
|
+
|
|
213
|
+
`--self-check` needs nothing. It runs the probes against the simulator twice, which must
|
|
214
|
+
agree, and then against mutations of it that answer an open question the other way, which
|
|
215
|
+
must be detected. That establishes the comparison works. It establishes nothing about
|
|
216
|
+
Shopify, and it says so on every run.
|
|
217
|
+
|
|
218
|
+
## `fix`
|
|
219
|
+
|
|
220
|
+
Emits a patch for the findings that are deterministic — the two columns Shopify documents, the
|
|
221
|
+
flag Shopify documents, and the additive migration to go with them. No model is involved, so the
|
|
222
|
+
patch is reproducible from your commit.
|
|
223
|
+
|
|
224
|
+
It never edits your working tree. It writes a patch, **re-runs the same checks over the patched
|
|
225
|
+
tree**, and reports only what the re-check confirms. Findings it cannot repair safely — a
|
|
226
|
+
credentialed client at module scope, a credential in a queue payload — are declined explicitly
|
|
227
|
+
with reasons, because a repair tool that quietly skips the hard finding leaves you believing it
|
|
228
|
+
was handled.
|
|
229
|
+
|
|
230
|
+
## GitHub Action
|
|
231
|
+
|
|
232
|
+
```yaml
|
|
233
|
+
- uses: Solus-QE/voeu@v0
|
|
234
|
+
with:
|
|
235
|
+
command: inspect
|
|
236
|
+
distribution: public # optional; see below
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Writes `run.json` and a self-contained `report.html`, adds a job summary, and uploads both as an
|
|
240
|
+
artifact.
|
|
241
|
+
|
|
242
|
+
The Action lives in a repository that is not yet public, so `uses: Solus-QE/voeu@v0` resolves only
|
|
243
|
+
for workflows in that account until it is. Everything the Action does, the CLI does directly:
|
|
244
|
+
|
|
245
|
+
```yaml
|
|
246
|
+
- run: npx @voeu/cli@0.1 inspect --repo . --out voeu-run
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
exits `1` on a failing check, `2` when the target could not be assessed, and leaves
|
|
250
|
+
`voeu-run/inspect.json` and `voeu-run/report.html` to upload with `actions/upload-artifact`.
|
|
251
|
+
|
|
252
|
+
### Reporting a run to Voeu Cloud
|
|
253
|
+
|
|
254
|
+
The hosted product reads your repository's archive and never executes it, so every verdict it
|
|
255
|
+
shows is static. `verify` is the runtime answer, and it runs in your CI. To put its result beside
|
|
256
|
+
the hosted inspect on the repository's page:
|
|
257
|
+
|
|
258
|
+
```yaml
|
|
259
|
+
jobs:
|
|
260
|
+
verify:
|
|
261
|
+
if: github.event_name == 'push'
|
|
262
|
+
runs-on: ubuntu-latest
|
|
263
|
+
permissions:
|
|
264
|
+
contents: read
|
|
265
|
+
id-token: write # the only credential: GitHub's id-token for this run
|
|
266
|
+
steps:
|
|
267
|
+
- uses: actions/checkout@v4
|
|
268
|
+
- uses: Solus-QE/voeu@v0
|
|
269
|
+
with:
|
|
270
|
+
command: verify
|
|
271
|
+
upload: "true"
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
The Action asks GitHub for an OIDC id-token with the `voeu` audience and POSTs the run with it.
|
|
275
|
+
Voeu verifies the token's signature, issuer and audience and takes the repository from its
|
|
276
|
+
`repository_id` claim — nothing in the run itself decides where it lands. There is no secret to
|
|
277
|
+
configure, and nothing to rotate. Fork-triggered workflows are never issued an id-token, which is
|
|
278
|
+
why this runs on `push` and not `pull_request`. Reporting never fails the job: a missing
|
|
279
|
+
permission, a repository without the Voeu GitHub App, or an unreachable endpoint is a warning in
|
|
280
|
+
the log, and the run's own exit code is unaffected.
|
|
281
|
+
|
|
282
|
+
## Exit codes
|
|
283
|
+
|
|
284
|
+
| | |
|
|
285
|
+
|---|---|
|
|
286
|
+
| `0` | no failing check. For `watch`: the baseline still holds |
|
|
287
|
+
| `1` | at least one check failed. For `watch`: the baseline no longer holds |
|
|
288
|
+
| `2` | unsupported target or missing configuration |
|
|
289
|
+
| `3` | infrastructure failure |
|
|
290
|
+
|
|
291
|
+
`2` is deliberately not `1`. "We could not assess this" is not "your app is broken," and CI that
|
|
292
|
+
conflates them teaches people to ignore the tool. The Action leaves the job green on `2` with a
|
|
293
|
+
notice.
|
|
294
|
+
|
|
295
|
+
## Four verdicts, never three
|
|
296
|
+
|
|
297
|
+
`passed` · `failed` · `not_applicable` · **`not_verified`**
|
|
298
|
+
|
|
299
|
+
`not_verified` is the load-bearing one. It means the check is in scope, applies to your
|
|
300
|
+
repository, and was not decided — the budget ran out, the stack was unsupported, an adapter
|
|
301
|
+
capability was missing. Folding it into `passed` is how a verification tool starts lying; folding
|
|
302
|
+
it into `failed` is how it starts crying wolf. It gets its own state and it is printed.
|
|
303
|
+
|
|
304
|
+
Every finding also carries **how** it was reached — `static`, `simulated`, or `live` — because
|
|
305
|
+
"this patch is verified" without an evidence level attached is the sentence this tool exists to
|
|
306
|
+
stop people from saying.
|
|
307
|
+
|
|
308
|
+
## What it will not tell you
|
|
309
|
+
|
|
310
|
+
- **Whether your app is publicly distributed.** `shopify.app.toml` has no `distribution` field;
|
|
311
|
+
the setting lives in the Dev Dashboard and is not recoverable from source. Pass
|
|
312
|
+
`--distribution public` if you know, or treat findings as conditional.
|
|
313
|
+
- **That your migration is complete.** `verify` proves fourteen behaviours held against a
|
|
314
|
+
simulator. It does not cover webhook verification, online sessions, billing, or Admin API
|
|
315
|
+
operations beyond the one used to prove authentication.
|
|
316
|
+
- **That the simulator is right.** Where it diverges from Shopify, results are wrong with it.
|
|
317
|
+
Every report says so, and `voeu differential --live` is the command that closes it —
|
|
318
|
+
built, self-tested, and waiting on a Partner account rather than on more code. Until
|
|
319
|
+
that has run, no result in this repository carries the `live` evidence level.
|
|
320
|
+
|
|
321
|
+
Voeu also never runs token conversion against your installed merchants. Conversion is
|
|
322
|
+
[one-time and irreversible per shop](https://github.com/Shopify/shopify-app-js/blob/main/packages/apps/shopify-api/docs/reference/auth/migrateToExpiringToken.md);
|
|
323
|
+
that call stays yours to make.
|
|
324
|
+
|
|
325
|
+
## Contract packs
|
|
326
|
+
|
|
327
|
+
Two, and they ask different questions — because the failure a provider can hand you is a
|
|
328
|
+
property of that provider, not a house style.
|
|
329
|
+
|
|
330
|
+
| Pack | Asks | Families exercised |
|
|
331
|
+
|---|---|---|
|
|
332
|
+
| `shopify-token-lifecycle@0.1.0` | Does your app survive a credential that expires, rotates, and can be lost mid-refresh? | credential-lifecycle, deferred-execution, concurrency, partial-failure, tenant-isolation |
|
|
333
|
+
| `stripe-idempotency@0.1.0` | Across a retry, does an operation keep its identity — and does a genuinely new one get a fresh one? | retry-identity, partial-failure, tenant-isolation |
|
|
334
|
+
|
|
335
|
+
Wrong in one direction bills the customer twice; wrong in the other silently no-ops. Neither is
|
|
336
|
+
visible in the source, which is why the Stripe pack exists at all.
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
npx @voeu/cli verify --provider stripe --reference
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**What the Stripe pack does not do yet, stated plainly.** It runs against the built-in reference
|
|
343
|
+
application only. Verifying *your* Stripe integration needs an adapter interface this CLI does not
|
|
344
|
+
yet document, a scaffold for `voeu init`, and static detectors so `inspect` has something to say.
|
|
345
|
+
None of those are written, so `--provider stripe --repo .` exits 2 and says so rather than
|
|
346
|
+
reporting every scenario `not_verified` while looking supported.
|
|
347
|
+
|
|
348
|
+
`voeu contract --provider stripe` does work: it prints seven claims quoted from Stripe's
|
|
349
|
+
idempotency reference, two of them carrying an open question that a live account would settle.
|
|
350
|
+
|
|
351
|
+
`credential-lifecycle` and `deferred-execution` are reported **not applicable** for Stripe rather
|
|
352
|
+
than omitted: secret keys do not expire or rotate on a schedule, and an absence that looks like
|
|
353
|
+
coverage is the thing the four verdicts exist to prevent.
|
|
354
|
+
|
|
355
|
+
## Requirements
|
|
356
|
+
|
|
357
|
+
Node 22 or later. One runtime dependency.
|
|
358
|
+
|
|
359
|
+
## Corrections
|
|
360
|
+
|
|
361
|
+
If a finding is wrong, it is wrong in a way that matters, and a correction is more useful than a
|
|
362
|
+
polite silence.
|