Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
190 commits
Select commit Hold shift + click to select a range
05b8486
feat(auth): add wallet_address table and display_name_confirmed column
joelorzet Jun 17, 2026
3df4e2c
feat(auth): add SIWE wallet login server flow
joelorzet Jun 17, 2026
3c5d828
feat(auth): add Connect modal with wallet and email/social login
joelorzet Jun 17, 2026
8607619
feat(auth): add wallet display-name modal
joelorzet Jun 17, 2026
07bfe4f
feat(onboarding): add getting-started overlay and Connect entry
joelorzet Jun 17, 2026
f867618
feat: KEEP-786 add intro.js onboarding tour for the sign-in button
suisuss Jun 18, 2026
2247997
feat: KEEP-786 interactive editor walkthrough (driver.js) + create-wa…
suisuss Jun 18, 2026
68bee43
feat(auth): add step-up policy and email columns
joelorzet Jun 19, 2026
763b1fb
feat(auth): gate sensitive actions through wallet step-up
joelorzet Jun 19, 2026
c61fe04
feat(auth): wallet step-up client and Security settings UI
joelorzet Jun 19, 2026
d7b3cca
feat(onboarding): back navigation, completion checks, wallet name tru…
joelorzet Jun 19, 2026
beec8ed
feat(auth): wallet step-up for withdraw and session revoke
joelorzet Jun 22, 2026
a3f6846
refactor(onboarding): drop redundant wallet-setup step
joelorzet Jun 23, 2026
fd77c19
feat(org): owner-configurable MFA enforcement for members
joelorzet Jun 23, 2026
2c75f71
feat(org): invite wallet users by address with sign-to-join
joelorzet Jun 23, 2026
0ad2cac
fix(auth): keep wallet sessions MFA-satisfied when TOTP is enrolled
joelorzet Jun 24, 2026
cc22743
feat(onboarding): goal-oriented getting-started launcher
joelorzet Jun 24, 2026
c873adb
feat(onboarding): restore Take-a-tour entry point + e2e testability h…
joelorzet Jun 25, 2026
9231808
test(e2e): wallet + onboarding coverage; fix Connect-modal auth helpers
joelorzet Jun 25, 2026
c61465a
feat(onboarding): goal-oriented getting-started launcher
joelorzet Jun 25, 2026
cda2641
feat(auth): random wallet display-name modal
joelorzet Jun 25, 2026
5d6440d
fix(api-keys): support wallet users for create, revoke and notifications
joelorzet Jun 25, 2026
4bb968b
refactor(auth): add single account-kind classifier
joelorzet Jun 25, 2026
79e6a24
refactor(auth): add unified authorizeAction guard
joelorzet Jun 25, 2026
b91687d
refactor(auth): migrate all gated actions to authorizeAction
joelorzet Jun 25, 2026
26421b0
feat(auth): default-on TOTP for wallet withdraw and key export
joelorzet Jun 25, 2026
f0a61c0
feat(auth): full per-action step-up policy UI for wallet users
joelorzet Jun 26, 2026
20c4a75
feat(auth): admin read-only view of org MFA enforcement
joelorzet Jun 26, 2026
92f384d
test(auth): unit-cover org MFA enforcement gating
joelorzet Jun 26, 2026
2c1c88b
fix(auth): count TOTP as enrolled only after the code is confirmed
joelorzet Jun 26, 2026
b6bc024
feat(onboarding): show a loading toast while the editor tour starts
joelorzet Jun 26, 2026
8dbce16
fix(onboarding): recover the editor tour when a wrong action is picked
joelorzet Jun 26, 2026
ed0a3e1
fix(wallet): show the wallet button + info for wallet users
joelorzet Jun 26, 2026
84521d3
feat(wallet): copyable sign-in address in the account menu
joelorzet Jun 26, 2026
9adbf92
feat(onboarding): "Take a guided tour" CTA on run-your-first-workflow
joelorzet Jun 26, 2026
5b0fe10
feat(org): copy invite link for pending invitations
joelorzet Jun 26, 2026
69754c1
fix(onboarding): guide the tour to the Web3 action and auto-expand it
joelorzet Jun 26, 2026
b957986
fix(onboarding): sequence the tour's action steps properly
joelorzet Jun 26, 2026
12c7674
fix(onboarding): guide the tour through Network and Address instead o…
joelorzet Jun 26, 2026
ed89a3b
fix(onboarding): re-anchor the tour spotlight for late-rendering targets
joelorzet Jun 26, 2026
47f4712
fix(onboarding): anchor the Network/Address tour cards to the real fi…
joelorzet Jun 26, 2026
90366bd
fix(settings): only show a per-action factor as on when it is enrolled
joelorzet Jun 26, 2026
e06725f
fix(mfa): unblock the enforce-mfa page for gated wallet members
joelorzet Jun 26, 2026
40ed2fb
fix(mfa): require every selected factor, not just one
joelorzet Jun 26, 2026
7bab967
feat(mfa): add an escape hatch to the enforce-mfa gate
joelorzet Jun 26, 2026
90ddd60
fix(mfa): show backup codes before signaling enrollment complete
joelorzet Jun 26, 2026
037e476
fix(mfa): flip two_factor_enabled on the session enroll path
joelorzet Jun 26, 2026
2dd80c3
fix(wallet): sign step-up with the login account, not window.ethereum
joelorzet Jun 26, 2026
d1f3cff
chore: remove redundant console.log from org api-key create route
suisuss Jun 26, 2026
e02fee9
fix: reject bare '0x' as wallet display name
suisuss Jun 26, 2026
b13b793
fix: add signature field to audit export request body type
suisuss Jun 26, 2026
10bb3c3
fix(auth): enforce server domain in SIWE signature verification
suisuss Jun 26, 2026
689f17e
fix(auth): block email signup with wallet email domain
suisuss Jun 26, 2026
b68b162
fix(auth): migrate session revoke to authorizeAction pipeline
suisuss Jun 26, 2026
0ef8a4f
fix(auth): defer wallet nonce consumption until all step-up factors pass
suisuss Jun 26, 2026
c30481e
fix(settings): flush onEnrolled before closeAndReset to prevent stale…
suisuss Jun 29, 2026
20e83fa
fix(auth): bind nonce from parsed SIWE message for non-WalletConnect …
suisuss Jun 29, 2026
febc010
fix(db): enforce unique constraint on wallet_address.address
suisuss Jun 29, 2026
4864a25
fix(mfa): make step-up nonce and OTP minting atomic via transaction
suisuss Jun 29, 2026
12b5a2a
fix(org): make invite challenge nonce minting and consumption atomic
suisuss Jun 29, 2026
b58f699
fix(org): wrap wallet-accept member insert and invite update in trans…
suisuss Jun 29, 2026
076922a
fix(api): handle malformed or missing body in display-name route
suisuss Jun 29, 2026
6891045
fix(onboarding): guard waitForAnchor against pre-aborted signals
suisuss Jun 29, 2026
3fcc65f
fix(onboarding): remove dead addressRef and resolveWalletAddress netw…
suisuss Jun 29, 2026
8ffbf1e
fix(settings): catch wallet step-up rejections in security section
suisuss Jun 29, 2026
2195ab5
fix(tests): guard wallet-invite test against undefined cast and conte…
suisuss Jun 29, 2026
182b307
fix(auth): gate HTTPS localhost origins behind NODE_ENV development
suisuss Jun 29, 2026
20cc279
fix(auth): treat wallet (SIWE) users as authenticated in isNewUserSes…
suisuss Jun 29, 2026
d03bb46
fix(api): add error handling to GET /api/user/step-up/policy
suisuss Jun 29, 2026
702f651
fix(ui): add error handling to signOut and email code flows in enforc…
suisuss Jun 29, 2026
52edfce
fix(org): route wallet invite acceptance by invitation email not sess…
suisuss Jun 29, 2026
9186dad
fix(org): add loadError state and null guard on factors in mfa-enforc…
suisuss Jun 29, 2026
91d7126
fix(onboarding): destroy driver.js tour instance on component unmount
suisuss Jun 29, 2026
c41708e
fix(onboarding): stabilize forceOpen effect deps and void floating pr…
suisuss Jun 29, 2026
22168bc
fix(settings): add error state and retry to wallet-security-section load
suisuss Jun 29, 2026
74cf22f
fix(settings): fire onEnrolled when TOTP dialog is closed via X after…
suisuss Jun 29, 2026
f496b02
fix(security): log errors from api-key notification delivery failures
suisuss Jun 29, 2026
1cd152e
fix(tests): type TEST_WALLET_PRIVATE_KEY as 0x${string} to eliminate …
suisuss Jun 29, 2026
e4c36a9
test(integration): align session mock shape with real Better Auth str…
suisuss Jun 29, 2026
fbb4f8d
fix(onboarding): prevent tour effect re-trigger on failure and canvas…
suisuss Jun 29, 2026
de4e275
fix(onboarding): stabilize latch effect in useGettingStarted to react…
suisuss Jun 29, 2026
97ffeec
fix(auth): add noreferrer to noopener on install wallet link
suisuss Jun 29, 2026
87fc5af
fix(auth): validate hex prefix on signature and wallet address before…
suisuss Jun 29, 2026
84890e9
fix(api): whitelist action against STEP_UP_ACTIONS before writing to …
suisuss Jun 29, 2026
7da3da2
chore(withdraw): remove redundant email cast in withdraw modal
suisuss Jun 29, 2026
3382597
feat(api): audit-log display name updates for wallet accounts
suisuss Jun 29, 2026
1be1ce4
chore(ui): void fire-and-forget copySigninAddress call in user menu
suisuss Jun 29, 2026
1e8dc5e
fix(canvas): stabilize centerNode effect by reading fitViewSidebarAwa…
suisuss Jun 29, 2026
61b400e
chore(ai): remove debug console statements and add explicit return ty…
suisuss Jun 29, 2026
e9d1d03
test(unit): flush microtasks via macrotask before negative assertion …
suisuss Jun 29, 2026
2d34f4e
fix(seed): scope TOTP update to record id and preserve secret on re-seed
suisuss Jun 29, 2026
c41a781
fix(settings): reset AddEmailDialog state when dismissed via X
suisuss Jun 29, 2026
0f201c2
revert(ai): restore console logging in AIPrompt - client components a…
suisuss Jun 29, 2026
5ad39e1
docs(claude): document server vs client logging conventions
suisuss Jun 29, 2026
05f03fd
fix(auth): restore http localhost as always-trusted; gate only HTTPS …
suisuss Jun 29, 2026
852cb61
test(integration): KEEP-171 update org-api-keys mocks for authorizeAc…
suisuss Jun 29, 2026
1780907
test: KEEP-171 fix user-delete integration tests after authorizeActio…
suisuss Jun 29, 2026
0d20813
fix(auth): audit step-up email and policy changes
suisuss Jun 29, 2026
0910a4e
test(auth): clarify trusted-origins test for NODE_ENV-gated HTTPS loc…
suisuss Jun 29, 2026
77291df
fix(auth): include wallet address count in deactivation audit event
suisuss Jun 29, 2026
e2a5abb
refactor(auth): register internal step-up actions in STEP_UP_ACTIONS
suisuss Jun 29, 2026
70807c8
fix(auth): reject step-up email if already a primary login address
suisuss Jun 29, 2026
c0da7fb
perf(auth): cache org MFA enforcement config to reduce proxy DB overhead
suisuss Jun 29, 2026
39474d2
test(auth): E2E coverage for wallet org-MFA enforcement gate
suisuss Jun 29, 2026
4dbe210
fix(test): repair CI failures from KEEP-171 gap fixes
suisuss Jun 29, 2026
50177bb
fix(onboarding): brand-green states, float card above pill, grow/shri…
joelorzet Jun 29, 2026
cccc0d8
feat(onboarding): clone curated HUB starter workflows from getting-st…
joelorzet Jun 29, 2026
9d1a08f
build(onboarding): pass starter-workflow NEXT_PUBLIC vars through the…
joelorzet Jun 29, 2026
3a1b73e
fix(onboarding): clone each starter workflow once, reopen it, and mar…
joelorzet Jun 29, 2026
6ecb782
fix(onboarding): refresh sidebar workflow list after cloning or creat…
joelorzet Jun 29, 2026
c6d9a70
fix(onboarding): flip launcher pill from right-panel state, not a DOM…
joelorzet Jun 29, 2026
4bb6d97
fix(onboarding): compose persisted state so a cloned chip stays marke…
joelorzet Jun 29, 2026
6f0469f
fix(onboarding): derive chip/step completion from live clone existenc…
joelorzet Jun 29, 2026
1006716
fix(workflow): autofill protocol address field when picking from the …
joelorzet Jun 29, 2026
6f8be5f
fix(auth): keep ConnectButton mounted during signup and verify-OTP steps
suisuss Jun 29, 2026
2222144
feat: KEEP-171 auto-add SIWE signing wallet to address book on signup
suisuss Jun 30, 2026
3a65d2a
fix(onboarding): exclude auto-created web3 integration from alertsCon…
suisuss Jun 30, 2026
c483bed
feat: KEEP-171 add Scan Wallet step to Monitor and Yield onboarding g…
suisuss Jun 30, 2026
25f0e75
chore: KEEP-171 fix migration indices after rebase
suisuss Jun 30, 2026
32cb3db
chore: KEEP-171 sync pnpm lockfile after rebase
suisuss Jun 30, 2026
d5a99aa
fix: KEEP-171 correct isAnonymous and emailVerified check in user-menu
suisuss Jun 30, 2026
00ace48
feat: KEEP-171 lock sequential steps until previous step is complete
suisuss Jun 30, 2026
b2e39fe
fix: KEEP-171 brighter locked steps, green scan spinner, hide launche…
suisuss Jun 30, 2026
dd46a8c
fix: KEEP-171 add resend verification code option to connect auth panel
suisuss Jun 30, 2026
3a5e592
fix: KEEP-171 show wallet connection hint and cancel after 2s wait
suisuss Jun 30, 2026
405f89e
refactor: KEEP-171 inline MFA steps into connect auth panel
suisuss Jun 30, 2026
ed6e81a
fix: KEEP-171 backstop wallet address book entry on every SIWE sign-in
suisuss Jun 30, 2026
47e13a9
chore(onboarding): add canonical social and support links module
joelorzet Jun 30, 2026
3773b3f
fix(auth): reject wallet-domain address as a step-up email
joelorzet Jun 30, 2026
1813b42
feat(onboarding): add a contact-support dialog
joelorzet Jun 30, 2026
331a7d0
feat(nav): show community link icons in the sidebar footer
joelorzet Jun 30, 2026
22acc17
feat: KEEP-171 replace onboarding env vars with dynamic DB-seeded wor…
suisuss Jul 1, 2026
0e5249a
chore: KEEP-171 remove unused imports and variables flagged by code q…
suisuss Jul 1, 2026
a71c18d
feat: KEEP-171 seed onboarding workflows on deploys and add fixture t…
suisuss Jul 1, 2026
861ec44
feat: KEEP-171 lazy-seed onboarding workflows on recommendations request
suisuss Jul 1, 2026
c63f12e
feat(onboarding): open the getting-started launcher after first walle…
joelorzet Jul 1, 2026
b03ab3e
fix(wallet): refresh wallet info for wallet users after signup provis…
joelorzet Jul 1, 2026
7432f38
refactor(mfa): merge API-key create/revoke into a single manage action
joelorzet Jul 1, 2026
17dc702
fix(auth): let wallet users see and act on received invitations
joelorzet Jul 1, 2026
80a2fbf
refactor(auth): extract shared wallet picker and reusable connect panel
joelorzet Jul 2, 2026
8acf3d6
refactor(auth): remove the wallet display-name modal
joelorzet Jul 2, 2026
de86527
refactor(nav): trim sidebar footer to Discord and Documentation
joelorzet Jul 2, 2026
e64c411
feat(db): persist onboarding completion per user
joelorzet Jul 2, 2026
22e4a3e
feat(onboarding): add welcome landing and session gating
joelorzet Jul 2, 2026
9467278
feat(onboarding): add post-signup wizard and agent connect
joelorzet Jul 2, 2026
1475a3b
test(auth): route wallet-login e2e through the welcome page
joelorzet Jul 2, 2026
d5264be
refactor(onboarding): extract invite validation into a pure module
joelorzet Jul 2, 2026
78dc216
test(onboarding): unit-cover agent commands, invite validation, and f…
joelorzet Jul 2, 2026
403c55e
test(onboarding): add welcome landing and guest e2e
joelorzet Jul 2, 2026
e7cff66
test(auth): align wallet-login e2e with the modal-free flow
joelorzet Jul 2, 2026
faf1cbf
feat(onboarding): redirect signed-out visitors to welcome at the proxy
joelorzet Jul 2, 2026
9285754
refactor(auth): match the Connect modal to the welcome sign-in
joelorzet Jul 2, 2026
1565861
fix(auth): show the KeeperHub logo in the Connect modal header
joelorzet Jul 2, 2026
0f2be7f
style(auth): loosen spacing in the Connect modal
joelorzet Jul 2, 2026
be5c0ce
fix(onboarding): gate on the server flag so sign-in always runs the w…
joelorzet Jul 2, 2026
fe189ec
test(onboarding): drop the welcome-seen flag from tests
joelorzet Jul 2, 2026
a070b87
fix(onboarding): hard-navigate home after finishing the wizard
joelorzet Jul 2, 2026
18b21b2
fix(onboarding): validate invites on Next, not while typing
joelorzet Jul 2, 2026
9f69715
Merge branch 'staging' into feat/keep-171-improve-user-onboarding-exp…
joelorzet Jul 2, 2026
b431f8e
feat(onboarding): require API key to finish connect-agent step
joelorzet Jul 2, 2026
2f1baa9
refactor(security): simplify wallet step-up to enrolled factors
joelorzet Jul 3, 2026
1e19d51
fix(onboarding): gate root canvas behind session decision
joelorzet Jul 3, 2026
1ae0c68
fix(onboarding): route wallet sign-in by onboarding status
joelorzet Jul 3, 2026
3e5463b
feat(onboarding): remove Scan your wallet from Get Started
joelorzet Jul 3, 2026
421fcae
fix(onboarding): surface starter-workflow clone failures
joelorzet Jul 3, 2026
77fe034
fix(onboarding): never mount root canvas until session gate resolves
joelorzet Jul 3, 2026
8369eb5
fix(workflows): fall back to membership org when no active org is set
joelorzet Jul 3, 2026
9fde604
feat(onboarding): seed three example workflows for new signups
joelorzet Jul 3, 2026
9c64da1
feat(auth): row of Google/GitHub/Wallet with inline email form
joelorzet Jul 3, 2026
08aac4b
refactor(auth): route the app-wide auth modal through SignInChoices
joelorzet Jul 3, 2026
867a57c
feat(onboarding): flatten Get Started to a single checklist
joelorzet Jul 3, 2026
77038b8
feat(onboarding): move Get Started launcher into the sidebar
joelorzet Jul 3, 2026
13f4dfa
feat(onboarding): tidy user menu, move Getting started above Logout
joelorzet Jul 3, 2026
caa659c
feat(onboarding): rework connect-agent step
joelorzet Jul 3, 2026
dcd5144
style(onboarding): translucent tab bar, swap Goose/Gemini order
joelorzet Jul 3, 2026
ba872bf
feat(welcome): branded multi-turn agent session in connect-agent preview
joelorzet Jul 3, 2026
ad86209
feat(welcome): interactive composer, per-agent models, animated previ…
joelorzet Jul 3, 2026
e6ba0e6
feat(welcome): longer example run, bounded chat viewport with auto-sc…
joelorzet Jul 3, 2026
f2d51a0
feat(welcome): longer stETH example prompt with fewer chat iterations
joelorzet Jul 4, 2026
18343fa
feat(auth): label the in-app anonymous entry point "Sign in"
joelorzet Jul 4, 2026
84bfb1b
test(e2e): fix auth helpers and seed for the welcome sign-in landing
joelorzet Jul 4, 2026
8585aad
test(e2e): add welcome/onboarding root-gating coverage
joelorzet Jul 4, 2026
ae20ab4
Merge branch 'staging' into feat/keep-171-improve-user-onboarding-exp…
joelorzet Jul 4, 2026
4e77d3a
feat(onboarding): open a connect-agent modal from getting started
joelorzet Jul 4, 2026
45ef98b
test: align proxy and dual-auth unit tests with welcome redirect and …
joelorzet Jul 4, 2026
3428866
feat(onboarding): add Connect an agent to the user menu
joelorzet Jul 6, 2026
89ad16f
feat(onboarding): richer example workflows with email alerts, pre-lai…
joelorzet Jul 6, 2026
c702384
feat(onboarding): expand getting-started by default for new users and…
joelorzet Jul 6, 2026
5f7a039
Merge branch 'staging' into feat/keep-171-improve-user-onboarding-exp…
joelorzet Jul 6, 2026
d994ba0
fix(org): widen manage-orgs tab grid to fit the Security and Limits tabs
joelorzet Jul 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ STRIPE_PRICE_BUSINESS_1M_YEARLY=
STRIPE_PRICE_ENTERPRISE_MONTHLY=
STRIPE_PRICE_ENTERPRISE_YEARLY=


# Turnkey Wallet Provider
TURNKEY_API_PUBLIC_KEY=
TURNKEY_API_PRIVATE_KEY=
Expand Down
48 changes: 48 additions & 0 deletions .github/workflows/deploy-keeperhub.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,54 @@ jobs:
--filter "name=${SERVICE_NAME}-common-test-irsa-auth" \
--logs

- name: Seed onboarding workflows
if: steps.skip.outputs.skip_deploy != 'true'
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ steps.vars.outputs.sha_short }}
REGION: ${{ env.REGION }}
NAMESPACE: ${{ env.NAMESPACE }}
SEED_EMAIL: ${{ vars.ONBOARDING_SEED_EMAIL }}
run: |
set -euo pipefail
if [[ "${{ github.ref_name }}" == "prod" ]]; then
SSM_PATH="/eks/techops-prod/keeperhub/db-url"
MIGRATOR_IMAGE="${ECR_REGISTRY}/keeperhub-prod:migrator-${IMAGE_TAG}"
else
SSM_PATH="/eks/techops-staging/keeperhub/db-url"
MIGRATOR_IMAGE="${ECR_REGISTRY}/keeperhub-staging:migrator-${IMAGE_TAG}"
fi
DATABASE_URL=$(aws ssm get-parameter --name "${SSM_PATH}" --with-decryption --query "Parameter.Value" --output text --region "${REGION}")
kubectl delete job keeperhub-onboarding-seed -n "${NAMESPACE}" --ignore-not-found
kubectl apply -n "${NAMESPACE}" -f - <<EOF
apiVersion: batch/v1
kind: Job
metadata:
name: keeperhub-onboarding-seed
spec:
backoffLimit: 2
ttlSecondsAfterFinished: 600
template:
spec:
restartPolicy: Never
containers:
- name: onboarding-seed
image: ${MIGRATOR_IMAGE}
command: ["/bin/sh", "-c"]
args:
- 'pnpm tsx scripts/seed/seed-onboarding-workflows.ts'
env:
- name: DATABASE_URL
value: "${DATABASE_URL}"
- name: ALLOW_REMOTE
value: "1"
- name: SEED_EMAIL
value: "${SEED_EMAIL}"
EOF
kubectl wait --for=condition=complete job/keeperhub-onboarding-seed -n "${NAMESPACE}" --timeout=120s || true
echo "--- onboarding-seed job logs ---"
kubectl logs -n "${NAMESPACE}" job/keeperhub-onboarding-seed --tail=30 || true

# DISABLED: remote E2E tests temporarily disabled while KEEP-1351 stabilises auth/rate-limit infrastructure
e2e-playwright-remote:
needs: deploy
Expand Down
41 changes: 41 additions & 0 deletions .github/workflows/deploy-pr-environment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,47 @@ jobs:
echo "--- hmac-seed job logs ---"
kubectl logs -n "${NAMESPACE}" job/keeperhub-pr-${PR_NUMBER}-hmac-seed --tail=30 || true

- name: Seed onboarding workflows
env:
PR_NUMBER: ${{ env.PR_NUMBER }}
NAMESPACE: ${{ env.NAMESPACE }}
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ steps.vars.outputs.sha_short }}
run: |
set -euo pipefail
DB_PASSWORD=$(kubectl get secret keeperhub-pr-${PR_NUMBER}-db-credentials -n "${NAMESPACE}" -o jsonpath='{.data.password}' | base64 -d)
ENCODED_PASSWORD=$(DB_PASSWORD="${DB_PASSWORD}" python3 -c "import urllib.parse, os; print(urllib.parse.quote(os.environ['DB_PASSWORD'], safe=''))")
DB_URL="postgresql://keeperhub:${ENCODED_PASSWORD}@keeperhub-pr-${PR_NUMBER}-db-rw.${NAMESPACE}.svc.cluster.local:5432/keeperhub"
kubectl delete job keeperhub-pr-${PR_NUMBER}-onboarding-seed -n "${NAMESPACE}" --ignore-not-found
kubectl apply -n "${NAMESPACE}" -f - <<EOF
apiVersion: batch/v1
kind: Job
metadata:
name: keeperhub-pr-${PR_NUMBER}-onboarding-seed
spec:
backoffLimit: 2
ttlSecondsAfterFinished: 600
template:
spec:
restartPolicy: Never
containers:
- name: onboarding-seed
image: ${ECR_REGISTRY}/keeperhub-staging:migrator-${IMAGE_TAG}
command: ["/bin/sh", "-c"]
args:
- 'pnpm tsx scripts/seed/seed-onboarding-workflows.ts || echo "onboarding seed failed - see above"'
env:
- name: DATABASE_URL
value: "${DB_URL}"
- name: ALLOW_REMOTE
value: "1"
- name: SEED_EMAIL
value: "pr-test-do-not-delete@techops.services"
EOF
kubectl wait --for=condition=complete job/keeperhub-pr-${PR_NUMBER}-onboarding-seed -n "${NAMESPACE}" --timeout=120s || true
echo "--- onboarding-seed job logs ---"
kubectl logs -n "${NAMESPACE}" job/keeperhub-pr-${PR_NUMBER}-onboarding-seed --tail=30 || true

# Opt-in (deploy-pr-metrics label). Deploys the metrics-collector
# satellite against the PR DB so its /metrics can be curled directly
# (ServiceMonitors are off in PR envs). TECH-6484.
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,6 @@ reviews/
.understand-anything/diff-overlay.json
.understand-anything/meta.json
.understand-anything/fingerprints.json

# Self-signed certs generated by `next dev --experimental-https`
certificates
3 changes: 3 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,6 @@ minimum-release-age-exclude[]=qrcode-generator
# only needed because pnpm checks the latest published version on resolve.
minimum-release-age-exclude[]=deep-diff
minimum-release-age-exclude[]=@types/deep-diff
# driver.js (MIT) is the onboarding-tour library; pnpm's release-age gate flags
# it on resolve, so exclude it so lockfile regeneration / fresh installs succeed.
minimum-release-age-exclude[]=driver.js
60 changes: 59 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
- Use optional chaining (`?.`) and nullish coalescing (`??`)
- Use `const` by default, `let` only when reassignment is needed
- Always `await` promises in async functions
- Remove `console.log`, `debugger`, and `alert` from production code
- Remove `debugger` and `alert` from all code without exception
- **Logging is context-dependent** — see the Logging section below for the full rule
- Use Next.js `<Image>` component instead of `<img>` tags
- Add `rel="noopener"` when using `target="_blank"`

Expand Down Expand Up @@ -102,6 +103,63 @@ When you must use an ignore comment:
const result = externalLib.call() as any;
```

## Logging

Logging rules differ between server and client code. The Biome `noConsole` rule enforces this boundary: it bans `console.warn/error` on server paths while disabling the rule entirely for `components/**`, `lib/hooks/**`, `app/page.tsx`, `app/workflows/**`, `lib/api-client.ts`, and `tests/**`.

### Server-side (app/api/**, lib/**, app/*/route.ts, scripts/**)

Use the functions from `lib/logging.ts`. Never use `console.warn` or `console.error` on server paths — Biome treats these as errors.

| Function | When to use | Sentry | Prometheus |
|---|---|---|---|
| `logSystemError(category, message, error, labels?)` | Infrastructure/DB/auth failures the system should not encounter | yes (error) | yes |
| `logUserError(category, message, error?, labels?)` | Validation, bad input, external-service rejections caused by the caller | no | yes |
| `logSystemWarn(category, message, error, labels?)` | Recovery events, pre-reconciliation notes, expected fallbacks worth tracking | yes (warning) | no |
| `logInfo(message, labels?)` | State transitions and lifecycle events | no | no |
| `logWarn(message, labels?)` | Benign anomalies that are not operational failures | no | no |
| `logDebug(message, labels?)` | Verbose tracing, gated by `LOG_LEVEL=debug` | no | no |
| `logSecurityEvent(name, fields?, sentry?)` | Security detection signals (KEEP-612) | yes | no |

`ErrorCategory` values: `VALIDATION`, `CONFIGURATION`, `EXTERNAL_SERVICE`, `NETWORK_RPC`, `TRANSACTION`, `BILLING`, `DATABASE`, `AUTH`, `INFRASTRUCTURE`, `WORKFLOW_ENGINE`, `UNKNOWN`.

**Message format**: Use a `[Context] message` prefix. The context string is extracted by regex and becomes the `error_context` label in Loki and Prometheus.

```typescript
// Good
logSystemError(ErrorCategory.DATABASE, "Failed to create workflow", error, {
endpoint: "/api/workflows/create",
operation: "create",
});

logUserError(ErrorCategory.VALIDATION, "[Withdraw] Invalid amount", undefined, {
userId: session.user.id,
});

// Wrong — banned by Biome on server paths
console.error("something broke", error);
console.warn("unexpected state");
```

`console.log/info/debug` are technically allowed by Biome on server paths (the lib/logger facade normalises them to structured JSON) but prefer `logInfo`/`logDebug` so the structured labels and workflow context are included automatically.

### Client-side (components/**, lib/hooks/**, lib/api-client.ts, app/page.tsx, app/workflows/**)

`console.*` is unrestricted — the `noConsole` rule is off for these paths. Logs go to the browser devtools and client-side Sentry; the server observability pipeline (Prometheus, Loki) does not apply.

Use a `[Component]` prefix to match server-side convention and make devtools filtering easy:

```typescript
// Good
console.log("[AI Prompt] Generating workflow", { hasNodes, existingWorkflow: !!existingWorkflow });
console.error("Failed to generate workflow:", error);

// Fine but noisy — keep to meaningful state transitions, not every render
console.log("[AIPrompt] re-render");
```

---

## Design System

Before writing or modifying any UI code, read the relevant spec file in `specs/design-system/`. Use only tokens from `specs/design-system/tokens.css`. Run `node scripts/token-audit.js` before committing UI changes. Zero errors required.
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ ENV NEXT_PUBLIC_BILLING_ENABLED=$NEXT_PUBLIC_BILLING_ENABLED
ENV NEXT_PUBLIC_GAS_SPONSORSHIP_ENABLED=$NEXT_PUBLIC_GAS_SPONSORSHIP_ENABLED
ENV NEXT_PUBLIC_TURNSTILE_SITE_KEY=$NEXT_PUBLIC_TURNSTILE_SITE_KEY


# Sentry DSN baked into client bundle for error reporting.
# SENTRY_ORG/PROJECT/AUTH_TOKEN/RELEASE are intentionally NOT set here
# so this stage is cache-deterministic across commits (see sentry-upload stage).
Expand Down
67 changes: 63 additions & 4 deletions app/accept-invite/[inviteId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Spinner } from "@/components/ui/spinner";
import { isWalletEmail } from "@/lib/auth/wallet-constants";
import {
authClient,
signIn,
Expand All @@ -18,6 +19,7 @@ import {
} from "@/lib/auth-client";
import { refetchOrganizations } from "@/lib/refetch-organizations";
import { refetchSidebar } from "@/lib/refetch-sidebar";
import { acceptWalletInvite } from "@/lib/wallet/invite-accept-client";

type InvitationData = {
id: string;
Expand Down Expand Up @@ -122,7 +124,11 @@ function LoadingState() {
);
}

async function trySignUp(email: string, password: string, captchaToken?: string) {
async function trySignUp(
email: string,
password: string,
captchaToken?: string
) {
const response = await signUp.email({
email,
password,
Expand Down Expand Up @@ -199,12 +205,20 @@ function AcceptDirectState({
}) {
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState("");
const isWallet = isWalletEmail(invitation.email);

const handleAccept = async () => {
setSubmitting(true);
setError("");

try {
// Wallet (SIWE) invites are accepted by signing the invite challenge with
// the connected wallet; the email-match path doesn't apply to them.
if (isWallet) {
await acceptWalletInvite(invitation.id);
onSuccess();
return;
}
const result = await acceptInvitation(invitation.id);
if (!result.success) {
setError(result.error || "Failed to accept invitation");
Expand Down Expand Up @@ -236,8 +250,12 @@ function AcceptDirectState({

<div className="space-y-4">
<div className="rounded-md bg-muted/50 p-3 text-center text-sm">
You&apos;re signed in as{" "}
<span className="font-medium">{invitation.email}</span>
{isWallet
? "Sign the challenge with your wallet to join."
: "You're signed in as "}
{!isWallet && (
<span className="font-medium">{invitation.email}</span>
)}
</div>

{error && (
Expand All @@ -252,7 +270,12 @@ function AcceptDirectState({
onClick={handleAccept}
>
{submitting && <Spinner className="mr-2 size-4" />}
{submitting ? "Joining..." : "Accept Invitation"}
{(() => {
if (submitting) {
return isWallet ? "Waiting for signature..." : "Joining...";
}
return isWallet ? "Sign to join" : "Accept Invitation";
})()}
</Button>
</div>
</div>
Expand Down Expand Up @@ -507,6 +530,34 @@ function VerificationFormState({
);
}

// Logged-out wallet invite: the invitee can't use email/password signup, so
// steer them to connect their wallet (SIWE). After connecting, their session
// email matches the invite and the page re-renders into the sign-to-join state.
function WalletConnectState({ invitation }: { invitation: InvitationData }) {
const { openAuthPrompt } = useAuthPrompt();
return (
<div className="pointer-events-auto flex min-h-screen items-center justify-center p-4">
<div className="w-full max-w-md space-y-6 rounded-lg border bg-card p-8 text-center">
<div className="space-y-2">
<h1 className="font-semibold text-2xl">
Join {invitation.organizationName}
</h1>
<p className="text-muted-foreground text-sm">
{invitation.inviterName} invited you to join as{" "}
<span className="font-medium text-foreground">
{invitation.role}
</span>
. Connect your wallet to continue, then sign to join.
</p>
</div>
<Button className="w-full" onClick={() => openAuthPrompt()}>
Connect your wallet
</Button>
</div>
</div>
);
}

// Component for logged-out users with sign-in/sign-up toggle
function AuthFormState({
invitation,
Expand Down Expand Up @@ -865,6 +916,14 @@ export default function AcceptInvitePage() {
);
}

if (isWalletEmail(invitation.email)) {
return (
<div data-page-state={pageState}>
<WalletConnectState invitation={invitation} />
</div>
);
}

return (
<div data-page-state={pageState}>
<AuthFormState
Expand Down
Loading
Loading