Claude/checkin - #47
Claude/checkin #47
Conversation
Implements the first two Checkin capabilities, spec-driven via OpenSpec. add-auth-foundation: - isct email magic-link sessions: HMAC-SHA256 mail_hash (no plaintext email stored), server-side sessions in __Host- cookies, double-submit CSRF, Mailer adapter (log/SendGrid). - traQ OAuth (PKCE) admin login gated by an env traQ-ID allow-list. - requireUser / requireAdmin authorization helpers on the oRPC Context. add-membership-collection: - Stripe adapter layer (lazy client; Customer get-or-create; Invoice create -> finalize -> send) isolated from the Stripe-independent billing domain (term/price/authorize). - membership.issueInvoice (self, mail_hash-matched) and issueSpecialInvoice (admin-only special price), with Stripe idempotency keys and draft cleanup on failure. - invoice.paid webhook: raw-body signature verify + event-id idempotency + accountant Notifier. DB (Drizzle/MariaDB): users / sessions / email_verifications / stripe_events, with users.stripe_customer_id. Migrations 0000, 0001. Tooling: @fission-ai/openspec, zod, vitest, stripe. Both changes were Codex-reviewed, fixes applied, delta specs synced to openspec/specs/ and archived. Gates green: lint, typecheck, build, test (26). Live Stripe E2E pending test-mode keys. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
add-payment-listing (OpenSpec): accountant-only, read-only payments
listing backed by Stripe (no DB writes).
- Stripe adapter: listInvoices / listCheckoutSessions (lazy client,
cursor pagination, customer + line-item price expansion).
- Stripe-independent payments domain: PaymentRow/PaymentPage DTOs,
invoice/checkout-session normalizers (null-safe), buildDashboardUrl
(test/live), limit clamp + nextCursor.
- oRPC payments.listInvoices / payments.listCheckoutSessions.
Authorization hardening (Codex review): introduce userProc / adminProc
oRPC middleware so the auth check runs BEFORE input validation (no input
shape leak to unauthenticated callers); migrate membership.* procedures
to them. Checkout-session rows now carry an explicit product/Price
reference (or null) instead of {}.
Codex-reviewed, fixes applied, delta spec synced to openspec/specs/ and
archived. Gates green: lint, typecheck, build, test (48). Live Stripe
E2E pending test-mode keys.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
add-connect-onboarding (OpenSpec): connected-account JIT onboarding, the prerequisite for payouts. Jomon-independent; actual payouts land in a later change. - Stripe Connect adapter: race-safe getOrCreateConnectedAccount (Express; fresh re-read + conditional compare-and-set claim; orphan account cleanup on lost race), createAccountOnboardingLink (hosted Account Link), Connect-secret signature verify. - Stripe-independent onboarding logic: isPayoutsReady (payouts_enabled + no currently_due), nextOnboardingStatus (none/requested/done, terminal & idempotent). - account.updated webhook: Connect-secret raw-body verify + event-id idempotency + flag-based requested->done (never "event = done"). - oRPC payouts.createOnboardingLink / onboardingStatus (admin-only). - DB: users.stripe_connected_account_id (UNIQUE) + payout_onboarding_status. Migrations 0002, 0003. Codex-reviewed (race + uniqueness findings fixed), delta specs synced to openspec/specs/ and archived. Gates green: lint, typecheck, build, test (62). Live Connect E2E pending Connect-enabled test keys. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
add-payout-execution (OpenSpec): pull approved transfer requests from Jomon, pay them out via Stripe Connect transfers, write results back. Responsibility split (design §5.3): approval is Jomon's, Checkin only executes. Pull-based; Jomon integrated stub-first with v1/v2 adapters. - Jomon adapter: JomonClient interface, StubJomonClient (dev/tests), v1/v2 HTTP clients (Bearer service token) with STRICT zod response validation that fails loudly rather than mis-mapping a payee. - payouts table + state machine (pending / onboarding_waiting / processing / paid / failed), jomon_ref UNIQUE, jomon_written_back_at. - Execution: ingest (idempotent upsert) -> resolve payee by mail_hash (userId immutable once set) -> onboarding gate -> atomic claim-to-processing -> Stripe transfer (idempotency key payout:<ref>) -> paid/failed -> Jomon write-back (retryable without re-transfer). Per-item error isolation; failed rows retried only by admin single execute, never auto-retried by the batch. - Stripe transfers adapter (createTransfer). oRPC payouts.processApproved / list / execute (admin-only). Migrations 0004, 0005. Codex-reviewed (1 critical + 2 high + 3 medium money-movement findings fixed: atomic execution claim, retryable write-back, strict Jomon validation, batch isolation, userId immutability, explicit failed-retry policy). Delta spec synced to openspec/specs/ and archived. Gates green: lint, typecheck, build, test (94). Live Jomon/Stripe E2E pending the Jomon Bearer-token endpoint and Connect-enabled test keys. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
add-traq-member-auth (OpenSpec): rework auth so a session can carry BOTH
a traQ identity and an isct user identity, so Jomon payouts (keyed by
traQ ID) can resolve to our users.
- Dual-identity session: SessionIdentity { traqId, isAdmin, userId,
mailHash }. traQ login now establishes a MEMBER session for any traQ
user; accountant (isAdmin) is the env allow-list subset (was: reject
non-allowlisted). requireMember/requireUser/requireAdmin +
memberProc/userProc/adminProc (auth before input validation).
- users.traq_id (UNIQUE) + race-safe linkTraqId (compare-and-set,
dup-key via .cause → 'conflict', never overwrites/relinks).
- Linkage: traQ login resolves a linked user; isct confirm on an
existing traQ session links the two (link-first, and on conflict mint
a plain user session instead of a corrupted dual one); issueInvoice
links the caller's own traQ id and stamps it into Customer metadata
(never a conflicting/foreign traq id).
- auth.me → { authenticated, member, admin, hasUser, traqId }.
Migration 0006 (sessions.is_admin, users.traq_id; drop actor_type).
Codex-reviewed (link-before-attach ordering + metadata-poisoning on
conflict fixed). Delta specs synced (admin-authorization/session
MODIFIED, identity/membership-billing ADDED) and archived. Gates green:
lint, typecheck, build, test (104). Uncommitted member UI adjusted
minimally to compile; it is reworked in add-member-ui.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix-jomon-payout (OpenSpec): correct the Jomon adapter to the real
traPtitech/Jomon API and resolve payees via users.traq_id.
- List approved via GET /api/applications (v1 ?current_state=accepted,
v2 ?status=approved); drop the fictional /transfer-requests.
- Payee = traQ ID: v1 repaid_to_user.trap_id; v2 ApplicationTarget.target
(User UUID) resolved through GET /api/users → User.name. Strict zod
parsing (fail-loud, no field defaulting). currency hardcoded jpy.
- jomon_ref: v2 = target.id; v1 = `${appId}:${trapId}`. Skip already
repaid_at / paid_at items.
- Payee resolution now getUserByTraqId (was deriveMailHash(email));
unresolved traQ ID → pending/要対応, no transfer.
- Write-back: v1 PUT .../states/repaid/{trapId}; v2 unsupported
(JomonWriteBackUnsupportedError) → kept paid, jomon_written_back_at
NULL, retried, never re-transfers (Jomon v2 needs a per-payee endpoint).
- Batch resilience (Codex): list-fetch error returns summary.listError
instead of aborting; v1 per-application detail failure is logged+skipped.
Notes: real Jomon has only traQ OAuth/cookie auth (no Bearer receiver)
and no v2 write-back — live v1/v2 needs Jomon-side additions; default
driver stays stub. Codex-reviewed (0 critical/high). Delta synced
(payout-execution MODIFIED) and archived. Gates green: lint, typecheck,
build, test (111).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
add-member-ui (OpenSpec): Nuxt 4 + @nuxt/ui member UI over the dual-identity auth. - Layout/header with auth.me state (member/admin/traqId) + CSRF-aware logout; @nuxt/ui + Tailwind setup, app.config theme, UApp wrapper. - / : state-based entry (anonymous → pay / login; logged-in → status). - /verify-email : isct email → auth.requestEmailVerification, redirect preserved (sanitized), domain/error feedback, no double-submit. - /membership : design §5.1 branching off auth.me — anonymous → 新規入部/再入部 (verify-email) or 現役 (traQ /login); member && !hasUser → verify-email to link; hasUser → invoice form → membership.issueInvoice → hostedInvoiceUrl. 区分→feeType mapping; special ¥2,000 hidden (accountant-only); errors mapped, submit guarded. Codex-reviewed (only a stale design note fixed). Smoke-tested: pages render, magic link emitted (LogMailer), confirm → hasUser, invoice error path handled. Delta synced (member-ui ADDED) and archived. Gates green: lint, typecheck, build, test (111). Real Stripe issuance E2E pending test-mode keys. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Current state, OpenSpec+subagent+Codex workflow, what's built (8 changes / 11 specs), key invariants (money/identity safety), prioritized remaining work (live Jomon coordination, Stripe E2E, accountant UI, hardening), env, local run/verify, and gotchas. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the accountant-facing pages on top of the existing adminProc oRPC: - /payments: 請求書/決済セッション tabs, status filter, append-style pagination, Stripe Dashboard links. Stale-response guard via request sequence tokens so a filter change can't be overwritten by an in-flight fetch (Codex finding). - /payouts: Jomon ingest (processApproved), status-filtered list, per-row execute, onboarding link issue + status. createOnboardingLink refetches. - useFormatters composable (currency/date), accountant nav in header/home shown only when auth.me.admin. 通貨 column added (Codex finding). Server adminProc is the real authorization; the UI gate is for UX. The USelect "すべて" option uses an 'all' sentinel (empty string crashes @nuxt/ui hydration). Verified non-admin gate + flows with Playwright. OpenSpec: new capability accountant-ui; change archived 2026-06-21-add-accountant-ui. Gates green (lint/typecheck/build/test). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ChAsb7VghL4Mw3oJLrzku9
GET /dev/login mints a session WITHOUT traQ OAuth / email verification so the UI flows can be exercised locally (?as=admin|member|user|both, ?email= override for a fresh customer, ?redirect=). Hard-gated as an auth bypass that must NEVER reach production: requires import.meta.dev (baked to false in the production build → unconditional 404, verified against the built server) AND CHECKIN_DEV_LOGIN=1. Documents the flag in .env.example with a danger note. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ChAsb7VghL4Mw3oJLrzku9
Members could pay twice for the same membership coverage — the only guard was Stripe's ~24h idempotency window. Adds a persistent half-period ledger keyed UNIQUE(user_id, activity_year, half) as the duplicate-payment guard. Model (user-confirmed rules): ¥4,000 = 通期 (both half-slots), ¥2,000 = one half. Standard self-serve (entry/recovery 前期→full, 後期→kouki; 継続→full, covers NEXT year). Special accountant ¥2,000 picks zenki|kouki. issueSpecialInvoice gains coverage + activityYear inputs. Issuance is gated by the ledger: paid→reject, open exact-match→reuse the existing invoice URL, partial overlap→reject, free→issue. invoice.paid flips the charge's slots to paid (idempotent, 台帳外 = 200 no-op). Money-safety (two Codex review rounds): draft-first ordering — create a NON-payable draft, reserve the slot WITH its id, THEN finalize. A payable invoice therefore never exists without a slot guarding it. Draft creation is non-idempotent (concurrent issuers get distinct ids so the reservation loser voids its OWN draft). finalizeAndSend throws ONLY while still a draft (email send is best-effort, concurrent-finalize tolerant), so the void+release path can never strip a payable invoice's guard. Verified E2E with real Stripe + stripe listen: issue → reuse-while-unpaid → pay → re-issue rejected; 通期 paid → 後期 special rejected. 9 ledger tests; gates green (lint/typecheck/build/test 120). OpenSpec: new capability issuance-ledger, membership-billing/payment-webhook updated, change archived 2026-06-23-add-issuance-ledger. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ChAsb7VghL4Mw3oJLrzku9
Offer customer_balance / jp_bank_transfer alongside card on every membership invoice (createDraftInvoice payment_settings), so members can pay dues by bank transfer. Settlement is async (invoice stays open until funds arrive) and rejoins the existing invoice.paid path — issuance-ledger paid-confirmation, accountant notification, and the duplicate-payment guard are unchanged; it only adds a payment method. OpenSpec: add-bank-transfer-payment (membership-billing delta synced to specs + archived). Codex money-safety review: no blocking issues. Verified end-to-end in Stripe test mode: issue -> hosted page offers card + 銀行振込 -> simulated transfer -> invoice.paid -> both ledger slots paid + accountant notified; paid re-issue rejected (409). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The v1 driver was guessed and rejected every live application: it required a per-payee `amount` on each repayment log, but real Jomon v1 has none — the amount lives on the application (`current_detail.amount`). Verified against a local Jomon v1 (master branch). Now: - v1 reads the amount from `current_detail.amount`; the schema no longer requires a per-log amount. - Decide by TOTAL payees on an application (not just unpaid count): exactly one payee → pay the application amount; MORE THAN ONE payee → never auto-pay (the application total can't be split safely, even when only one remains unpaid — would overpay). Multi-payee apps are flagged needs-review via `summary.multiPayeeRefs`; `/payouts` shows a warning alert. Operations guarantee one payee per application; this guards the exception. - `executePayout` also refuses multi-payee markers. Codex money-safety review caught the partial-repaid overpay (HIGH) on the first pass; fixed by the total-payee gate and re-reviewed clean. OpenSpec: fix-jomon-v1-amount-multipayee (payout-execution delta synced + archived). Verified end-to-end on local Jomon v1: single payee -> ingest -> resolve -> onboarding gate -> real Stripe transfer (tr_…, ¥1,500) -> paid -> `repaid` write-back to Jomon (live 200); re-run is idempotent (already_paid). Multi-payee and partial-repaid -> multiPayeeRefs + UI alert, no transfer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `membership.issueSpecialInvoiceByEmail` (adminProc): selects the target by email and get-or-creates the person row by mail_hash, so a first-time member can be issued the 継続特別 ¥2,000 invoice without a prior login / userId. The Customer is created from the same email, so the spec's "管理者が指定したメール=対象者" mail_hash match holds by construction; the email domain must be allowed (avoids junk rows). Extract the special-issuance core into `issueSpecialForRow` so the existing userId-based `issueSpecialInvoice` stays intact (DRY). Add the accountant page `/special-invoice` (email / name / coverage / activityYear) surfacing the hosted invoice URL to forward to the member, plus an admin nav link. Coverage keeps 前期のみ/後期のみ; the 後期のみ option is labelled for its two-stage case (前期特別→後期追加). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACKUEYT79nkgEKUjBWjnZM
`<UInput type="number">` coerces v-model to a JS number, so onSubmit's `form.activityYear.trim()` threw TypeError, was swallowed by the catch, and the RPC never fired — the accountant saw "発行に失敗しました" and could not issue a next-year (継続特別 collected in 後期) special invoice from the UI. Normalize to a string before trimming. The server API was already correct. Verified via Playwright: activityYear=2027 now issues (metadata.activity_year= 2027); empty still defaults to the current year. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACKUEYT79nkgEKUjBWjnZM
…ession tests The dedup→markPaid→notify→record logic lived only in the Nitro route handler and had no unit coverage (event-id idempotency, notify-before-record ordering). Extract it into a dependency-injected pure function `processInvoicePaid(ops, verified)` (packages/api/src/webhook/process.ts); the route now just binds the real db/notifier ops. Behavior is byte-for-byte equivalent (order, return shapes, notify text, objectId null guard). Adds process.test.ts (DI spies, DB-free): paid → markPaid→notify→record in order; duplicate event → no side effects; notify failure → not recorded (Stripe retries); ledger-less invoice (objectId null) → skip markPaid, still 200. 126 → 131 tests passing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACKUEYT79nkgEKUjBWjnZM
Playwright (playwright-core) harness driving the dev server at :13000 for UI-level verification of 入部/継続/特別/払い戻し + cross-cutting flows. Mints dev sessions and injects the __Host-/Secure session + CSRF cookies (Chromium drops them from Set-Cookie over http://localhost) and pays test invoices with a test card to exercise the invoice.paid → ledger-paid loop. - E2E-SCENARIOS.md: 94-scenario verification ledger (Codex-reviewed for gaps) - scripts/e2e/: harness.mjs, pay.mjs, run_*.mjs per feature, RESULTS.md - eslint: ignore scripts/e2e/** (machine-specific helpers, absolute paths) - .gitignore: scripts/e2e/shots/ (binary screenshots) - add playwright-core devDependency Result: 84/94 green (54 UI/HTTP + 30 unit), 0 FAIL, 10 blocked by environment only (system clock / env restart / DB pre-state / bank-transfer settlement). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACKUEYT79nkgEKUjBWjnZM
To deploy behind NeoShowcase "Soft" member-auth without wiring our own traQ OAuth, trust the reverse proxy's `X-Forwarded-User` as the traQ identity when `CHECKIN_TRUST_FORWARD_AUTH=1`. - session: `applyForwardedIdentity` (pure) merges the forwarded traQ identity (and accountant flag via the allow-list) onto the cookie session; the isct user identity (userId/mailHash) still rides on the email-verification cookie. - buildRequestContext: when enabled, read X-Forwarded-User (fallback X-Showcase-User) and apply it over the resolved cookie session. - /login: when enabled, redirect to the platform's `/_oauth/login?redirect=...` instead of our traQ OAuth, so unauthenticated users get prompted to log in. - config/runtimeConfig: `trustForwardAuth` (env CHECKIN_TRUST_FORWARD_AUTH). Only trust the header where the app is reachable solely via the trusted proxy. Default (flag off) keeps the existing traQ OAuth behavior. 4 unit tests added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACKUEYT79nkgEKUjBWjnZM
…auth) Step-by-step for deploying both as 1 Runtime app each, using NeoShowcase Soft member-auth (X-Forwarded-User) instead of self-auth, built-in MariaDB, no Swift (Jomon falls back to local storage), no Dockerfile for Checkin (Command/ Buildpack), and Checkin→Jomon Bearer for service calls. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACKUEYT79nkgEKUjBWjnZM
- Checkin env: switch to NUXT_-prefixed names (runtimeConfig is baked at build; NUXT_* overrides at runtime) and export NUXT_DATABASE_URL in the entrypoint. - Jomon: concrete `administrators` seed SQL (fresh prod DB has no admin → 403). - Troubleshooting: the two real startup panics (MARIADB_* unset → DB connection refused; local-storage dir missing → fixed in Jomon b41808e, rebuild needed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACKUEYT79nkgEKUjBWjnZM
…h/login) Under Soft member-auth the Jomon Vue client drove its own traQ OAuth (genpkce) and failed with a blank ClientID. Documented the fix (client now redirects to the platform forward-auth login; member-auth stays Soft so Bearer still works). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACKUEYT79nkgEKUjBWjnZM
Under Soft forward-auth the traQ identity comes from the proxy's X-Forwarded-User, not our session — so POST /logout destroying the session left the user logged in (the header re-asserted the identity on the next request). When trustForwardAuth is on, /logout now returns a redirect to the platform logout (/_oauth/logout) and the client follows it with a full-page navigation (the proxy auth cookie is HttpOnly and only dropped that way). Non-forward-auth deployments are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACKUEYT79nkgEKUjBWjnZM
Jomon now seeds administrators from INITIAL_ADMIN_TRAP_IDS (+ SERVICE_USER_TRAP_ID) at startup, so the manual `INSERT INTO administrators` is no longer required. Documented the env and kept the SQL as an optional fallback. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACKUEYT79nkgEKUjBWjnZM
Jomon refunds are keyed by traQ ID; previously a payee who had never done isct email verification was "unresolved" and never paid, because a person row could only be born from a mail_hash. Make a person identifiable by EITHER mail_hash OR traq_id: - schema: `users.mail_hash` is now nullable (unique; migration 0008). A payout-only recipient has a traq_id and no mail_hash. - identity: `getOrCreateUserByTraqId` mints a payout-only row; `setUserMailHash` race-safely fills a null mail_hash; `resolveUserForEmailVerify` unifies email verification — create / link a member / MERGE a payout-only row gaining its email / refuse to auto-merge two separate rows (conflict) — keeping one row per person. - payout: `resolvePayee` auto-creates the traQ-only row instead of leaving it unresolved → the recipient just needs Connect onboarding (no membership). - connect: the Express account is labelled with whichever non-PII key exists (traq_id when there is no mail_hash). - billing stays mail_hash-gated: `requireUser`/session need mail_hash, and the special-invoice path rejects a payout-only target — traQ-only rows can't be billed. DB-backed tests cover the merge matrix and the payout auto-create. All gates green (typecheck/lint/build, 142 tests). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACKUEYT79nkgEKUjBWjnZM
オンボーディング未完了の受取人に会計が手で銀行振込したケースを 「手動振込済み」として記録できるようにする。 - DB: payouts に payout_method/manual_paid_at/manual_paid_note/manual_paid_by を追加 - 確定: Stripe transfer を発行せず、claim と同じ claimable 述語の単一原子的 UPDATE で paid 確定(paid/processing は拒否、Stripe 実行と競合しても二重支払い不可) - Jomon: 既存 write-back を再利用し manual note で settled を書き戻し(transfer_id 非依存) - API: payouts.markManuallyPaid(adminProc + CSRF、実行者はセッションから解決) - UI: /payouts 各行「手動振込済みにする」(参照メモ+確認モーダル、手動振込バッジ+実行者/日時) OpenSpec change add-manual-bank-payout を実装・sync・archive 済み。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Important Review skippedToo many files! This PR contains 227 files, which is 127 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (227)
You can disable this status message by setting the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| */ | ||
| export function safeEqual(a: string, b: string): boolean { | ||
| const ah = createHash('sha256').update(a).digest() | ||
| const bh = createHash('sha256').update(b).digest() |
No description provided.