module.exports = [
"[externals]/crypto [external] (crypto, cjs)", ((__turbopack_context__, module, exports) => {

var mod = __turbopack_context__.x("crypto", () => require("crypto"));

module.exports = mod;
}),
"[externals]/next/dist/compiled/@opentelemetry/api [external] (next/dist/compiled/@opentelemetry/api, cjs)", ((__turbopack_context__, module, exports) => {

var mod = __turbopack_context__.x("next/dist/compiled/@opentelemetry/api", () => require("next/dist/compiled/@opentelemetry/api"));

module.exports = mod;
}),
"[externals]/next/dist/compiled/next-server/app-page-turbo.runtime.dev.js [external] (next/dist/compiled/next-server/app-page-turbo.runtime.dev.js, cjs)", ((__turbopack_context__, module, exports) => {

var mod = __turbopack_context__.x("next/dist/compiled/next-server/app-page-turbo.runtime.dev.js", () => require("next/dist/compiled/next-server/app-page-turbo.runtime.dev.js"));

module.exports = mod;
}),
"[externals]/next/dist/compiled/next-server/app-route-turbo.runtime.dev.js [external] (next/dist/compiled/next-server/app-route-turbo.runtime.dev.js, cjs)", ((__turbopack_context__, module, exports) => {

var mod = __turbopack_context__.x("next/dist/compiled/next-server/app-route-turbo.runtime.dev.js", () => require("next/dist/compiled/next-server/app-route-turbo.runtime.dev.js"));

module.exports = mod;
}),
"[externals]/next/dist/server/app-render/action-async-storage.external.js [external] (next/dist/server/app-render/action-async-storage.external.js, cjs)", ((__turbopack_context__, module, exports) => {

var mod = __turbopack_context__.x("next/dist/server/app-render/action-async-storage.external.js", () => require("next/dist/server/app-render/action-async-storage.external.js"));

module.exports = mod;
}),
"[externals]/next/dist/server/app-render/after-task-async-storage.external.js [external] (next/dist/server/app-render/after-task-async-storage.external.js, cjs)", ((__turbopack_context__, module, exports) => {

var mod = __turbopack_context__.x("next/dist/server/app-render/after-task-async-storage.external.js", () => require("next/dist/server/app-render/after-task-async-storage.external.js"));

module.exports = mod;
}),
"[externals]/next/dist/server/app-render/work-async-storage.external.js [external] (next/dist/server/app-render/work-async-storage.external.js, cjs)", ((__turbopack_context__, module, exports) => {

var mod = __turbopack_context__.x("next/dist/server/app-render/work-async-storage.external.js", () => require("next/dist/server/app-render/work-async-storage.external.js"));

module.exports = mod;
}),
"[externals]/next/dist/server/app-render/work-unit-async-storage.external.js [external] (next/dist/server/app-render/work-unit-async-storage.external.js, cjs)", ((__turbopack_context__, module, exports) => {

var mod = __turbopack_context__.x("next/dist/server/app-render/work-unit-async-storage.external.js", () => require("next/dist/server/app-render/work-unit-async-storage.external.js"));

module.exports = mod;
}),
"[externals]/next/dist/server/runtime-reacts.external.js [external] (next/dist/server/runtime-reacts.external.js, cjs)", ((__turbopack_context__, module, exports) => {

var mod = __turbopack_context__.x("next/dist/server/runtime-reacts.external.js", () => require("next/dist/server/runtime-reacts.external.js"));

module.exports = mod;
}),
"[externals]/next/dist/shared/lib/no-fallback-error.external.js [external] (next/dist/shared/lib/no-fallback-error.external.js, cjs)", ((__turbopack_context__, module, exports) => {

var mod = __turbopack_context__.x("next/dist/shared/lib/no-fallback-error.external.js", () => require("next/dist/shared/lib/no-fallback-error.external.js"));

module.exports = mod;
}),
"[externals]/node:stream [external] (node:stream, cjs)", ((__turbopack_context__, module, exports) => {

var mod = __turbopack_context__.x("node:stream", () => require("node:stream"));

module.exports = mod;
}),
"[project]/src/app/api/trahs/[trahId]/search/route.ts [app-route] (ecmascript)", ((__turbopack_context__) => {
"use strict";

return __turbopack_context__.a(async (__turbopack_handle_async_dependencies__, __turbopack_async_result__) => { try {
__turbopack_context__.s([
    "GET",
    ()=>GET
]);
// GET /api/trahs/[trahId]/search?q=[query] — calls genealogy's
// searchByName() with the caller's OWN membershipId (resolved server-side
// via getCallerMembership, never client-supplied) as the viewerMembershipId.
// See src/modules/genealogy/searchByName.ts.
//
// Unlike getProfile (T-507), searchByName has no public/unauthenticated-view
// case — it REQUIRES an ACTIVE Membership in trahId to run at all (per its
// own header: "requires viewerMembershipId to resolve to an ACTIVE
// Membership in trahId"). So a caller with no session/no ACTIVE Membership
// in THIS Trah never reaches searchByName (401), same discipline as every
// other route under src/app/api/trahs/[trahId]/**. searchByName's own
// FORBIDDEN branch (membership exists but isn't ACTIVE-in-this-trahId) maps
// to 403, though it's not expected to be reachable here in practice since
// getCallerMembership already enforces ACTIVE-in-this-trahId before calling
// it — kept for defense in depth, same pattern as tree/route.ts.
//
// `q` is required and must be non-empty after trimming — an empty query
// against `contains: ""` would match every Person in the Trah, which isn't
// a useful "search" result and is rejected as 400 rather than silently
// returning everyone.
var __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f$next$2f$server$2e$js__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/node_modules/next/server.js [app-route] (ecmascript)");
var __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$prisma$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/src/lib/prisma.ts [app-route] (ecmascript)");
var __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$modules$2f$genealogy$2f$searchByName$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/src/modules/genealogy/searchByName.ts [app-route] (ecmascript)");
var __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$get$2d$caller$2d$membership$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/src/lib/get-caller-membership.ts [app-route] (ecmascript)");
var __turbopack_async_dependencies__ = __turbopack_handle_async_dependencies__([
    __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$prisma$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__,
    __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$get$2d$caller$2d$membership$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__
]);
[__TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$prisma$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__, __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$get$2d$caller$2d$membership$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__] = __turbopack_async_dependencies__.then ? (await __turbopack_async_dependencies__)() : __turbopack_async_dependencies__;
;
;
;
;
async function GET(request, { params }) {
    const { trahId } = await params;
    const caller = await (0, __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$get$2d$caller$2d$membership$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["getCallerMembership"])(trahId);
    if (!caller) {
        return __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f$next$2f$server$2e$js__$5b$app$2d$route$5d$__$28$ecmascript$29$__["NextResponse"].json({
            error: "UNAUTHORIZED"
        }, {
            status: 401
        });
    }
    const url = new URL(request.url);
    const query = url.searchParams.get("q");
    if (!query || query.trim().length === 0) {
        return __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f$next$2f$server$2e$js__$5b$app$2d$route$5d$__$28$ecmascript$29$__["NextResponse"].json({
            error: "INVALID_INPUT"
        }, {
            status: 400
        });
    }
    const result = await (0, __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$modules$2f$genealogy$2f$searchByName$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["searchByName"])(__TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$prisma$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["prisma"], trahId, query.trim(), caller.membershipId);
    if (!result.ok) {
        return __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f$next$2f$server$2e$js__$5b$app$2d$route$5d$__$28$ecmascript$29$__["NextResponse"].json({
            error: result.error
        }, {
            status: 403
        });
    }
    return __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f$next$2f$server$2e$js__$5b$app$2d$route$5d$__$28$ecmascript$29$__["NextResponse"].json({
        results: result.results
    }, {
        status: 200
    });
}
__turbopack_async_result__();
} catch(e) { __turbopack_async_result__(e); } }, false);}),
"[project]/src/auth.ts [app-route] (ecmascript)", ((__turbopack_context__) => {
"use strict";

return __turbopack_context__.a(async (__turbopack_handle_async_dependencies__, __turbopack_async_result__) => { try {
__turbopack_context__.s([
    "auth",
    ()=>auth,
    "handlers",
    ()=>handlers,
    "signIn",
    ()=>signIn,
    "signOut",
    ()=>signOut
]);
// Auth.js (NextAuth v5) wiring for T-501.
//
// ADR-0004 calls for a Credentials provider with "database session
// strategy" backed by IAM's own users/sessions tables. Auth.js's own
// session-strategy option is more restrictive than that phrasing suggests:
// Auth.js explicitly REJECTS `session.strategy: "database"` when the only
// configured provider is Credentials (see
// node_modules/@auth/core/lib/utils/assert.js: "Signing in with
// credentials only supported if JWT strategy is enabled" /
// UnsupportedStrategy). Auth.js's built-in "database" strategy also
// assumes its own Adapter schema shape (Account/Session/User/
// VerificationToken with specific field names like `expires` rather than
// IAM's `expiresAt`), which doesn't match the schema IAM's contract
// functions already implement and fully own.
//
// Rather than fight that framework constraint or duplicate IAM's session
// logic behind a second, Adapter-shaped implementation, this config keeps
// Auth.js's *transport* (the signed cookie) on the JWT strategy, but
// treats IAM's own `sessionToken` (from login()/logout()/verifySession())
// as the actual source of truth for whether a session is valid:
//   - `authorize()` calls IAM's login(); on success, the sessionToken IAM
//     issued is embedded in the JWT payload (not a Auth.js-invented id).
//   - the `jwt` callback re-validates that sessionToken against the DB via
//     IAM's verifySession() on every subsequent request, so a session
//     revoked by logout() or confirmPasswordReset() (which deletes
//     Session rows) stops working immediately — genuine server-side
//     revocation, not just "valid until the JWT's own expiry."
//   - the `signOut` event calls IAM's logout() so the DB row is actually
//     removed, not just orphaned until it naturally expires.
//
// This satisfies the ADR's actual intent (revocable, DB-backed sessions
// reusing IAM's own tables/contract functions) within Auth.js's
// documented Credentials-provider constraint. Documented here since it's
// a deliberate deviation from the ADR's literal wording, not an oversight.
var __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f$next$2d$auth$2f$index$2e$js__$5b$app$2d$route$5d$__$28$ecmascript$29$__$3c$locals$3e$__ = __turbopack_context__.i("[project]/node_modules/next-auth/index.js [app-route] (ecmascript) <locals>");
var __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f$next$2d$auth$2f$providers$2f$credentials$2e$js__$5b$app$2d$route$5d$__$28$ecmascript$29$__$3c$locals$3e$__ = __turbopack_context__.i("[project]/node_modules/next-auth/providers/credentials.js [app-route] (ecmascript) <locals>");
var __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f40$auth$2f$core$2f$providers$2f$credentials$2e$js__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/node_modules/@auth/core/providers/credentials.js [app-route] (ecmascript)");
var __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$prisma$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/src/lib/prisma.ts [app-route] (ecmascript)");
var __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$modules$2f$iam$2f$login$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/src/modules/iam/login.ts [app-route] (ecmascript)");
var __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$modules$2f$iam$2f$logout$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/src/modules/iam/logout.ts [app-route] (ecmascript)");
var __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$modules$2f$iam$2f$verifySession$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/src/modules/iam/verifySession.ts [app-route] (ecmascript)");
var __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$rate$2d$limit$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/src/lib/rate-limit.ts [app-route] (ecmascript)");
var __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$client$2d$ip$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/src/lib/client-ip.ts [app-route] (ecmascript)");
var __turbopack_async_dependencies__ = __turbopack_handle_async_dependencies__([
    __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$prisma$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__
]);
[__TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$prisma$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__] = __turbopack_async_dependencies__.then ? (await __turbopack_async_dependencies__)() : __turbopack_async_dependencies__;
;
;
;
;
;
;
;
;
function parseIdentifier(raw) {
    if (typeof raw !== "string" || raw.trim().length === 0) return null;
    const type = raw.includes("@") ? "EMAIL" : "PHONE";
    return {
        type,
        value: raw
    };
}
// Security fix (T-501 review, HIGH): login() had zero throttling —
// this is the primary, most-targeted entry point for credential-stuffing/
// password-brute-force bots, and login.ts's own timing-parity defense
// only prevents user-enumeration, not unlimited online guessing. Same
// class of protection as reset/confirm's limiter (see rate-limit.ts's
// doc comment for the accepted single-instance/V1 scope), a more
// generous window since legitimate users mistype passwords far more
// often than they need 6-digit codes.
const LOGIN_LIMIT = 10;
const LOGIN_WINDOW_MS = 15 * 60 * 1000; // 15 minutes
const loginLimiter = new __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$rate$2d$limit$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["FixedWindowRateLimiter"](LOGIN_LIMIT, LOGIN_WINDOW_MS);
const { handlers, auth, signIn, signOut } = (0, __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f$next$2d$auth$2f$index$2e$js__$5b$app$2d$route$5d$__$28$ecmascript$29$__$3c$locals$3e$__["default"])({
    session: {
        strategy: "jwt"
    },
    pages: {
        signIn: "/login"
    },
    providers: [
        (0, __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f40$auth$2f$core$2f$providers$2f$credentials$2e$js__$5b$app$2d$route$5d$__$28$ecmascript$29$__["default"])({
            credentials: {
                identifier: {
                    label: "Email or phone",
                    type: "text"
                },
                password: {
                    label: "Password",
                    type: "password"
                }
            },
            async authorize (credentials, request) {
                const identifier = parseIdentifier(credentials?.identifier);
                const password = typeof credentials?.password === "string" ? credentials.password : "";
                if (!identifier || !password) return null;
                // Rate-limit BEFORE calling login(), so a throttled attempt never
                // even reaches bcrypt.compare — keyed by identifier+IP, same
                // spirit as reset/confirm's limiter (identifier alone would let
                // one attacker lock out a victim; IP alone would let a botnet
                // spread guesses across addresses).
                const rateLimitKey = `${identifier.type}:${identifier.value.trim().toLowerCase()}|${(0, __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$client$2d$ip$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["clientIp"])(request)}`;
                const { allowed } = loginLimiter.check(rateLimitKey);
                if (!allowed) return null;
                const result = await (0, __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$modules$2f$iam$2f$login$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["login"])(__TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$prisma$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["prisma"], identifier, password);
                if (!result.ok) return null;
                // Everything after `id` is a custom field carried through to the
                // `jwt` callback's `user` argument on initial sign-in only.
                return {
                    id: result.userId,
                    sessionToken: result.sessionToken
                };
            }
        })
    ],
    callbacks: {
        async jwt ({ token, user }) {
            if (user) {
                // Initial sign-in: `user` is what authorize() returned above.
                token.sessionToken = user.sessionToken;
                token.userId = user.id;
                return token;
            }
            // Subsequent requests: re-check IAM's own session store so that
            // logout()/confirmPasswordReset() (both of which delete Session
            // rows) take effect immediately, not just once the JWT expires.
            if (typeof token.sessionToken === "string") {
                const userId = await (0, __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$modules$2f$iam$2f$verifySession$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["verifySession"])(__TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$prisma$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["prisma"], token.sessionToken);
                if (!userId) {
                    // Session was revoked server-side — strip credentials so the
                    // `session` callback below reports "signed out."
                    delete token.sessionToken;
                    delete token.userId;
                }
            }
            return token;
        },
        async session ({ session, token }) {
            if (typeof token.userId === "string") {
                session.user.id = token.userId;
            } else {
                // No valid IAM session behind this JWT (revoked or never set) —
                // surface as unauthenticated rather than a half-populated user.
                return {
                    ...session,
                    user: undefined,
                    expires: session.expires
                };
            }
            return session;
        }
    },
    events: {
        async signOut (message) {
            // With the "jwt" strategy, Auth.js's signOut event passes `{ token }`
            // rather than `{ session }`. Clean up the underlying IAM Session row
            // so it doesn't linger until its natural expiry.
            if ("token" in message && typeof message.token?.sessionToken === "string") {
                await (0, __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$modules$2f$iam$2f$logout$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["logout"])(__TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$prisma$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["prisma"], message.token.sessionToken);
            }
        }
    }
});
__turbopack_async_result__();
} catch(e) { __turbopack_async_result__(e); } }, false);}),
"[project]/src/lib/client-ip.ts [app-route] (ecmascript)", ((__turbopack_context__) => {
"use strict";

// Shared client-IP extraction for rate-limiting purposes.
//
// Security fix (T-501 review, HIGH): a naive implementation that reads
// the FIRST entry of X-Forwarded-For is trivially spoofable — proxies
// (including Vercel) APPEND the real connecting client's address to the
// end of the list; the first entry is whatever the client itself sent in
// its own request and is fully attacker-controlled. Reading the LAST
// entry instead reflects the address the immediate, trusted proxy hop
// actually observed, which a client cannot forge (they can prepend fake
// entries, but not remove or rewrite the one their own connecting proxy
// appends).
//
// This is still not a perfect IP for a multi-hop/CDN setup (a compromised
// or misconfigured intermediate proxy could still lie), but it is not
// attacker-forgeable in the common single-reverse-proxy deployment this
// app targets (Vercel, per ADR-0003) the way "first entry" is.
__turbopack_context__.s([
    "clientIp",
    ()=>clientIp
]);
function clientIp(request) {
    const forwarded = request.headers.get("x-forwarded-for");
    if (!forwarded) return "unknown";
    const hops = forwarded.split(",").map((h)=>h.trim()).filter(Boolean);
    return hops.length > 0 ? hops[hops.length - 1] : "unknown";
}
}),
"[project]/src/lib/get-caller-membership.ts [app-route] (ecmascript)", ((__turbopack_context__) => {
"use strict";

return __turbopack_context__.a(async (__turbopack_handle_async_dependencies__, __turbopack_async_result__) => { try {
__turbopack_context__.s([
    "getCallerMembership",
    ()=>getCallerMembership,
    "getCallerUserId",
    ()=>getCallerUserId
]);
// T-502 shared helper — resolves "the current session's userId, for a
// given trahId, to their membershipId" so every Route Handler under
// src/app/api/trahs/** (and later tickets that need the same pattern)
// does this exactly once, the same way.
//
// SECURITY: tier-a's changeRole/removeMember/invite contract functions
// take an `actingMembershipId` as an *input*, trusting the caller to have
// already verified it belongs to the authenticated user (see
// getMembership.ts's SECURITY NOTE — that lookup does no authorization of
// its own). This helper is the one place that trust boundary is enforced:
// it never accepts a client-supplied membershipId as "the actor" — it
// always derives it server-side from Auth.js's session + the trahId in
// the URL, via findMembershipByUserAndTrah's INV-TM-5-backed compound-key
// lookup (same discipline as getRole.ts). Route handlers should use this
// for the ACTOR only; a client-supplied membershipId may still be used as
// the TARGET of an action (e.g. which member to remove/change).
var __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$auth$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/src/auth.ts [app-route] (ecmascript)");
var __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$prisma$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/src/lib/prisma.ts [app-route] (ecmascript)");
var __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$modules$2f$trah$2d$membership$2f$findMembershipByUserAndTrah$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/src/modules/trah-membership/findMembershipByUserAndTrah.ts [app-route] (ecmascript)");
var __turbopack_async_dependencies__ = __turbopack_handle_async_dependencies__([
    __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$auth$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__,
    __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$prisma$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__
]);
[__TURBOPACK__imported__module__$5b$project$5d2f$src$2f$auth$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__, __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$prisma$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__] = __turbopack_async_dependencies__.then ? (await __turbopack_async_dependencies__)() : __turbopack_async_dependencies__;
;
;
;
async function getCallerUserId() {
    const session = await (0, __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$auth$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["auth"])();
    const userId = session?.user?.id;
    return typeof userId === "string" ? userId : null;
}
async function getCallerMembership(trahId) {
    const userId = await getCallerUserId();
    if (!userId) return null;
    const membership = await (0, __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$modules$2f$trah$2d$membership$2f$findMembershipByUserAndTrah$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["findMembershipByUserAndTrah"])(__TURBOPACK__imported__module__$5b$project$5d2f$src$2f$lib$2f$prisma$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["prisma"], trahId, userId);
    if (!membership || membership.status !== "ACTIVE") return null;
    return {
        userId,
        membershipId: membership.id
    };
}
__turbopack_async_result__();
} catch(e) { __turbopack_async_result__(e); } }, false);}),
"[project]/src/lib/prisma.ts [app-route] (ecmascript)", ((__turbopack_context__) => {
"use strict";

return __turbopack_context__.a(async (__turbopack_handle_async_dependencies__, __turbopack_async_result__) => { try {
__turbopack_context__.s([
    "prisma",
    ()=>prisma
]);
// Standard Next.js + Prisma singleton pattern: in dev, Next.js hot-reloads
// modules on every file change, which would otherwise create a fresh
// PrismaClient (and a fresh DB connection pool) on every reload and
// eventually exhaust Postgres's connection limit. Stashing the instance on
// `globalThis` (which survives hot-reload, unlike module-local state)
// avoids that. In production there's only ever one module load, so this
// is a no-op there beyond the initial construction.
//
// Queries run through @prisma/adapter-pg (a plain-JS `pg` Pool) rather than
// Prisma's default Rust query engine — the Rust engine's Tokio runtime
// spawns one OS worker thread per reported CPU core, which exceeds the
// process/thread ceiling on constrained shared hosting and crashes with
// "OS can't spawn worker thread: Resource temporarily unavailable" on the
// very first request. The adapter has no native multi-threaded runtime.
var __TURBOPACK__imported__module__$5b$externals$5d2f40$prisma$2f$client__$5b$external$5d$__$2840$prisma$2f$client$2c$__cjs$2c$__$5b$project$5d2f$node_modules$2f40$prisma$2f$client$29$__ = __turbopack_context__.i("[externals]/@prisma/client [external] (@prisma/client, cjs, [project]/node_modules/@prisma/client)");
var __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f40$prisma$2f$adapter$2d$pg$2f$dist$2f$index$2e$mjs__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/node_modules/@prisma/adapter-pg/dist/index.mjs [app-route] (ecmascript)");
var __turbopack_async_dependencies__ = __turbopack_handle_async_dependencies__([
    __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f40$prisma$2f$adapter$2d$pg$2f$dist$2f$index$2e$mjs__$5b$app$2d$route$5d$__$28$ecmascript$29$__
]);
[__TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f40$prisma$2f$adapter$2d$pg$2f$dist$2f$index$2e$mjs__$5b$app$2d$route$5d$__$28$ecmascript$29$__] = __turbopack_async_dependencies__.then ? (await __turbopack_async_dependencies__)() : __turbopack_async_dependencies__;
;
;
const globalForPrisma = globalThis;
const adapter = new __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f40$prisma$2f$adapter$2d$pg$2f$dist$2f$index$2e$mjs__$5b$app$2d$route$5d$__$28$ecmascript$29$__["PrismaPg"]({
    connectionString: process.env.DATABASE_URL
});
const prisma = globalForPrisma.prisma ?? new __TURBOPACK__imported__module__$5b$externals$5d2f40$prisma$2f$client__$5b$external$5d$__$2840$prisma$2f$client$2c$__cjs$2c$__$5b$project$5d2f$node_modules$2f40$prisma$2f$client$29$__["PrismaClient"]({
    adapter
});
if ("TURBOPACK compile-time truthy", 1) {
    globalForPrisma.prisma = prisma;
}
__turbopack_async_result__();
} catch(e) { __turbopack_async_result__(e); } }, false);}),
"[project]/src/lib/rate-limit.ts [app-route] (ecmascript)", ((__turbopack_context__) => {
"use strict";

// Simple in-memory fixed-window rate limiter.
//
// Scope: V1-appropriate protection for a low-traffic single-instance app
// (per WORKPLAN.md's environment notes, this deploys to a small number of
// serverless instances, not a large fleet) — NOT a distributed limiter.
// It resets if the process restarts and does not coordinate across
// multiple server instances. That's an accepted tradeoff for V1: the
// spec's must-do is "have SOME rate limiting on reset/confirm, a handful
// of attempts per 15-minute window," not "build a production-grade
// distributed limiter." A Redis-backed limiter would be a natural
// follow-up if this app ever runs on more than one instance.
//
// Extracted as a pure class (no Next.js/Request coupling) so it's testable
// in isolation without spinning up a route handler.
__turbopack_context__.s([
    "FixedWindowRateLimiter",
    ()=>FixedWindowRateLimiter
]);
class FixedWindowRateLimiter {
    limit;
    windowMs;
    hits;
    constructor(limit, windowMs){
        this.limit = limit;
        this.windowMs = windowMs;
        this.hits = new Map();
    }
    /**
   * Records an attempt for `key` and reports whether it's allowed under the
   * limit. Pass `now` explicitly in tests to control window boundaries.
   */ check(key, now = Date.now()) {
        const existing = this.hits.get(key);
        if (!existing || now - existing.windowStart >= this.windowMs) {
            // New window (first attempt ever for this key, or the previous
            // window has fully elapsed).
            this.hits.set(key, {
                count: 1,
                windowStart: now
            });
            return {
                allowed: true,
                remaining: this.limit - 1
            };
        }
        if (existing.count >= this.limit) {
            return {
                allowed: false,
                remaining: 0
            };
        }
        existing.count += 1;
        return {
            allowed: true,
            remaining: this.limit - existing.count
        };
    }
    /** Test/diagnostic helper — not used by route handlers. */ reset() {
        this.hits.clear();
    }
}
}),
"[project]/src/modules/genealogy/searchByName.ts [app-route] (ecmascript)", ((__turbopack_context__) => {
"use strict";

// T-305 (standard lane) — Genealogy searchByName() contract function.
// See spec/genealogy/tier-a-contract.md:
//   searchByName(trahId, query, viewerMembershipId)
//     -> Result<PersonSummary[], "FORBIDDEN">
// and spec/genealogy/tier-b-implementation.md's "Search (REQ-SRCH-001)":
//   "requires viewerMembershipId to resolve to an ACTIVE Membership in
//    trahId (enforces REQ-FAM-005 — search never crosses a Trah boundary).
//    Query implementation: case-insensitive partial match (`ILIKE
//    '%query%'`...) against persons.display_name, filtered
//    `where trah_id = :trahId`."
//
// Contract-gap fix (same pattern as T-304's findRelationship fix): tier-a's
// signature for searchByName had no error branch (`-> PersonSummary[]`)
// even though tier-b's own prose states the ACTIVE-membership requirement
// in the same words used for findRelationship's (now-fixed) FORBIDDEN
// branch. Revised here to `Result<PersonSummary[], "FORBIDDEN">`, matching
// T-304's precedent exactly.
//
// Known gap inherited from T-301 (WORKPLAN.md "Known gaps before real
// users"): `Person.displayName` is not kept in sync with `person_facts`
// FULL_NAME rows (createPerson writes displayName directly with no backing
// fact row; updatePersonFact never writes back to displayName). Tier-b
// explicitly specifies searching `persons.display_name` (the denormalized
// column), which is what this function does — searching against the
// literal column named in tier-b is the correct implementation of THIS
// ticket's contract; the staleness risk that column carries is a pre-
// existing, already-tracked gap in createPerson/updatePersonFact, not
// something T-305 introduces or is in scope to fix.
__turbopack_context__.s([
    "searchByName",
    ()=>searchByName
]);
var __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$modules$2f$trah$2d$membership$2f$getMembership$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/src/modules/trah-membership/getMembership.ts [app-route] (ecmascript)");
;
async function searchByName(prisma, trahId, query, viewerMembershipId) {
    // Read-permission check: viewer must be an ACTIVE member of this trahId
    // (REQ-FAM-005 — search never crosses a Trah boundary).
    const membership = await (0, __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$modules$2f$trah$2d$membership$2f$getMembership$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["getMembership"])(prisma, viewerMembershipId);
    if (!membership || membership.trahId !== trahId || membership.status !== "ACTIVE") {
        return {
            ok: false,
            error: "FORBIDDEN"
        };
    }
    const persons = await prisma.person.findMany({
        where: {
            trahId,
            displayName: {
                contains: query,
                mode: "insensitive"
            }
        }
    });
    const results = persons.map((p)=>({
            id: p.id,
            displayName: p.displayName
        }));
    return {
        ok: true,
        results
    };
}
}),
"[project]/src/modules/iam/identifier.ts [app-route] (ecmascript)", ((__turbopack_context__) => {
"use strict";

// Shared identifier type + normalization for IAM's email-or-phone login
// scheme. Extracted so register.ts and login.ts can't silently drift
// (review follow-up from T-102).
__turbopack_context__.s([
    "normalizeIdentifier",
    ()=>normalizeIdentifier
]);
function normalizeIdentifier(identifier) {
    const trimmed = identifier.value.trim();
    return identifier.type === "EMAIL" ? trimmed.toLowerCase() : trimmed;
}
}),
"[project]/src/modules/iam/login.ts [app-route] (ecmascript)", ((__turbopack_context__) => {
"use strict";

__turbopack_context__.s([
    "login",
    ()=>login
]);
// T-102 — IAM login() contract function.
// See spec/iam/tier-a-contract.md (signature, INV-IAM-4) and
// spec/iam/tier-b-implementation.md (schema, session expiry, "same generic
// error for unknown identifier vs wrong password" edge case).
var __TURBOPACK__imported__module__$5b$externals$5d2f$crypto__$5b$external$5d$__$28$crypto$2c$__cjs$29$__ = __turbopack_context__.i("[externals]/crypto [external] (crypto, cjs)");
var __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f$bcryptjs$2f$index$2e$js__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/node_modules/bcryptjs/index.js [app-route] (ecmascript)");
var __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$modules$2f$iam$2f$identifier$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__ = __turbopack_context__.i("[project]/src/modules/iam/identifier.ts [app-route] (ecmascript)");
;
;
;
const SESSION_LIFETIME_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
const SESSION_TOKEN_BYTES = 32; // 32 bytes -> 64 hex chars, well over the 32-char floor
// Dummy hash to compare against when the identifier doesn't match a User,
// so a failed lookup still pays the bcrypt.compare cost — mitigates
// timing-based user enumeration (same code path either way).
const DUMMY_HASH_FOR_TIMING_PARITY = "$2a$10$CwTycUXWue0Thq9StjUM0uJ8i1Ax/OhpFrOTdmKGKQ8pC/S8v7B9m";
async function login(prisma, identifier, password) {
    const value = (0, __TURBOPACK__imported__module__$5b$project$5d2f$src$2f$modules$2f$iam$2f$identifier$2e$ts__$5b$app$2d$route$5d$__$28$ecmascript$29$__["normalizeIdentifier"])(identifier);
    // Same field-scoped lookup pattern as register.ts: email checked
    // against email, phone against phone — never a combined OR.
    const user = identifier.type === "EMAIL" ? await prisma.user.findFirst({
        where: {
            email: value
        }
    }) : await prisma.user.findFirst({
        where: {
            phone: value
        }
    });
    const passwordMatches = await __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f$bcryptjs$2f$index$2e$js__$5b$app$2d$route$5d$__$28$ecmascript$29$__["default"].compare(password, user?.passwordHash ?? DUMMY_HASH_FOR_TIMING_PARITY);
    if (!user || !passwordMatches) {
        return {
            ok: false,
            error: "INVALID_CREDENTIALS"
        };
    }
    const sessionToken = __TURBOPACK__imported__module__$5b$externals$5d2f$crypto__$5b$external$5d$__$28$crypto$2c$__cjs$29$__["default"].randomBytes(SESSION_TOKEN_BYTES).toString("hex");
    const expiresAt = new Date(Date.now() + SESSION_LIFETIME_MS);
    await prisma.session.create({
        data: {
            sessionToken,
            userId: user.id,
            expiresAt
        }
    });
    return {
        ok: true,
        sessionToken,
        userId: user.id
    };
}
}),
"[project]/src/modules/iam/logout.ts [app-route] (ecmascript)", ((__turbopack_context__) => {
"use strict";

// T-102 — IAM logout() contract function.
// See spec/iam/tier-a-contract.md (signature, INV-IAM-4).
__turbopack_context__.s([
    "logout",
    ()=>logout
]);
async function logout(prisma, sessionToken) {
    // Judgment call (Tier B doesn't specify delete vs. expire-flag): delete
    // the row — simplest reading that satisfies INV-IAM-4 (a subsequent
    // verifySession lookup finds nothing).
    await prisma.session.deleteMany({
        where: {
            sessionToken
        }
    });
}
}),
"[project]/src/modules/iam/verifySession.ts [app-route] (ecmascript)", ((__turbopack_context__) => {
"use strict";

// T-102 — IAM verifySession() contract function.
// See spec/iam/tier-a-contract.md (signature, INV-IAM-4) and
// spec/iam/tier-b-implementation.md (session expiry logic — expired
// sessions must be treated as invalid, same as nonexistent).
__turbopack_context__.s([
    "verifySession",
    ()=>verifySession
]);
async function verifySession(prisma, sessionToken) {
    const session = await prisma.session.findUnique({
        where: {
            sessionToken
        }
    });
    if (!session) return null;
    if (session.expiresAt.getTime() <= Date.now()) return null;
    return session.userId;
}
}),
"[project]/src/modules/trah-membership/findMembershipByUserAndTrah.ts [app-route] (ecmascript)", ((__turbopack_context__) => {
"use strict";

// T-201 (danger-list: auth-RBAC) — internal read helper, NOT a
// tier-a-contract.md function by this name. tier-a's `getMembership` is a
// lookup BY membershipId (see getMembership.ts); this helper resolves the
// (trahId, userId) pair callers actually have at request time (e.g. a
// route handler with a session userId and a Trah id from the URL) into a
// Membership row, INV-TM-5's one-per-(user,trah) key. Used internally by
// this module (and by getRole.ts) — not a cross-module contract call.
//
// See spec/trah-membership/tier-b-implementation.md (`memberships` schema,
// `@@unique([trahId, userId])`) and REQ-FAM-005 / INV-TM-6 (every read
// scoped by trahId AND userId, never userId alone).
__turbopack_context__.s([
    "findMembershipByUserAndTrah",
    ()=>findMembershipByUserAndTrah
]);
async function findMembershipByUserAndTrah(prisma, trahId, userId) {
    // INV-TM-5's `@@unique([trahId, userId])` compound key backs this
    // lookup — findUnique against the (unnamed, Prisma-default-named)
    // `trahId_userId` compound key.
    return prisma.membership.findUnique({
        where: {
            trahId_userId: {
                trahId,
                userId
            }
        }
    });
}
}),
"[project]/src/modules/trah-membership/getMembership.ts [app-route] (ecmascript)", ((__turbopack_context__) => {
"use strict";

// T-201 (danger-list: auth-RBAC) — getMembership() contract function.
// See spec/trah-membership/tier-a-contract.md:
//   getMembership(membershipId: MembershipId) -> MembershipView | null
//   MembershipView = { id, trahId, userId, role, claimedPersonId, status }
// Lookup BY membership id (not by trahId/userId — see
// findMembershipByUserAndTrah.ts for that shape).
//
// SECURITY NOTE (T-201 review, must read before adding any caller): this
// function performs NO authorization check of its own — it returns
// whatever membership matches the given id, including another Trah's
// data, regardless of who's asking. That's intentional per tier-a (a raw
// lookup primitive), but it means every future caller (e.g. Audit's
// getHistory, or any route handler) MUST independently verify the
// CALLING user's own membership/session is authorized for the target
// Trah before trusting this result. Never expose membershipId as a raw,
// unauthenticated route parameter that calls straight into this
// function — that would be a horizontal privilege-escalation bug
// (enumerate membershipId, read another Trah's userId/role/claimedPersonId).
__turbopack_context__.s([
    "getMembership",
    ()=>getMembership
]);
async function getMembership(prisma, membershipId) {
    const membership = await prisma.membership.findUnique({
        where: {
            id: membershipId
        }
    });
    if (!membership) return null;
    return {
        id: membership.id,
        trahId: membership.trahId,
        userId: membership.userId,
        role: membership.role,
        claimedPersonId: membership.claimedPersonId,
        status: membership.status
    };
}
}),
];

//# sourceMappingURL=%5Broot-of-the-server%5D__1jmu_cx._.js.map