@ssheleg/sheleg-dev 0.6.0 → 0.7.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 (36) hide show
  1. package/CHANGELOG.md +256 -0
  2. package/README.md +101 -1
  3. package/SECURITY.md +190 -52
  4. package/bin/sheleg-dev.js +18 -0
  5. package/package.json +2 -2
  6. package/plugins/sheleg-dev/.claude-plugin/plugin.json +1 -1
  7. package/plugins/sheleg-dev/hooks/hooks.json +29 -0
  8. package/plugins/sheleg-dev/hooks/lib/moneygate.js +587 -0
  9. package/plugins/sheleg-dev/hooks/money-gate.js +68 -0
  10. package/plugins/sheleg-dev/skills/ad-tracking/SKILL.md +7 -3
  11. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/README.md +76 -0
  12. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/assert-dedup-contract.mjs +249 -0
  13. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/manifest.json +75 -0
  14. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/purchase-capi-from-thank-you-page.json +32 -0
  15. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/purchase-capi-server.json +26 -0
  16. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/purchase-pixel-browser.json +12 -0
  17. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/reference-emitter.mjs +129 -0
  18. package/plugins/sheleg-dev/skills/ad-tracking/references/meta-linkedin.md +13 -1
  19. package/plugins/sheleg-dev/skills/crypto-payments/SKILL.md +68 -0
  20. package/plugins/sheleg-dev/skills/crypto-payments/references/heleket-provider.md +349 -10
  21. package/plugins/sheleg-dev/skills/stripe-billing/SKILL.md +1 -1
  22. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/README.md +110 -0
  23. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/assert-money-invariants.mjs +404 -0
  24. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/charge-refunded-partial.json +52 -0
  25. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/charge-refunded-remainder.json +63 -0
  26. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/checkout-session-async-payment-failed.json +43 -0
  27. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/checkout-session-completed-async-unpaid.json +43 -0
  28. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/checkout-session-completed-paid.json +42 -0
  29. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-cycle-next-period.json +70 -0
  30. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-cycle-redelivery.json +70 -0
  31. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-cycle.json +61 -0
  32. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-update-proration.json +90 -0
  33. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/manifest.json +172 -0
  34. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/reference-handler.mjs +315 -0
  35. package/plugins/sheleg-dev/skills/stripe-billing/references/testing-and-local-dev.md +38 -2
  36. package/plugins/sheleg-dev/skills/stripe-billing/references/webhook-events.md +43 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,262 @@ All notable changes to this project are documented here.
4
4
  Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
  Versioning: [SemVer](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## v0.7.0 — 2026-08-19
8
+
9
+ **The pack that takes money had the weakest controls in the family, and every one of them
10
+ was a sentence.** A conformance audit against the Proof of Done manifesto scored this
11
+ member last on both counts it measured — most requirements absent, fewest mechanically
12
+ enforced — and all three of its enforced rows were the artifact layer, none the money
13
+ layer. The manifesto's own test failed here literally: *a credential that cannot reach
14
+ production is stronger than a sentence saying not to use it there.*
15
+
16
+ Four changes, in the order they were built: the security document stopped describing a
17
+ different skill, the crypto provider gained a credential boundary, the four manual-gate
18
+ categories became a hook that refuses, and the money invariants became fixtures a reader
19
+ runs.
20
+
21
+ ### The four money invariants were prose, and prose delegates enforcement to the reader
22
+
23
+ This pack already knew the invariants a generated integration gets wrong in the ways no
24
+ screen shows — the webhook is the payment and the redirect only proves a browser, the same
25
+ `event_id` on both sides or the revenue counts twice, `amount_refunded` arrives cumulative,
26
+ delivery is not ordered — and shipped every one of them as a paragraph. The giveaway was in
27
+ the document whose whole subject is proving a money defect would be caught: *Mutation testing
28
+ — the only proof that counts* (`references/testing-and-local-dev.md:157` at `90e9621`), whose
29
+ method was the sentence **"For every guard, delete it and re-run."** An instruction where a
30
+ test belongs, in the section arguing that tests are what count.
31
+
32
+ `manifesto.md:200` is that a test is stronger than an instruction, and `:289` that evidence
33
+ proves no more than it observed. So the invariants ship as something a reader runs.
34
+
35
+ **`stripe-billing/fixtures/` and `ad-tracking/fixtures/`** — copy either directory in and run
36
+ it. Twelve provider webhook bodies shaped the way the provider sends them: the January
37
+ renewal, the same `evt_` re-delivered, a mid-cycle proration invoice with two proration lines,
38
+ the February renewal (delivered *first*, it is the out-of-order pair), a `4000`-then-`9000`
39
+ cumulative refund against a `9000` charge, a bank-debit session that completed `unpaid` and
40
+ the `async_payment_failed` that followed it, a card session that cleared, and the pixel
41
+ arguments plus the Conversions API body one purchase produces. Plus one payload that must
42
+ never exist, kept as a fixture: the CAPI body a thank-you-page-sourced emitter sends for a
43
+ charge that never cleared.
44
+
45
+ **Eighteen assertions across two packs, and every one has been watched failing.**
46
+ `node assert-money-invariants.mjs --self-test` deletes one rule at a time from the reference
47
+ handler and requires the matching assertion to go red; it fails if the measured set differs
48
+ from the declared one in *either* direction. Nine rules on the Stripe side, five on the
49
+ tracking side, each with a fixture that isolates it.
50
+
51
+ **Isolating them is the whole engineering, and three masking pairs were found by the tool
52
+ rather than by inspection.** SD-03's mutation sweep reached 10/10 only on its second attempt
53
+ because two mechanisms were each leaning on the other's fixtures; the same shape appeared
54
+ three times here. A redelivery judged by the grant count alone is refused by the event claim
55
+ **or** by the per-period grant marker, so it proves neither — the fixture is kept, declares
56
+ `claim+grant-marker` as a multi-rule mutant, and the claim's real isolator is a **concurrent**
57
+ delivery (the marker reads before it writes, and a read is a round trip) while the marker's is
58
+ the **reconciliation** entry point, which carries no event id at all. A duplicate
59
+ `charge.refunded` is stopped by the claim and by `increment <= 0` alike, so the arithmetic is
60
+ measured by the two-step pair, whose events carry different ids. And in `ad-tracking`, reading
61
+ both sides out of one emitter meant deleting the browser event turned the id *and* name
62
+ assertions red — fixed by comparing the server's output against the shipped pixel fixture, a
63
+ boundary the server does not control.
64
+
65
+ **The fixtures cannot rot.** `check_money_fixtures()` in `test/validate.py` (15 → 16 checks)
66
+ reads each `fixtures/manifest.json` and requires both directions: every claimed invariant has
67
+ a fixture that exists and an assertion by that name, every fixture is claimed by a row, every
68
+ claiming document still carries its recorded phrase and names both the invariant and a path
69
+ under `fixtures/`, and every `fixtures/…` token in the skill's markdown resolves — the bounded
70
+ widening of the B-79 path guard that B-82 asked for. `test/fixtures_test.js` (13 checks) runs
71
+ both packs as processes in both modes and then neuters three assertions to prove `--self-test`
72
+ notices, because a self-test that cannot fail is the same defect one level up.
73
+
74
+ Five negative self-tests, 23 → **28**: a claimed invariant whose fixture is gone, a fixture no
75
+ invariant claims, a reference pointing at a fixture that is not there, a claim reworded away
76
+ from its fixture, and an assertion that can no longer fail. The guard's own first run found a
77
+ real orphan — `checkout-session-completed-paid.json`, claimed by nothing — which is why
78
+ `paid-session-grants-once` exists: without a positive control, a handler refusing *every*
79
+ checkout session would have satisfied `unpaid-session-grants-nothing`.
80
+
81
+ `SECURITY.md` moved with it, including one claim this change **falsified**: *"There is still
82
+ no runtime code inside the six skills"* stopped being true, because two of them now ship
83
+ runnable `.mjs`. It is replaced by what that code does and by the grep that proves the
84
+ boundary — no `child_process`, no `fetch`, no socket, no write, and no `process.env` read at
85
+ all; `readFileSync` of the JSON beside it is the entire I/O surface. Recounted: 30 → **50**
86
+ files in the payload, 4 → **22** non-markdown, 36 → **56** in the tarball.
87
+
88
+ ### `SECURITY.md` described a different skill, in the published tarball
89
+
90
+ The document was a wholesale copy of `seo-aeo-audit`'s. It opened *"documentation plus one
91
+ small Python script"* and named `scripts/page_audit.py`, a `commands/` directory, a
92
+ `cursor/rules/` directory and `references/threats-and-defense.md` — this repository has
93
+ never contained any of them; there is no runtime code in the skill payload at all. A whole
94
+ section described the network behaviour of that auditor, down to `--url`, `--timeout` and
95
+ `--max-bytes`. It closed with *Verifying for yourself*, three commands of which **two could
96
+ not run**: `python3 test/test_page_audit.py` exits 2, and a `grep` at
97
+ `plugins/sheleg-dev/skills/sheleg-dev/scripts/page_audit.py` exits 2 under a skill
98
+ directory named `sheleg-dev` that has never existed.
99
+
100
+ `package.json` ships `SECURITY.md` in the tarball, so the pack was inviting an outside
101
+ reader to verify its safety with commands that fail — for a pack whose subject is payment
102
+ credentials and sign-in. **Six dead references** in one document.
103
+
104
+ Rewritten against what the tree actually returns: 27 files in the skill payload at that
105
+ commit, 26 of them markdown and one a plugin manifest — 30 once the manual gate shipped
106
+ three files beside them, see below; two installers using nothing but Node built-ins and
107
+ coreutils, with no network, no subprocess and no npm lifecycle script; the one destructive
108
+ difference between them (`install.sh` runs `rm -rf` on the destination every time,
109
+ `bin/sheleg-dev.js` skips unless `--force`); credential *names* in examples and no key, with
110
+ the one live-key-shaped line in the payload named and explained; and the five vendor commands
111
+ the advice can lead an agent to run, each with the `file:line` that says it. Every command in
112
+ the new *Verifying for yourself* block was run and its output is what the document claims.
113
+
114
+ ### The guard the B-47 guard was waiting for
115
+
116
+ B-47 fixed this disease inside one table of `CONTRIBUTING.md` and said, in the guard's own
117
+ docstring, that going wider would flag paths being *discussed* rather than used. This is the
118
+ widening, bounded twice instead of not at all:
119
+
120
+ - **By corpus** — the documents whose subject is this repository: `README.md`, `SECURITY.md`,
121
+ `CONTRIBUTING.md`, `.github/PULL_REQUEST_TEMPLATE.md`. A skill reference naming the
122
+ reader's `next.config.ts` or `src/lib/heleket.ts` is describing their project; 41 such
123
+ names in the payload would have been false positives, and a guard with 41 of those gets
124
+ switched off.
125
+ - **By `FOREIGN_BY_DESIGN`** — the cross-repo signposts, enumerated one document at a time
126
+ with a reason. An exemption the document stops naming **fails**, so the list cannot widen
127
+ into a blanket.
128
+
129
+ It reads fenced blocks as well as inline spans, because the worst reference in the old
130
+ document was a fenced command, and it understands `path:line`: an address past the end of the
131
+ file resolves to nothing, which is the same defect one level down.
132
+
133
+ Watched refusing three plants and the real defect. Turned up two more of its own on the first
134
+ run: `.github/PULL_REQUEST_TEMPLATE.md` asked every contributor to paste output from
135
+ `python3 test/test_page_audit.py` and to avoid relative links in `cursor/rules/*.mdc` — both
136
+ inherited from the same copy. Also `CONTRIBUTING.md` named `agent_sync.py` with no owner; it
137
+ now says the script ships with the `agent-sync` skill.
138
+
139
+ ### `crypto-payments` had no test/live credential boundary, in the skill about taking money
140
+
141
+ There was one `HELEKET_API_KEY`. It is *also* the webhook signing secret, so it cannot be
142
+ scoped down; the key carries no `test`/`live` marker; there is one host; and "test mode" is a
143
+ **toggle in merchant settings** — a property of the account, over the same key. The document
144
+ handed that key to the reader in a local-development block with no environment declared beside
145
+ it and then pointed at the dashboard toggle, so a dev, CI or agent run held the production
146
+ credential and nothing in the pack said so. Manifesto M-06 — *a credential that cannot reach
147
+ production is stronger than a sentence saying not to use it there, because the last control
148
+ still works after context loss* — failed literally, in the one skill whose subject is money.
149
+
150
+ **Established the provider's model before designing for it**, and it is worse than the brief
151
+ assumed: Heleket offers no separate test credential at all, so the Stripe-shaped fix of
152
+ reading the environment out of the key prefix does not exist here. The boundary is therefore
153
+ built on the two non-secret things Heleket does expose — the merchant UUID, and a 12-hex
154
+ SHA-256 prefix of the live key — pinned as `HELEKET_LIVE_MERCHANT_ID` and
155
+ `HELEKET_LIVE_KEY_FINGERPRINT`. Same shape as the house pattern in
156
+ `stripe-billing/references/price-integrity.md` (*a declaration separate from the secret, so
157
+ the two can be checked against each other*), different comparand.
158
+
159
+ Shipped: `HELEKET_ENV` with **no default**, because a default is the control disappearing the
160
+ first time somebody copies a `.env`; `assertHeleketEnv()` as a copy-whole snippet that runs at
161
+ module load rather than in the checkout handler, so a run that merely *holds* the key fails
162
+ too; refusals in **both** directions — a live credential declared test, and the quiet one, a
163
+ test credential declared live, where invoices settle to a merchant nobody reconciles and
164
+ nothing errors until the revenue is missing; a refusal for `SKIP_BILLING=true` under
165
+ `HELEKET_ENV=production`, which is a free-money path rather than a shortcut; and a refusal
166
+ when nothing is pinned and a *test* run therefore cannot prove it is not live, because "could
167
+ not prove it was safe" must never read as "it was safe". Named error codes, not sentences, so
168
+ a rewording cannot silently remove a control and an operator can alert on them.
169
+
170
+ **And the exposure that remains, written down.** The assertion checks the declaration; it does
171
+ not limit the credential. Unless a second merchant account is available — which this document
172
+ cannot confirm and now says so instead of implying it — a developer following Option B holds a
173
+ production key, the verifier cannot be issued without the invoice-creating power attached, and
174
+ rotation is the only revocation. An unavoidable risk that is named is a different object from
175
+ one that is silent.
176
+
177
+ The gate went from 13 checks to 14: `check_credential_boundary()` requires that every copyable
178
+ block assigning the secret also assigns the declared environment, that the assertion exists to
179
+ be copied, that both refusal codes are present, and that the residual exposure is written.
180
+ Table-driven, one row, and the empty-corpus case fails rather than passing. Three negative
181
+ self-tests (12 → 15), one per direction plus the original defect restored verbatim. The
182
+ assertion's logic was run over 11 cases, 11/11. `stripe-billing` states the same control at
183
+ `references/price-integrity.md:62-64` and asks for it at `testing-and-local-dev.md:210` while
184
+ shipping no assertion — filed as B-86 rather than fixed here, because a guard added in the
185
+ same breath as the defect it flags turns the gate red for work this change did not do. The
186
+ other three credential-holding skills were not looked at (B-87), and this reference is now
187
+ a 4× size outlier among references, filed as B-88 with a split rather than a trim as the
188
+ remedy — the precedent this repository set at v0.6.0.
189
+
190
+ ### The four manual-gate categories were prose, and prose stops nothing
191
+
192
+ `pod-manifesto/manifesto.md:204` names money movement, irreversible action, production
193
+ access and destructive operations as the authorised person's to decide: *"The agent
194
+ prepares the decision and its evidence. The authorised person decides."* And `:200`: *"a
195
+ precondition is stronger than a warning."*
196
+
197
+ This pack named all four and stopped none. `crypto-payments/SKILL.md:310` said **"Never
198
+ auto-refund from the webhook. Route holds and refunds to a queue a human can see"**;
199
+ `stripe-billing/references/webhook-events.md:170` said a dispute is **"money already gone
200
+ plus a fee … route it to a human — evidence has a deadline"**. The plugin shipped no hooks,
201
+ no permission list and no gate, so both were advice to a reader who could skip it and to an
202
+ agent that never saw it. SD-02's boot assertion was the one real control here, and it runs
203
+ *inside the reader's application* — it cannot see a shell that merely **exports** the same
204
+ live merchant credential before any application starts.
205
+
206
+ **Shipped: a `PreToolUse` hook, `plugins/sheleg-dev/hooks/`.** Eight refusals, unless the
207
+ authorised person has signed the category off for the session: a live-shaped
208
+ `sk_live_…`/`rk_live_…` key reaching a command; an export of `HELEKET_API_KEY` — the one
209
+ credential SD-02 established has no test variant — in a run declaring `test` or declaring
210
+ nothing; `stripe refunds create` or a POST to a `…/v1/refunds` URL or a `create_refund`
211
+ tool; a payout or transfer, including Heleket's `…/v1/payout`; `stripe disputes close`; an
212
+ explicit `--live` flag; a command setting the gate's **own** switch; and `SKIP_BILLING=true`
213
+ in a run declaring production. The last two are never authorisable, and **no** category is
214
+ authorisable in a run that declares a non-production environment — a run that says it is a
215
+ test and then refunds a real card is incoherent whichever half is true.
216
+
217
+ Three invariants borrowed from the family umbrella, each one enforced by the validator so
218
+ it cannot be quietly collapsed. **The deciding is a pure module** —
219
+ `hooks/lib/moneygate.js`, payload and environment in, verdict out, no `require` of its own
220
+ and no filesystem — and the hook only moves bytes. **The hook fails silent**: it catches
221
+ everything and exits 0, because a guard that throws breaks every turn in every session,
222
+ including sessions of packs that never asked for this one; and every refusal names its
223
+ remedy, because one without a next step teaches an operator to switch the hook off.
224
+ **There is no `if` filter** on the hook entry: the reference calls that filter best-effort
225
+ and says it fails open on a command it cannot parse, and `Bash(stripe refunds*)` is exactly
226
+ the shape a `bash -c '…'` wrapper defeats.
227
+
228
+ Two defects this program proved elsewhere shaped the reading. Nothing here decides from
229
+ state the command is about to change (the umbrella's own gate asked "is anything staged?"
230
+ at `PreToolUse`, and an add-then-commit line walked through it). And the gate **reads what
231
+ would run**: a heredoc body fed to `cat` is data while a body fed to `bash` is a script; a
232
+ whole-line comment does not run; an assignment-shaped token is only an assignment in
233
+ command-prefix position, so `grep 'HELEKET_API_KEY=' plugins/` searches rather than
234
+ exports; a money endpoint must be a URL rather than a bare path; and a live key is
235
+ recognised by its **shape**, not its prefix, so `SECURITY.md`'s own sweep for
236
+ `sk_live_[A-Za-z0-9]` still runs.
237
+
238
+ `node test/moneygate_test.js` — **65 fixtures, both directions**, and the allow-plants are
239
+ real commands from this repository: that sweep, a `.env` heredoc fed to `cat`, a
240
+ commented-out `stripe refunds create`, a test-mode `sk_test_` key, and the non-secret
241
+ `HELEKET_LIVE_MERCHANT_ID` pin `assertHeleketEnv()` *requires* in a test run. `npm test`
242
+ now runs the validator and then the fixtures.
243
+
244
+ The gate went from 14 checks to 15 with `check_manual_gate()`. Eight negative self-tests
245
+ (15 → 23): five plant a defect in the gate's shipped shape and require the validator to
246
+ refuse, three break its decision module and require its own fixtures to go red. **Two of
247
+ those three were written because the first attempt went uncaught** — every `sk_live_`
248
+ allow-plant was leaning on the reader denylist while every reader allow-plant was leaning
249
+ on the key's shape, so neither mechanism was individually proven. A third plant escaped
250
+ because the validator's "the hook must require the pure module" check was satisfied by the
251
+ hook's own doc comment; it now reads the `require` expression.
252
+
253
+ **Registration, and what enforces it.** As a Claude Code plugin the hook is live when the
254
+ plugin is enabled, and `claude plugin validate --strict` schema-validates
255
+ `hooks/hooks.json`. Installed by `npx @ssheleg/sheleg-dev`, `install.sh` or `npx skills
256
+ add`, only the six skills are copied and **the gate is absent**: `README.md` → *The manual
257
+ gate* carries the settings snippet, the installer now prints the reminder, and **nothing
258
+ enforces it** — filed as B-90. Nothing writes to a person's settings file; that is the one
259
+ thing this repository must never do unasked. `SECURITY.md`'s counts move with the three new
260
+ files: 27 → **30** in the payload, 33 → **36** in the tarball, and the gate's whole reach is
261
+ two `require` calls with no `child_process`, no `fetch` and no `fs` on either path.
262
+
7
263
  ## v0.6.0 — 2026-08-16
8
264
 
9
265
  ### The file that promised the deduplication contract contained none of it
package/README.md CHANGED
@@ -20,6 +20,21 @@ Part of the [ssheleg skill family](https://github.com/ssheleg/sshlg-skills).
20
20
 
21
21
  Each carries its own references and loads them only when the work reaches them.
22
22
 
23
+ **Two of them ship a suite rather than a warning.** `stripe-billing` and `ad-tracking`
24
+ carry a `fixtures/` directory — real provider webhook bodies, an assertion pack, and a
25
+ `--self-test` that deletes one rule at a time so you can watch every assertion fail. Copy
26
+ the directory in and run it:
27
+
28
+ ```bash
29
+ node plugins/sheleg-dev/skills/stripe-billing/fixtures/assert-money-invariants.mjs --self-test
30
+ node plugins/sheleg-dev/skills/ad-tracking/fixtures/assert-dedup-contract.mjs --self-test
31
+ ```
32
+
33
+ That covers the invariants whose failure is money rather than an error page: the webhook is
34
+ the payment and the redirect only proves a browser, one `event_id` on both sides or the
35
+ revenue counts twice, a refund total that arrives cumulative, and delivery that is not
36
+ ordered. Each skill's `fixtures/README.md` is the map.
37
+
23
38
  **`stripe-billing`** — Stripe's own agent toolchain first (CLI, `stripe agent
24
39
  setup`, a sandbox without an account, the MCP implementation planner), then the
25
40
  seam it cannot help with: a pinned API version and SDK retries instead of a loop
@@ -95,6 +110,87 @@ Restart your agent afterwards — skills load at session start.
95
110
 
96
111
  ---
97
112
 
113
+ ## The manual gate
114
+
115
+ Four things this pack tells you to route to a human — a refund, a payout, closing a
116
+ dispute, a live credential — used to be **sentences**. `crypto-payments/SKILL.md` says
117
+ "Never auto-refund from the webhook. Route holds and refunds to a queue a human can
118
+ see"; `stripe-billing/references/webhook-events.md` says a dispute is "money already
119
+ gone plus a fee … route it to a human". An agent reading neither, and a shell nobody
120
+ read, were unaffected by both.
121
+
122
+ So the plugin ships a `PreToolUse` hook. It refuses eight things unless the authorised
123
+ person has signed the category off **for this session**:
124
+
125
+ | Category | What it stops |
126
+ |---|---|
127
+ | `live-key` | a live-shaped `sk_live_…` / `rk_live_…` reaching a command |
128
+ | `credential` | exporting a credential whose provider issues no test value — `HELEKET_API_KEY` — in a run that declares `test`, or declares nothing |
129
+ | `refund` | `stripe refunds create`, a POST to a `…/v1/refunds` URL, or a `create_refund` tool |
130
+ | `payout` | `stripe payouts\|transfers create`, a POST to `…/v1/payouts`, `…/v1/transfers` or Heleket's `…/v1/payout` |
131
+ | `dispute` | `stripe disputes close`, or `…/v1/disputes/…/close` — closing one accepts the loss |
132
+ | `live-flag` | an explicit `--live` / `--live-mode` flag |
133
+ | `self-authorisation` | a command that sets this gate's own switch. Never authorisable |
134
+ | `skip-billing` | `SKIP_BILLING=true` in a run declaring production — the free-money path. Never authorisable |
135
+
136
+ **Authorising one.** The authorised person exports the category — or `all` — in the
137
+ shell that starts the session, and starts a new one:
138
+
139
+ ```bash
140
+ SHELEG_DEV_LIVE_AUTHORISED=refund claude
141
+ ```
142
+
143
+ Two properties of that make it a decision rather than a formality. It is read from the
144
+ environment the **hook** was spawned with, so a value exported inside a tool call never
145
+ reaches it — and a command that tries is refused as `self-authorisation`. And **a run
146
+ declaring a non-production environment can never be authorised**: if `SHELEG_DEV_ENV`,
147
+ `HELEKET_ENV`, `STRIPE_ENV`, `PAYMENTS_ENV`, `APP_ENV` or `NODE_ENV` says anything other
148
+ than production, no variable makes a live operation pass. A run that says it is a test and
149
+ then refunds a real card is incoherent whichever half is true.
150
+
151
+ `SHELEG_DEV_MONEY_GATE=off` disables it. That is deliberate and documented: a gate with no
152
+ off switch gets deleted instead of disabled.
153
+
154
+ **Registration, and what enforces it.** Nothing in this repository writes to your
155
+ settings, by design.
156
+
157
+ - **Installed as a Claude Code plugin** — `plugins/sheleg-dev/hooks/hooks.json` travels
158
+ with the plugin and the hook is live as soon as the plugin is enabled. Nothing further
159
+ to do, and no per-hook switch: enablement is the whole control.
160
+ - **Installed by `npx @ssheleg/sheleg-dev`, `install.sh` or `npx skills add`** — those
161
+ copy the six skills into `~/.claude/skills/` and **carry no hook**. The gate is prose
162
+ again until you register it yourself, in `~/.claude/settings.json` or a project's
163
+ `.claude/settings.json`:
164
+
165
+ ```json
166
+ { "hooks": { "PreToolUse": [ { "matcher": "Bash",
167
+ "hooks": [ { "type": "command",
168
+ "command": "node \"$HOME/.claude/skills/.sheleg-dev-hooks/money-gate.js\"",
169
+ "timeout": 15 } ] } ] } }
170
+ ```
171
+
172
+ Copy `plugins/sheleg-dev/hooks/` to that path first. `npx @ssheleg/sheleg-dev` prints
173
+ this reminder; **nothing enforces it**, which is why the plugin channel is the
174
+ recommended one.
175
+
176
+ The deciding is a pure function in
177
+ [`plugins/sheleg-dev/hooks/lib/moneygate.js`](plugins/sheleg-dev/hooks/lib/moneygate.js) —
178
+ payload and environment in, verdict out, no filesystem and no `HOME`.
179
+ [`plugins/sheleg-dev/hooks/money-gate.js`](plugins/sheleg-dev/hooks/money-gate.js) only
180
+ moves bytes, catches everything and exits 0: a hook that throws breaks every turn in
181
+ every session, including sessions of packs that never asked for this one. There is
182
+ deliberately **no `if` filter** on the hook entry — the Claude Code reference calls that
183
+ filter best-effort and says it fails open on a command it cannot parse.
184
+
185
+ Both directions are fixtured in
186
+ [`test/moneygate_test.js`](test/moneygate_test.js), and the allow-plants are real commands
187
+ from this repository: `SECURITY.md`'s own sweep for `sk_live_[A-Za-z0-9]`, a `.env`
188
+ heredoc fed to `cat`, a commented-out `stripe refunds create`, and the non-secret
189
+ `HELEKET_LIVE_MERCHANT_ID` pin that `assertHeleketEnv()` *requires* in a test run. A guard
190
+ that refuses correct input gets switched off, and then there is no gate at all.
191
+
192
+ ---
193
+
98
194
  ## A note on `crypto-payments`
99
195
 
100
196
  The skill is provider-neutral on purpose. The reusable engineering — signature
@@ -113,7 +209,7 @@ correctly. It does not tell you which one to trust.
113
209
  ## Verify
114
210
 
115
211
  ```bash
116
- python3 test/validate.py
212
+ npm test # the validator, the manual gate's 65 fixtures, the money fixtures' 13 checks
117
213
  ```
118
214
 
119
215
  One version across `package.json`, `plugin.json`, `marketplace.json` and the top
@@ -122,6 +218,10 @@ matter does not error — hosts truncate it silently, which is worse); and
122
218
  `SKILL.md` ↔ `references/` agreement in **both** directions, so neither a
123
219
  dangling link nor a file nobody loads can ship.
124
220
 
221
+ Plus, for the money fixtures, that every assertion has been watched failing and that no
222
+ two rules are covering for each other — `test/fixtures_test.js` neuters three assertions
223
+ and requires each pack's `--self-test` to notice.
224
+
125
225
  CI plants a defect against every one of those guards and requires the validator
126
226
  to fail. A green from a check nobody has watched fail is not evidence.
127
227