@ssheleg/sheleg-dev 0.10.5 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,112 @@ 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.11.1 — the evals stop being a plan, and the last tails get their receipts
8
+
9
+ The 2026-08-29 skill audit left four tails (DEV-06 through DEV-08, DEV-10), and the
10
+ eval suite's own RESULTS.md opened with "authored, schema-validated, never executed
11
+ against a model". This release closes all four; that sentence is replaced by two dated
12
+ model runs and the method that produced them.
13
+
14
+ - **The behavioral evals cover all seven skills and carry dated runs** (DEV-10, board
15
+ B-103). Three scenarios are authored for the skills that had none — `crypto-payments`
16
+ (crediting under hostile deliveries), `error-tracking` (scrubbing layers and release
17
+ parity), `frontend-performance` (vitals moved and proven) — bringing `scenarios.json`
18
+ to six, green under `evals_validate.py` with the planted-defect self-test still
19
+ caught. Then the whole suite was executed on 2026-08-31: 14 trigger queries × 3 fresh
20
+ subagent sessions × 2 models (haiku, sonnet — 84 blind probes), plus all six
21
+ scenarios' expected-behavior lines scored against fresh sonnet implementation designs.
22
+ Haiku: train 23/24, validation 15/18. Sonnet: train 23/24, validation 16/18; scenario
23
+ lines 23/24 (s01's design named no refund-path test). Two findings are recorded
24
+ rather than tuned away: "Explain what LCP means" (q13, a negative case) pulls
25
+ `frontend-performance` in 5 of 6 runs across both models, and one sonnet run routed
26
+ the crypto implementation query to `task-pipeline` — coexistence changes routing,
27
+ exactly as the eval README warns. Method and its limits: `test/evals/RESULTS.md`.
28
+ - **`ad-tracking`'s last two undated vendor facts were re-verified, and one was stale**
29
+ (DEV-06, B-104). The Enhanced Conversions step said `Tools → Conversions → …` — the
30
+ pre-redesign console path; checked 2026-08-31 against
31
+ `support.google.com/google-ads/answer/13258081`, the live path is **Goals →
32
+ Conversions → Settings → Enhanced conversions**, and the step now states it with its
33
+ date. The `_gcl_aw` claim was true but incomplete: the cookie's ~90-day default life
34
+ and the Safari ITP cap (JS-set cookies live 7 days, 24 hours after a link-decorated
35
+ navigation — which a gclid landing is) now sit beside the redirect-survival claim,
36
+ dated. Body ~4726 tokens of the 4750 house working limit.
37
+ - **`google-signin` stops pointing installers at a private repository** (DEV-07,
38
+ B-102). The third sentence a reader met cited `web/server.py:1657` in the Prowl repo
39
+ as the live reference implementation — an address no installer can resolve (M-07).
40
+ The shipped `references/full-guide.md` FastAPI + JS skeleton is the reference
41
+ implementation, and the sentence now says so.
42
+ - **`google-auth` states the major its own examples require** (DEV-08, B-101). The ADC
43
+ and JWT examples call `client.fetch()` while `npm install google-auth-library` pinned
44
+ nothing, so a reader on 9.x copied a method their client does not have. Checked
45
+ 2026-08-31 against the library's CHANGELOG: the fetch-compatible API landed in
46
+ **10.1.0** (2025-06-12, PR #1939), `client.request()` is the 9.x form and survives in
47
+ the current 11.x. The install block now carries the floor, the fallback and the date.
48
+
49
+ ## v0.11.0 — a trigger literal gets one home, and four facts get their dates
50
+
51
+ The 2026-08-29 skill audit found the pack's two Google skills advertising the same
52
+ end-user sign-in triggers, one skill re-teaching another's contract more weakly, and
53
+ three external facts stated without the date they were true on. All of it shipped green
54
+ through a 23-check validator, because none of it was a check yet.
55
+
56
+ - **`google-auth` and `google-signin` no longer compete for the same prompt** (audit
57
+ DEV-01, HIGH). Both front-matter descriptions carried `"google login"`, `"GIS"`,
58
+ `"Sign In with Google"` and «вход через Google» — four literal collisions between the
59
+ skill that owns end-user sign-in and the skill that owns the server library surface,
60
+ with which one loads left to the host's guess. The end-user set is stripped from
61
+ `google-auth`, whose description now speaks the server vocabulary (ADC, service
62
+ accounts and their keys, Workload Identity Federation, `verifyIdToken`); the
63
+ cross-deferral sentence stays. A new validator check,
64
+ `check_trigger_literals_have_one_home`, compares the quoted trigger literals of every
65
+ skill pair and refuses a collision — watched failing in both directions (a re-planted
66
+ `"google login"`, and a stale entry in `TRIGGERS_SHARED_BY_DESIGN`, the enumerated
67
+ exemption that carries the one deliberate share: `webhook signature`, which the
68
+ umbrella routes to `stripe-billing`). One new negative self-test in CI; the local
69
+ floor moves 42 → 43. The verdict line moves 23 → 24 checks.
70
+ - **`google-auth` stops re-teaching ID-token verification with a weaker contract**
71
+ (DEV-02). Its section 3 taught the sign-in verification omitting the `nonce` binding
72
+ and `email_verified` — the two checks `google-signin`'s checklist adds above the
73
+ library call, drifting live under a section that duplicated the other skill's core
74
+ job. Cut to the bare `verifyIdToken` / `verify_oauth2_token` call plus an explicit
75
+ statement of what the call does NOT check and where the sign-in contract lives. The
76
+ partial `g_csrf_token` bullet in Security Best Practices is likewise now a pointer.
77
+ - **The Stripe `apiVersion` pins carry their date** (DEV-03). `2026-07-29.dahlia` was
78
+ pinned undated in a document that itself says the API moves monthly — and it was
79
+ already one release behind: checked 2026-08-30 against `docs.stripe.com/changelog`,
80
+ where `2026-08-26.dahlia` is current. Both pin sites now say when they were true and
81
+ send the reader to the changelog before pinning.
82
+ - **The crypto `sign()` block is labeled as ONE provider's scheme** (DEV-04). The
83
+ SKILL taught `md5(base64(json) + apiKey)` under a provider-neutral heading two
84
+ screens after claiming the section holds for four providers — it is Heleket's scheme
85
+ alone. Checked 2026-08-30: Coinbase Commerce signs HMAC-SHA256
86
+ (`X-CC-Webhook-Signature`), NOWPayments HMAC-SHA512 (`x-nowpayments-sig`), BTCPay
87
+ HMAC-SHA256 (`BTCPay-Sig`). The pattern transfers; the algorithm does not, and the
88
+ intro now says which is which.
89
+ - **`allow_enhanced_conversions` moves to the tag it belongs on** (DEV-05). The setup
90
+ step put the flag on the GA4 `G-` config, where it enables nothing for Google Ads;
91
+ where it appears at all it belongs on the `AW-` config command. Checked 2026-08-30:
92
+ Google's current setup docs (`support.google.com/google-ads/answer/9888145`,
93
+ `…/13258081`) no longer document the flag — the live mechanism is the account-level
94
+ toggle plus `gtag('set', 'user_data', …)` — and the step now states both facts.
95
+ - **`ad-tracking`'s UTM Attribution section moved into
96
+ `references/event-tracking.md`.** The DEV-05 fix pushed the body past the 4750-token
97
+ house working limit (23 tokens of headroom going in), and the house answer at the
98
+ limit is a split, not a trim — same seam as v0.10.0's cancellation split.
99
+ Body ~4753 tokens after the move; `stripe-billing`'s recomputed numbers in
100
+ `docs/evals/stripe-billing.md` moved with its dated pins (4737 tokens, 383 lines).
101
+ - **The social card now names all five trades** (umbrella deferral, 2026-08-29). The
102
+ committed `docs/assets/social-preview.png` is compared pixel-for-pixel by the
103
+ umbrella's site test against the card it generates from its `skills.json` role cell,
104
+ and the role grows `errors` for the seventh skill. Regenerated with the umbrella's
105
+ own generator from the exact cell it will adopt — `integrations: money in, tracking,
106
+ errors, sign-in, speed` — verified by reproducing the previous committed card
107
+ byte-for-byte from the previous cell first. README and `marketplace.json` carry the
108
+ same five-trade phrasing. Measured while regenerating: at scale 3 the new eyebrow
109
+ paints to 1 px of the canvas edge, because the umbrella's `fitScale` measures without
110
+ the tracking `drawText` adds — nothing is truncated, and the fix (if wanted) belongs
111
+ in `og-card.js`, not here.
112
+
7
113
  ## v0.10.5 — the installers refuse the shadow they used to write
8
114
 
9
115
  Both install channels — `npx @ssheleg/sheleg-dev` and `install.sh` — now consult the
package/README.md CHANGED
@@ -23,7 +23,7 @@ Agent Skills standard directly, scanning `~/.agents/skills` — where `npx skill
23
23
  add` puts this pack — at rank 500.
24
24
 
25
25
  The integration layer a product reaches once it has users: **money in, tracking,
26
- sign-in, and speed.**
26
+ errors, sign-in, and speed.**
27
27
 
28
28
  Part of the [ssheleg skill family](https://github.com/ssheleg/sshlg-skills).
29
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssheleg/sheleg-dev",
3
- "version": "0.10.5",
3
+ "version": "0.11.1",
4
4
  "description": "Seven integration skills for the layer a product reaches once it has users: Stripe subscription billing and cancellation retention, crypto payments, ad and conversion tracking under Consent Mode v2, Google sign-in and the wider Google auth surface, error tracking, and Core Web Vitals. This package is the installer CLI.",
5
5
  "bin": {
6
6
  "sheleg-dev": "bin/sheleg-dev.js"
@@ -3,7 +3,7 @@
3
3
  "name": "sheleg-dev",
4
4
  "displayName": "Sheleg Dev",
5
5
  "description": "The integration layer a product needs once it has users: Stripe subscription billing reconciled into your own database, crypto payments that survive over-payment and duplicate webhooks, ad and conversion tracking under Consent Mode v2, Google sign-in with the account-linking guards, the wider Google auth surface, and Core Web Vitals work that moves the score, and Sentry error tracking wired so it does not forward your own credentials to a third party and so a stack trace names the commit that caused it.",
6
- "version": "0.10.5",
6
+ "version": "0.11.1",
7
7
  "author": {
8
8
  "name": "ssheleg",
9
9
  "url": "https://x.com/sshlg93"
@@ -157,7 +157,20 @@ Both share consent state, first-party cookies, and the dataLayer pipeline.
157
157
 
158
158
  Enhanced Conversions improve attribution by matching hashed user data (email, phone) with Google accounts. Setup:
159
159
 
160
- 1. Enable in GA4 config: `allow_enhanced_conversions: true`
160
+ 1. Where the flag appears at all, it belongs on the **Google Ads config — the
161
+ `AW-` tag, never the GA4 `G-` tag** (an earlier revision of this skill put it
162
+ on the `G-` config, where it enables nothing for Ads):
163
+
164
+ ```javascript
165
+ gtag('config', 'AW-XXXXXXXXX', { allow_enhanced_conversions: true });
166
+ ```
167
+
168
+ Checked 2026-08-30: Google's current setup docs
169
+ (`support.google.com/google-ads/answer/9888145`, `…/13258081`) no longer
170
+ document the flag at all — the live mechanism is the account-level toggle
171
+ (step 3) plus `user_data` (step 2). Keep the flag if your tag already
172
+ carries it; do not copy it onto the `G-` config.
173
+
161
174
  2. Set user data before conversion events fire:
162
175
 
163
176
  ```javascript
@@ -167,7 +180,10 @@ gtag('set', 'user_data', {
167
180
  });
168
181
  ```
169
182
 
170
- 3. Enable in Google Ads console: Tools → Conversions → Enhanced conversions → Turn on
183
+ 3. Enable in the Google Ads console: **Goals → Conversions → Settings →
184
+ Enhanced conversions → "Turn on enhanced conversions"** (checked 2026-08-31
185
+ against `support.google.com/google-ads/answer/13258081`; the old
186
+ Tools → Conversions path is gone from the redesigned UI)
171
187
 
172
188
  ### user_id (Cross-Device Tracking)
173
189
 
@@ -182,7 +198,11 @@ gtag('config', 'G-XXXXXXXXXX', {
182
198
 
183
199
  ### gclid Preservation Through External Redirects
184
200
 
185
- The `gclid` from Google Ads auto-tagging is stored in the `_gcl_aw` first-party cookie. This cookie persists across external redirects (e.g. Stripe Checkout → return URL) because it's set on your domain. No additional code needed.
201
+ The `gclid` from Google Ads auto-tagging is stored in the `_gcl_aw` first-party
202
+ cookie (~90-day default life). It survives external redirects (Stripe Checkout →
203
+ return URL) because only expiry ends a cookie on your own domain — no extra code.
204
+ Checked 2026-08-31; the caveat is Safari, where ITP caps JS-set cookies at 7
205
+ days — 24 hours after a link-decorated navigation, which a gclid landing is.
186
206
 
187
207
  ### Google Ads console setup
188
208
 
@@ -323,32 +343,10 @@ Strategies** for `next/script` strategy choice and SPA route changes.
323
343
  the parent page's.
324
344
  ## UTM Attribution
325
345
 
326
- ### Capture Flow
327
-
328
- 1. `UtmCapture` component reads UTM params from URL on page load → stores in localStorage
329
- 2. On registration (credentials): read UTM from localStorage, send in POST body, save to DB
330
- 3. On registration (OAuth): UTM persists in localStorage through redirect → synced after return via API call
331
- 4. Mixpanel `people.set_once()` stores initial UTM as permanent user properties
332
- 5. Clear localStorage after successful DB sync
333
-
334
- ### UTM Parameters
335
-
336
- | Parameter | Purpose | Example |
337
- |---|---|---|
338
- | `utm_source` | Traffic source | `google`, `facebook`, `newsletter` |
339
- | `utm_medium` | Marketing medium | `cpc`, `social`, `email` |
340
- | `utm_campaign` | Campaign name | `spring_sale_2025` |
341
- | `utm_content` | Ad creative variant | `banner_a`, `text_link` |
342
- | `utm_term` | Paid search keyword | `ai+chatbot` |
343
-
344
- ### gclid / fbclid / li_fat_id
345
-
346
- Google, Meta, and LinkedIn append their own click IDs to URLs:
347
- - `gclid` — Google Ads click ID (stored in `_gcl_aw` cookie by gtag.js automatically)
348
- - `fbclid` — Meta click ID (stored in `_fbc` cookie by fbevents.js automatically)
349
- - `li_fat_id` — LinkedIn click ID (handled by Insight Tag)
350
-
351
- These are handled automatically by the respective SDKs. Do not strip them from URLs.
346
+ Read `references/event-tracking.md` → **UTM Attribution** for the capture flow
347
+ (localStorage through OAuth redirects into the DB), the five-parameter table,
348
+ and the platform click IDs (`gclid`/`fbclid`/`li_fat_id`) — which the SDKs
349
+ handle automatically; do not strip them from URLs.
352
350
 
353
351
  ---
354
352
 
@@ -13,6 +13,8 @@
13
13
  - [Ecommerce Events](#ecommerce-events)
14
14
  - [SaaS / Subscription Events](#saas--subscription-events)
15
15
  - [trackEvent Wrapper Pattern](#trackevent-wrapper-pattern)
16
+ - [User identification](#user-identification)
17
+ - [UTM Attribution](#utm-attribution)
16
18
 
17
19
  ## Table of Contents
18
20
 
@@ -23,6 +25,8 @@
23
25
  - [Ecommerce Events](#ecommerce-events)
24
26
  - [SaaS / Subscription Events](#saas--subscription-events)
25
27
  - [trackEvent Wrapper Pattern](#trackevent-wrapper-pattern)
28
+ - [User identification](#user-identification)
29
+ - [UTM Attribution](#utm-attribution)
26
30
 
27
31
  ## Event Types
28
32
 
@@ -243,3 +247,38 @@ Moved out of `SKILL.md` on 2026-08-16: the body was 5273 tokens against a
243
247
  < 5000 budget, and identity is the other half of the question this file already
244
248
  owns — an event name says what happened, identity says who it happened to.
245
249
 
250
+
251
+ ---
252
+
253
+ ## UTM Attribution
254
+
255
+ Moved out of `SKILL.md` on 2026-08-30: the body crossed the 4750-token house
256
+ working limit when the Enhanced Conversions fix landed, and attribution
257
+ parameters are schema — the question this file already owns.
258
+
259
+ ### Capture Flow
260
+
261
+ 1. `UtmCapture` component reads UTM params from URL on page load → stores in localStorage
262
+ 2. On registration (credentials): read UTM from localStorage, send in POST body, save to DB
263
+ 3. On registration (OAuth): UTM persists in localStorage through redirect → synced after return via API call
264
+ 4. Mixpanel `people.set_once()` stores initial UTM as permanent user properties
265
+ 5. Clear localStorage after successful DB sync
266
+
267
+ ### UTM Parameters
268
+
269
+ | Parameter | Purpose | Example |
270
+ |---|---|---|
271
+ | `utm_source` | Traffic source | `google`, `facebook`, `newsletter` |
272
+ | `utm_medium` | Marketing medium | `cpc`, `social`, `email` |
273
+ | `utm_campaign` | Campaign name | `spring_sale_2025` |
274
+ | `utm_content` | Ad creative variant | `banner_a`, `text_link` |
275
+ | `utm_term` | Paid search keyword | `ai+chatbot` |
276
+
277
+ ### gclid / fbclid / li_fat_id
278
+
279
+ Google, Meta, and LinkedIn append their own click IDs to URLs:
280
+ - `gclid` — Google Ads click ID (stored in `_gcl_aw` cookie by gtag.js automatically)
281
+ - `fbclid` — Meta click ID (stored in `_fbc` cookie by fbevents.js automatically)
282
+ - `li_fat_id` — LinkedIn click ID (handled by Insight Tag)
283
+
284
+ These are handled automatically by the respective SDKs. Do not strip them from URLs.
@@ -24,7 +24,9 @@ production.
24
24
  This skill is provider-neutral. Concrete request/response shapes for one gateway
25
25
  live in [`references/heleket-provider.md`](references/heleket-provider.md);
26
26
  the invariants below hold for Coinbase Commerce, NOWPayments, BTCPay, Heleket
27
- and anything else that issues an invoice and calls you back.
27
+ and anything else that issues an invoice and calls you back. One thing is NOT
28
+ provider-neutral and is labeled where it appears: the webhook signature
29
+ *algorithm*, which differs per gateway.
28
30
 
29
31
  > **Choosing a provider is a business and compliance decision, not a technical
30
32
  > one.** Crypto payment processors differ sharply in regulatory standing —
@@ -117,9 +119,20 @@ waterfall below.
117
119
 
118
120
  The callback is an unauthenticated public endpoint until you prove otherwise.
119
121
 
122
+ **The `sign()` below is ONE provider's scheme — Heleket's:
123
+ `md5(base64(json) + apiKey)`.** The *pattern* (constant-time compare, sign the
124
+ raw body, verify before parsing) transfers; the algorithm does not. Checked
125
+ 2026-08-30: Coinbase Commerce signs with **HMAC-SHA256** of the raw body
126
+ (`X-CC-Webhook-Signature`), NOWPayments with **HMAC-SHA512** of the
127
+ sorted-key JSON (`x-nowpayments-sig`), BTCPay with **HMAC-SHA256**
128
+ (`BTCPay-Sig`). Take the algorithm from your provider's own reference — for
129
+ Heleket's full shapes see
130
+ [`references/heleket-provider.md`](references/heleket-provider.md).
131
+
120
132
  ```ts
121
133
  import { createHash, timingSafeEqual } from 'node:crypto';
122
134
 
135
+ // Heleket's scheme. Swap the digest for your provider — see the note above.
123
136
  function sign(payload: unknown, apiKey: string): string {
124
137
  const json = JSON.stringify(payload);
125
138
  return createHash('md5')
@@ -1,16 +1,16 @@
1
1
  ---
2
2
  name: google-auth
3
3
  description: >-
4
- Use when implementing Google authentication in a Node.js or Python web application — Google
5
- login, OAuth 2.0 flows, verifying Google ID tokens, service account authentication,
6
- Application Default Credentials, Google Identity Services, Workload Identity Federation, API
7
- keys, or working with google-auth-library (Node.js) or google-auth (Python). Covers ID token
8
- verification and security best practices. Triggers - "google auth", "google login", "google
9
- sign in", "OAuth 2.0 Google", "google-auth-library", "Sign In with Google", "Google Identity
10
- Services", "GIS", "ADC", "Application Default Credentials", "service account", "Google ID
11
- token", "verifyIdToken", "GOOGLE_APPLICATION_CREDENTIALS", "Google SSO", "вход через Google",
12
- "авторизация Google", "сервисный аккаунт", "проверить ID-токен", "гугл-логин". For end-user
13
- web sign-in only, use the google-signin skill instead.
4
+ Use when a server authenticates to Google in a Node.js or Python application — OAuth 2.0
5
+ flows, verifying Google ID tokens server-side, service account authentication and keys,
6
+ Application Default Credentials, Workload Identity Federation, API keys, or working with
7
+ google-auth-library (Node.js) or google-auth (Python). Covers server-side ID token
8
+ verification and security best practices. Triggers - "google auth", "OAuth 2.0 Google",
9
+ "google-auth-library", "ADC", "Application Default Credentials", "service account",
10
+ "Workload Identity Federation", "Google ID token", "verifyIdToken",
11
+ "GOOGLE_APPLICATION_CREDENTIALS", "Google SSO", "авторизация Google", "сервисный аккаунт",
12
+ "ключи сервисного аккаунта", "проверить ID-токен". For end-user web sign-in only, use the
13
+ google-signin skill instead.
14
14
  ---
15
15
 
16
16
  # Google Authentication for Node.js & Python
@@ -27,6 +27,11 @@ npm install google-auth-library
27
27
  npm install googleapis
28
28
  ```
29
29
 
30
+ The `client.fetch()` calls below require google-auth-library **≥ 10.1.0** (the
31
+ fetch-compatible API landed in 10.1.0, 2025-06-12); on 9.x use
32
+ `client.request()` with the same arguments. Checked 2026-08-31: latest is 11.x
33
+ and keeps both methods.
34
+
30
35
  ### Python
31
36
 
32
37
  - `google-auth` — core auth library (google.oauth2, google.auth, credentials, transport)
@@ -45,7 +50,7 @@ pip install google-api-python-client
45
50
  |--------|----------|-------------------|---------------------------|
46
51
  | **ADC** | Same identity for all users, server-to-server | `GoogleAuth` | `google.auth.default()` |
47
52
  | **OAuth 2.0** | Actions on behalf of end users | `OAuth2Client` | `google_auth_oauthlib.flow.Flow` |
48
- | **Sign In with Google (GIS)** | User sign-in/sign-up on websites | GIS JS SDK + `verifyIdToken()` | GIS JS SDK + `id_token.verify_oauth2_token()` |
53
+ | **Sign In with Google (GIS)** | User sign-in/sign-up on websites — the google-signin skill's ground | GIS JS SDK + `verifyIdToken()` | GIS JS SDK + `id_token.verify_oauth2_token()` |
49
54
  | **JWT / Service Account** | Server-to-server, single identity | `JWT` | `service_account.Credentials` |
50
55
  | **API Key** | Public data, no user context | `OAuth2Client({ apiKey })` | passed to `googleapiclient.discovery.build(developerKey=)` |
51
56
  | **Compute** | On GCP with attached service account | `Compute` | `google.auth.compute_engine.Credentials` |
@@ -134,7 +139,7 @@ credentials = flow.credentials
134
139
 
135
140
  For the complete OAuth 2.0 flow (parameters, token exchange, refresh, revocation, incremental auth), see [references/oauth2-web-server.md](references/oauth2-web-server.md).
136
141
 
137
- ### 3. Sign In with Google — ID Token Verification
142
+ ### 3. Verifying a Google ID Token — the library call
138
143
 
139
144
  **Node.js**
140
145
 
@@ -147,10 +152,6 @@ const ticket = await client.verifyIdToken({
147
152
  audience: WEB_CLIENT_ID,
148
153
  });
149
154
  const payload = ticket.getPayload();
150
- const userId = payload['sub'];
151
- const email = payload['email'];
152
- const name = payload['name'];
153
- const picture = payload['picture'];
154
155
  ```
155
156
 
156
157
  **Python**
@@ -159,22 +160,14 @@ const picture = payload['picture'];
159
160
  from google.oauth2 import id_token
160
161
  from google.auth.transport import requests
161
162
 
162
- request = requests.Request()
163
-
164
- payload = id_token.verify_oauth2_token(
165
- token,
166
- request,
167
- WEB_CLIENT_ID
168
- )
169
- user_id = payload['sub']
170
- email = payload['email']
171
- name = payload.get('name', '')
172
- picture = payload.get('picture')
163
+ payload = id_token.verify_oauth2_token(token, requests.Request(), WEB_CLIENT_ID)
173
164
  ```
174
165
 
175
- Verification checks: JWT signature, `aud`, `exp`, `iss` (accounts.google.com).
176
-
177
- For GIS integration, CSRF protection, and hosted domain validation, see [references/sign-in-with-google.md](references/sign-in-with-google.md).
166
+ The call checks signature, `aud`, `exp` and `iss` — **and nothing else**. That is
167
+ the library contract, not the web sign-in contract: for the sign-in security
168
+ checklist (`nonce` binding, `email_verified`, login-CSRF defense, account
169
+ linking) use the **google-signin** skill — it is the one home for that contract,
170
+ and this skill deliberately does not restate it.
178
171
 
179
172
  ### 4. JWT / Service Account
180
173
 
@@ -264,7 +257,7 @@ if credentials.expired and credentials.refresh_token:
264
257
  - Store `refresh_token` securely; it's only returned on first authorization
265
258
  - Validate external credential configurations before use (check `token_url`, `service_account_impersonation_url` point to googleapis.com)
266
259
  - Prefer Workload Identity Federation over service account keys for non-GCP environments
267
- - For GIS: verify `g_csrf_token` with double-submit-cookie pattern
260
+ - For end-user web sign-in, apply the google-signin skill's full checklist — a partial restatement here is how the two skills drifted apart once already
268
261
  - **Python-specific**: reuse a single `google.auth.transport.requests.Request()` instance across verifications for connection pooling; do not create a new one per call in hot paths
269
262
 
270
263
  ## Reference Files
@@ -17,9 +17,8 @@ description: >-
17
17
  Condensed operating instructions. Full narrative guide with diagrams, token
18
18
  anatomy, troubleshooting table, and a copy-paste FastAPI + JS skeleton:
19
19
  [references/full-guide.md](references/full-guide.md) — read it when
20
- implementing from scratch. A live reference implementation exists in the
21
- Prowl repo (`0xDEV`): `web/auth.py`, `web/server.py:1657`
22
- (`/api/auth/google`), `web/static/js/app.js:16-110`.
20
+ implementing from scratch; its FastAPI + JS skeleton is the reference
21
+ implementation this skill ships.
23
22
 
24
23
  ## Pick the right flow first
25
24
 
@@ -90,6 +90,8 @@ export function getStripe(): Stripe {
90
90
  if (!client) {
91
91
  const key = process.env.STRIPE_SECRET_KEY;
92
92
  if (!key) throw new Error("STRIPE_SECRET_KEY is not set");
93
+ // Pin checked 2026-08-30 — 2026-08-26.dahlia was already newer. Confirm
94
+ // against Stripe's changelog before pinning; this line goes stale monthly.
93
95
  client = new Stripe(key, { apiVersion: "2026-07-29.dahlia", maxNetworkRetries: 2 });
94
96
  }
95
97
  return client;
@@ -171,7 +173,7 @@ await stripe.checkout.sessions.create({
171
173
 
172
174
  `{CHECKOUT_SESSION_ID}` is substituted by Stripe, not rendered by you. On
173
175
  `2026-03-25.dahlia`+ an `integration_identifier` label lets you compare flows in
174
- the Dashboard.
176
+ the Dashboard (floor checked 2026-08-30 against Stripe's changelog).
175
177
 
176
178
  Three decisions belong to `stripe-best-practices`: **usage-based billing**
177
179
  (Metronome for anything new; Billing Meters is a low-level primitive), **tax**