@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.
- package/CHANGELOG.md +256 -0
- package/README.md +101 -1
- package/SECURITY.md +190 -52
- package/bin/sheleg-dev.js +18 -0
- package/package.json +2 -2
- package/plugins/sheleg-dev/.claude-plugin/plugin.json +1 -1
- package/plugins/sheleg-dev/hooks/hooks.json +29 -0
- package/plugins/sheleg-dev/hooks/lib/moneygate.js +587 -0
- package/plugins/sheleg-dev/hooks/money-gate.js +68 -0
- package/plugins/sheleg-dev/skills/ad-tracking/SKILL.md +7 -3
- package/plugins/sheleg-dev/skills/ad-tracking/fixtures/README.md +76 -0
- package/plugins/sheleg-dev/skills/ad-tracking/fixtures/assert-dedup-contract.mjs +249 -0
- package/plugins/sheleg-dev/skills/ad-tracking/fixtures/manifest.json +75 -0
- package/plugins/sheleg-dev/skills/ad-tracking/fixtures/purchase-capi-from-thank-you-page.json +32 -0
- package/plugins/sheleg-dev/skills/ad-tracking/fixtures/purchase-capi-server.json +26 -0
- package/plugins/sheleg-dev/skills/ad-tracking/fixtures/purchase-pixel-browser.json +12 -0
- package/plugins/sheleg-dev/skills/ad-tracking/fixtures/reference-emitter.mjs +129 -0
- package/plugins/sheleg-dev/skills/ad-tracking/references/meta-linkedin.md +13 -1
- package/plugins/sheleg-dev/skills/crypto-payments/SKILL.md +68 -0
- package/plugins/sheleg-dev/skills/crypto-payments/references/heleket-provider.md +349 -10
- package/plugins/sheleg-dev/skills/stripe-billing/SKILL.md +1 -1
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/README.md +110 -0
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/assert-money-invariants.mjs +404 -0
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/charge-refunded-partial.json +52 -0
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/charge-refunded-remainder.json +63 -0
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/checkout-session-async-payment-failed.json +43 -0
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/checkout-session-completed-async-unpaid.json +43 -0
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/checkout-session-completed-paid.json +42 -0
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-cycle-next-period.json +70 -0
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-cycle-redelivery.json +70 -0
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-cycle.json +61 -0
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-update-proration.json +90 -0
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/manifest.json +172 -0
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/reference-handler.mjs +315 -0
- package/plugins/sheleg-dev/skills/stripe-billing/references/testing-and-local-dev.md +38 -2
- 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
|
-
|
|
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
|
|