Skip to content

feat(auth): passkey conditional UI and WebAuthn Level 3 hints - #894

Merged
2witstudios merged 11 commits into
masterfrom
pu/passkey
Apr 13, 2026
Merged

2witstudios merged 11 commits into
masterfrom
pu/passkey

Conversation

@2witstudios

@2witstudios 2witstudios commented Apr 12, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Add WebAuthn conditional UI (passkey autofill) to the sign-in page via a new useConditionalPasskeyUI hook, allowing users to authenticate with a single tap from the browser's autofill dropdown
  • Add hints: ['client-device'] to authentication options (WebAuthn Level 3) to prefer platform authenticators over QR/hybrid prompts
  • Add autoComplete="email webauthn" to email inputs on sign-in and magic-link forms to anchor conditional mediation
  • Extract useConditionalPasskeyUI into its own module with ref-based callback stability (prevents infinite re-render loops from inline arrow props)
  • Proactively refresh the conditional UI challenge on a 4-minute timer (server TTL 5 − 1-min safety buffer, derived from a shared constant) so long-idle autofill selections no longer fail with CHALLENGE_EXPIRED. Ceremony logic extracted into a pure conditionalPasskeyCeremony module (asyncPipe-composed steps, explicit state machine, result objects) so the hook is a thin wiring layer.
  • Fix cross-session challenge cleanup race: generateAuthenticationOptions no longer deletes concurrent visitors' in-flight challenges when the flow is conditional-UI (shared system user). User-keyed flows still wipe all unused webauthn_auth rows; the system-user branch now only evicts expired rows.

Changes

File What
packages/lib/src/auth/passkey-service.ts hints: ['client-device'] + split cleanup (user-keyed wipes all unused; system-user conditional-UI only wipes expired) + challengeExpiryMinutes now sourced from shared constant
packages/lib/src/auth/passkey-client-constants.ts New — client-safe PASSKEY_CHALLENGE_EXPIRY_MINUTES constant, single source of truth for client/server
packages/lib/src/{index,client-safe,auth/index}.ts Re-export the new client-safe constant
apps/web/src/components/auth/useConditionalPasskeyUI.ts New — extracted hook with ref-based callback stability, CSRF refresh before verify, mountedRef guards; now a thin wiring layer over driveCeremony + handleCeremonyResult
apps/web/src/components/auth/conditionalPasskeyCeremony.ts New — pure module: predicates, classifiers, deriveRefreshIntervalMs, asyncPipe, pipe steps, nextState reducer, driveCeremony loop, handleCeremonyResult
apps/web/src/components/auth/PasskeyLoginButton.tsx Remove inline hook (now in own module)
apps/web/src/app/auth/signin/page.tsx Wire conditional UI + add email input anchor
apps/web/src/components/auth/MagicLinkForm.tsx Add webauthn to autoComplete
apps/web/src/components/auth/index.ts Update barrel export

Test plan

  • packages/lib/src/auth/__tests__/passkey-service.test.ts — 43/43 passing (hints assertions + 2 new cleanup-scope assertions: conditional UI adds lt(expiresAt, now), email-scoped flow does not)
  • apps/web/src/components/auth/__tests__/useConditionalPasskeyUI.test.ts — 4/4 passing (callback stability with stable refs, changing token, inline arrows; public surface unchanged)
  • apps/web/src/components/auth/__tests__/conditionalPasskeyCeremony.test.ts — 33/33 passing (predicates, classifiers, deriveRefreshIntervalMs, nextState reducer, driveCeremony loop, handleCeremonyResult, integrated runCeremony pipe with fake-timer refresh-abort path)
  • Full @pagespace/lib test suite — 4028/4028 passing
  • TypeScript type check — zero errors (both web and @pagespace/lib)
  • Lint — no new warnings
  • Manual: open /auth/signin, verify passkey autofill appears in email field on supported browsers (Chrome 108+, Safari 16+)
  • Manual: verify explicit "Sign in with Passkey" button still works as fallback
  • Manual: observe a second POST /api/auth/passkey/authenticate/options hitting the network tab at ~4 min without user interaction, then selecting a passkey from autofill succeeds

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added cloud-only passkey autofill support to the sign-in page, enabling seamless passwordless authentication through browser autofill.
  • Improvements

    • Enhanced email input autocomplete behavior across authentication forms for a better user experience.

2witstudios and others added 5 commits April 11, 2026 16:18
Destructure options fields into useCallback dependency array to prevent
infinite re-render loop caused by object identity changing every render.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace QR-first passkey flow with native platform authenticator experience.
Adds WebAuthn Level 3 hints: ['client-device'] to prefer Touch ID / Windows
Hello over hybrid/QR transport, and activates conditional UI (autofill) on
the signin page so passkeys appear in the browser dropdown without a button
click — per FIDO Alliance UX guidelines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The useConditionalPasskeyUI hook recreated startConditionalUI every
render because callers passed inline onSuccess/refreshToken arrows.
Using refs for option callbacks decouples identity from the useCallback
dep array, so startConditionalUI stays stable across re-renders.

Also: extract hook to own module for testability, guard conditional UI
behind isAvailable check and on-prem flag, fix GenerateAuthOptionsResult
type to include hints field, remove redundant mountedRef initialization.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Point index.ts directly at useConditionalPasskeyUI.ts and remove the
now-unnecessary re-export from PasskeyLoginButton.tsx.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Apr 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pagespace-master-plan Ready Ready Preview, Comment Apr 13, 2026 3:52am

@coderabbitai

coderabbitai Bot commented Apr 12, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@2witstudios has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 15 minutes and 20 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 15 minutes and 20 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e19a252c-b6af-4476-8f9c-fc2c2e7992c7

📥 Commits

Reviewing files that changed from the base of the PR and between 7f7c144 and 47146ff.

📒 Files selected for processing (10)
  • apps/web/src/components/auth/__tests__/conditionalPasskeyCeremony.test.ts
  • apps/web/src/components/auth/__tests__/useConditionalPasskeyUI.test.ts
  • apps/web/src/components/auth/conditionalPasskeyCeremony.ts
  • apps/web/src/components/auth/useConditionalPasskeyUI.ts
  • packages/lib/src/auth/__tests__/passkey-service.test.ts
  • packages/lib/src/auth/index.ts
  • packages/lib/src/auth/passkey-client-constants.ts
  • packages/lib/src/auth/passkey-service.ts
  • packages/lib/src/client-safe.ts
  • packages/lib/src/index.ts
📝 Walkthrough

Walkthrough

Refactored useConditionalPasskeyUI hook into a standalone module, integrated it into the sign-in page with cloud-only (CSRF-gated) conditional UI support, updated email input autocomplete attributes, added comprehensive tests, and enhanced authentication options with device hints.

Changes

Cohort / File(s) Summary
Passkey Conditional UI Hook
apps/web/src/components/auth/PasskeyLoginButton.tsx, apps/web/src/components/auth/useConditionalPasskeyUI.ts
Extracted useConditionalPasskeyUI hook from PasskeyLoginButton.tsx into dedicated useConditionalPasskeyUI.ts file. Hook manages conditional mediation availability detection, WebAuthn authentication, CSRF token refresh, and response verification with optional callbacks and error handling.
Sign-in Page Integration
apps/web/src/app/auth/signin/page.tsx
Integrated conditional passkey UI into sign-in form. Initializes hook with CSRF token (cloud-only) and refreshToken, renders conditional email input field with autoComplete="email webauthn" as autofill anchor when available, with fallback passkey button for unsupported browsers.
Email Input Autocomplete
apps/web/src/components/auth/MagicLinkForm.tsx
Updated email input autoComplete attribute from "email" to "email webauthn" to support WebAuthn autofill integration.
Authentication Options
packages/lib/src/auth/passkey-service.ts
Added AuthenticationOptionsWithHints type and updated generateAuthenticationOptions to include hints: ['client-device'] in returned options for conditional mediation support.
Test Coverage
apps/web/src/components/auth/__tests__/useConditionalPasskeyUI.test.ts, packages/lib/src/auth/__tests__/passkey-service.test.ts
Added comprehensive Vitest suite for useConditionalPasskeyUI hook covering callback stability and token updates. Updated passkey service tests to assert hints: ['client-device'] in authentication options results.
Module Exports
apps/web/src/components/auth/index.ts
Updated barrel export to re-export useConditionalPasskeyUI from standalone ./useConditionalPasskeyUI instead of ./PasskeyLoginButton.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SignInForm as SignIn Page
    participant ConditionalUI as useConditionalPasskeyUI
    participant WebAuthn as Browser WebAuthn
    participant API as Backend API
    participant AuthStore as Auth Store

    User->>SignInForm: Load sign-in page
    SignInForm->>ConditionalUI: Initialize hook with csrfToken
    ConditionalUI->>WebAuthn: Check isConditionalMediationAvailable()
    WebAuthn-->>ConditionalUI: availability status
    ConditionalUI-->>SignInForm: isAvailable flag
    
    alt Conditional UI Available & CSRF Present
        SignInForm->>SignInForm: Render email input with autofill anchor
        User->>SignInForm: Focus email field (triggers autofill)
        SignInForm->>ConditionalUI: startConditionalUI()
        ConditionalUI->>API: POST /api/auth/passkey/authenticate/options
        API-->>ConditionalUI: auth challenge & options
        ConditionalUI->>WebAuthn: startAuthentication(useBrowserAutofill: true)
        WebAuthn-->>ConditionalUI: credential response
        ConditionalUI->>ConditionalUI: Refresh CSRF token via callback
        ConditionalUI->>API: POST /api/auth/passkey/authenticate (verify credential)
        API-->>ConditionalUI: verification success
        ConditionalUI->>AuthStore: Clear auth failure flag
        ConditionalUI-->>User: Success toast & redirect
    else Fallback (No Conditional UI)
        SignInForm->>SignInForm: Render passkey button
        User->>SignInForm: Click passkey button
        SignInForm->>User: Redirect or manual auth flow
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~35 minutes

Possibly related PRs

Poem

🐰 A hop through WebAuthn's sweet terrain,
Conditional UI flows through the sign-in lane,
The hook stands alone, clean and refined,
With hints for devices, a charm so kind!
No more passkey buttons blocking the view—
Cloud autofill's here, hopping on through! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: adding conditional UI support for WebAuthn/passkey autofill and implementing WebAuthn Level 3 hints for authentication options.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pu/passkey

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/app/auth/signin/page.tsx (1)

4-4: ⚠️ Potential issue | 🟠 Major

Auto-start conditional UI only once per mount.

refreshToken() updates the csrfToken state, and both useConditionalPasskeyUI and PasskeyLoginButton call it during auth. Because this effect keys off csrfToken, a mid-flight token refresh can re-enter startConditionalUI() while another passkey ceremony is already running. That turns the passive autofill flow and the explicit passkey button into a race on browsers that support conditional UI.

🛠️ Suggested fix
-import { useState, useEffect, Suspense } from "react";
+import { useState, useEffect, useRef, Suspense } from "react";
...
+  const conditionalUiStartedRef = useRef(false);
+
   useEffect(() => {
-    if (csrfToken && !onPrem) startConditionalUI();
-  }, [csrfToken, startConditionalUI, onPrem]);
+    if (!csrfToken || onPrem || !conditionalUIAvailable || conditionalUiStartedRef.current) {
+      return;
+    }
+
+    conditionalUiStartedRef.current = true;
+    void startConditionalUI();
+  }, [csrfToken, conditionalUIAvailable, startConditionalUI, onPrem]);

Also applies to: 95-106

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/auth/signin/page.tsx` at line 4, refreshToken updating
csrfToken can retrigger startConditionalUI and cause two passkey ceremonies;
prevent re-entry by adding a mount-scoped guard (e.g., a ref like
conditionalUIStartedRef) inside useConditionalPasskeyUI and PasskeyLoginButton
so startConditionalUI is only invoked once per component mount even if csrfToken
changes; check the ref before calling startConditionalUI, set it true
immediately when starting, and reset it on unmount/cleanup to allow future
mounts to start the UI again.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/components/auth/useConditionalPasskeyUI.ts`:
- Line 4: The pending startAuthentication call in useConditionalPasskeyUI.ts
must be explicitly cancelled on unmount and its expected abort error must be
ignored: import and call WebAuthnAbortService.cancelCeremony() in the cleanup to
abort the SimpleWebAuthn ceremony started by startAuthentication, and update the
error handling for the promise to treat both DOM AbortError and SimpleWebAuthn's
WebAuthnError with code === 'ERROR_CEREMONY_ABORTED' as non-failures (silently
ignore), ensuring you import WebAuthnAbortService and WebAuthnError from
'@simplewebauthn/browser' and reference the startAuthentication invocation and
its catch/cleanup logic when making the changes.

---

Outside diff comments:
In `@apps/web/src/app/auth/signin/page.tsx`:
- Line 4: refreshToken updating csrfToken can retrigger startConditionalUI and
cause two passkey ceremonies; prevent re-entry by adding a mount-scoped guard
(e.g., a ref like conditionalUIStartedRef) inside useConditionalPasskeyUI and
PasskeyLoginButton so startConditionalUI is only invoked once per component
mount even if csrfToken changes; check the ref before calling
startConditionalUI, set it true immediately when starting, and reset it on
unmount/cleanup to allow future mounts to start the UI again.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 73ed8fca-4f9e-44ee-874f-28a2e6046572

📥 Commits

Reviewing files that changed from the base of the PR and between 30c996d and 7f7c144.

📒 Files selected for processing (8)
  • apps/web/src/app/auth/signin/page.tsx
  • apps/web/src/components/auth/MagicLinkForm.tsx
  • apps/web/src/components/auth/PasskeyLoginButton.tsx
  • apps/web/src/components/auth/__tests__/useConditionalPasskeyUI.test.ts
  • apps/web/src/components/auth/index.ts
  • apps/web/src/components/auth/useConditionalPasskeyUI.ts
  • packages/lib/src/auth/__tests__/passkey-service.test.ts
  • packages/lib/src/auth/passkey-service.ts

Comment thread apps/web/src/components/auth/useConditionalPasskeyUI.ts Outdated
…RTED

Import WebAuthnAbortService and WebAuthnError from @simplewebauthn/browser.
Call cancelCeremony() in the cleanup effect so the conditional UI promise
doesn't leak across client-side navigation. Catch ERROR_CEREMONY_ABORTED
separately from generic AbortError to avoid spurious debug logging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7f7c1444ef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/app/auth/signin/page.tsx
Comment thread apps/web/src/components/auth/useConditionalPasskeyUI.ts Outdated
2witstudios and others added 2 commits April 12, 2026 22:30
…anup

Addresses two review concerns from PR #894 (Codex P1 + P2):

P2 — Refresh conditional UI challenge before long-idle verify
  The hook fetched authentication options once and waited indefinitely
  in conditional mediation; the server challenge expires after 5 min so
  long-idle autofill failed with CHALLENGE_EXPIRED. The ceremony is now
  driven by a pure state machine that proactively re-fetches options at
  4 minutes (server TTL - 1 min buffer, derived from a shared constant
  so client and server can't drift). If verify still returns
  CHALLENGE_EXPIRED as a safety net the same retry path fires.

  The ceremony logic is extracted into conditionalPasskeyCeremony.ts as
  pure predicates + classifiers + asyncPipe-composed steps + a reducer
  driven loop. The hook becomes a thin wiring layer and its public
  surface is unchanged (existing callback-ref stability tests still
  pass unmodified).

P1 — Stop clobbering concurrent sessions' in-flight challenges
  generateAuthenticationOptions unconditionally deleted all unused
  webauthn_auth rows for the cleanup user. For the user-keyed flow
  that is fine (single in-flight ceremony per user), but for the
  conditional-UI flow the cleanup user is the shared system user, so
  one visitor's /options call was invalidating every other concurrent
  visitor's challenge. The P2 refresh timer increases the /options
  call rate which would have made this race more frequent, so the
  cleanup is now split: user-keyed flows still wipe all unused rows,
  the system-user flow only evicts EXPIRED rows.

New shared client-safe constant PASSKEY_CHALLENGE_EXPIRY_MINUTES is
exported from @pagespace/lib so the browser-side refresh derivation
has a single source of truth with PASSKEY_CONFIG.challengeExpiryMinutes.

Tests:
- 33 new pure unit tests for conditionalPasskeyCeremony (predicates,
  classifiers, deriveRefreshIntervalMs, nextState reducer, driveCeremony
  loop, handleCeremonyResult, integrated runCeremony pipe with fake-
  timer refresh-abort path)
- 2 new tests asserting the cleanup-scope contract (conditional UI
  adds lt(expiresAt, now); email-scoped flow does not)
- Existing useConditionalPasskeyUI callback-ref stability tests pass
  unchanged
- Full lib test suite: 4028/4028 passing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Minor cleanup — the refreshTimer variable was being set to undefined in
the finally block after clearTimeout, but the variable is about to go
out of scope, so the assignment is noise.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The new conditionalPasskeyCeremony client module imported
PASSKEY_CHALLENGE_EXPIRY_MINUTES from '@pagespace/lib', which Next.js's
webpack resolves to the main server index (not the browser-conditional
client-safe entry), dragging the whole server tree into the client
bundle — google-auth-library then tried to pull node:fs/node:https/
node:buffer/child_process and the web#build step failed with
UnhandledSchemeError.

Switched the import to '@pagespace/lib/client-safe' explicitly, matching
the project convention used in all other client-side files. Constant is
re-exported from both entries so semantics are identical; only the
bundle path changes.

Verified locally with a full `pnpm --filter web build` — build now
succeeds. Targeted ceremony tests still pass (33/33).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only startAssertionWithRefreshTimer had its own catch block — a throw
from fetchAuthenticationOptions (e.g. network error, JSON parse error,
getDevicePlatformFields throwing) would propagate up to the hook's
useEffect as an unhandled rejection. The original hook's catch swallowed
non-abort errors with a console.debug, which this refactor had lost.

runCeremony now wraps the pipe in a try/catch that classifies the error
(distinguishing abort vs. ceremony-error) and preserves the original
console.debug behavior for non-abort failures, so anything thrown by a
step returns a terminal CeremonyResult instead of propagating.

Adds a test that a throwing getDevicePlatformFields is classified as
abort/ceremony-error and debug-logged once.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@2witstudios
2witstudios merged commit dfcdf2f into master Apr 13, 2026
12 checks passed
@2witstudios
2witstudios deleted the pu/passkey branch April 13, 2026 04:07
2witstudios added a commit that referenced this pull request Apr 13, 2026
…d output shape

Wave 1 of 3 in the SIEM dual-read effort. After PRs #894–#898 routed ~170
routes through audit()/auditRequest() into security_audit_log, those events
became invisible to the SIEM worker which only reads activity_logs. Rather
than dual-write at the route hot path, this PR lays the groundwork to
dual-read at the worker.

- Verify siem_delivery_cursors.id is unconstrained text (multi-source ready)
- Add SecurityAuditSiemRow + pure mapSecurityAuditToSiemEntry mapper that
  folds forensic context (sessionId, ipAddress, riskScore, anomalyFlags) into
  metadata while preserving the hash chain
- Unify AuditLogEntry with a `source` field; bump webhook payload to v1.1
  and add `source` SD-PARAM to syslog so receivers can distinguish origins
- Stamp existing activity_logs entries with source: 'activity_logs'

Worker refactor (Wave 2) is blocked on this merging. Plan at
~/.claude/plans/giggly-hopping-church.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2witstudios added a commit that referenced this pull request Apr 13, 2026
…d output shape (#899)

* feat(siem): dual-read foundation — security_audit_log mapper + unified output shape

Wave 1 of 3 in the SIEM dual-read effort. After PRs #894–#898 routed ~170
routes through audit()/auditRequest() into security_audit_log, those events
became invisible to the SIEM worker which only reads activity_logs. Rather
than dual-write at the route hot path, this PR lays the groundwork to
dual-read at the worker.

- Verify siem_delivery_cursors.id is unconstrained text (multi-source ready)
- Add SecurityAuditSiemRow + pure mapSecurityAuditToSiemEntry mapper that
  folds forensic context (sessionId, ipAddress, riskScore, anomalyFlags) into
  metadata while preserving the hash chain
- Unify AuditLogEntry with a `source` field; bump webhook payload to v1.1
  and add `source` SD-PARAM to syslog so receivers can distinguish origins
- Stamp existing activity_logs entries with source: 'activity_logs'

Worker refactor (Wave 2) is blocked on this merging. Plan at
~/.claude/plans/giggly-hopping-church.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(siem): address review feedback on Wave 1 foundation

- Document why mapSecurityAuditToSiemEntry hard-codes isAiGenerated: false
  (security_audit_log has no AI-attribution columns today).
- Drop the tautological "accepts arbitrary source identifiers" test — it
  tested JS property assignment, not the schema.
- Drop the columnType === 'PgText' assertion — Drizzle internal, fragile
  across minor bumps. dataType + enumValues already prove the same thing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

This branch was previously deployed

1 inactive deployment
Preview — 47146ffb Deployed Apr 13, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant