/* RFC-1191→1198: lagebild.referral.issue / lagebild.referral.list — operator commands over the Lagebild referral-partner transport (POST/GET /v1/referrals). Issue registers a human-readable partner slug and returns the shareable /r/ link; list renders the registry rows. Reads LAGEBILD_API_URL/LAGEBILD_TENANT_ID from system-state and the LAGEBILD_API_KEY from the cache-clone .env — never logs the key. Do not store partner data locally — Lagebild owns the registry; this is a thin client. Do not log or echo the API key. The slug here registers the CRM-side partner identity only — the public /r/ page still requires the site-side partners.yaml entry (mission workpiece) per RFC-1198. RFC-1191: initial referral.issue / referral.list command handlers. RFC-1198: issue takes --slug/--name, returns /r/ link; opaque-code pattern replaced by REFERRAL_SLUG_PATTERN. RFC-1198: cookie-free referral attribution — /r/ partner landings + voluntary referrer field Replace the RFC-1191 wg_ref cookie capture with URL+form attribution: static /r/ and //r/ partner landings (noindex, zero JS, consent-gated via publicNameConsentRef), ?ref prefill on the existing referrerField rail, server-side slug resolution against the bundled partners.yaml, referral_partner_slug/name on the Lagebild ingress contract, legacy /?ref -> /r/ 302 in the generated worker, and full removal of the cookie capture module (wg_ref, referral.attribution entitlement, payload.attribution). */ import { join } from "node:path"; import type { KernelCommandInput, KernelCommandResult, KernelRuntimeContext, } from "@warpgogol/werkstatt-engine/kernel"; import { readSystemConfigSmart, readSystemStateSmart, resolveCacheClonePath, } from "../sternsystem/registry-io.ts"; import { sourceDotenv, filterEnv } from "../leitstand/adapters/index.ts"; import { REFERRAL_SLUG_PATTERN } from "@warpgogol/werkstatt-shared/referral"; import type { ChannelName } from "./lagebild-helpers.ts"; function flagString(input: KernelCommandInput, name: string): string | undefined { const v = input.flags[name]; return typeof v === "string" ? v : undefined; } interface ReferralCredentials { apiUrl: string; apiKey: string; tenantId: string; } async function resolveReferralCredentials( workspaceRoot: string, systemId: string, ): Promise { const state = await readSystemStateSmart(workspaceRoot, systemId); const lagebild = state.lagebild; if (!lagebild?.connected || !lagebild.apiUrl || !lagebild.tenantId) { throw new Error( `[lagebild.referral] Site '${systemId}' is not connected to Lagebild — run lagebild.connect first.`, ); } const envPath = join(resolveCacheClonePath(workspaceRoot, systemId), ".env"); const secretsEnv = (await sourceDotenv(envPath).catch(() => ({}))) as Record< string, string | undefined >; const env: Record = { ...filterEnv(process.env as Record), ...secretsEnv, }; const apiKey = env.LAGEBILD_API_KEY; if (!apiKey) { throw new Error("[lagebild.referral] LAGEBILD_API_KEY not found in .env or environment."); } return { apiUrl: lagebild.apiUrl, apiKey, tenantId: lagebild.tenantId }; } export interface ReferralIssueResult { site: string; slug: string; url: string; } export interface ReferralListResult { site: string; rows: unknown[]; } export async function runLagebildReferralIssue( input: KernelCommandInput, context: KernelRuntimeContext, ): Promise> { const systemId = flagString(input, "site"); if (!systemId) throw new Error("[lagebild.referral.issue] --site is required"); const slug = flagString(input, "slug"); if (!slug || !REFERRAL_SLUG_PATTERN.test(slug)) { throw new Error( "[lagebild.referral.issue] --slug is required and must match the referral slug pattern (kebab-case, 3–32 chars).", ); } const displayName = flagString(input, "name"); if (!displayName) throw new Error("[lagebild.referral.issue] --name is required"); const contact = flagString(input, "contact"); const channel = (flagString(input, "channel") ?? "main") as ChannelName; const { apiUrl, apiKey } = await resolveReferralCredentials(context.workspaceRoot, systemId); const response = await fetch(`${apiUrl.replace(/\/$/, "")}/v1/referrals`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, }, body: JSON.stringify({ slug, display_name: displayName, ...(contact ? { contact_email: contact } : {}), }), }); if (!response.ok) { throw new Error(`[lagebild.referral.issue] POST /v1/referrals failed: ${response.status}`); } const body = (await response.json().catch(() => ({}))) as { slug?: string }; if (!body.slug || !REFERRAL_SLUG_PATTERN.test(body.slug)) { throw new Error("[lagebild.referral.issue] POST /v1/referrals returned a missing/invalid slug"); } const config = await readSystemConfigSmart(context.workspaceRoot, systemId); const origin = config.deployment?.channels[channel]?.url?.replace(/\/$/, ""); if (!origin) { throw new Error( `[lagebild.referral.issue] No deployment.channels.${channel}.url in system-config.yaml.`, ); } return { data: { site: systemId, slug: body.slug, url: `${origin}/r/${body.slug}` } }; } export async function runLagebildReferralList( input: KernelCommandInput, context: KernelRuntimeContext, ): Promise> { const systemId = flagString(input, "site"); if (!systemId) throw new Error("[lagebild.referral.list] --site is required"); const { apiUrl, apiKey } = await resolveReferralCredentials(context.workspaceRoot, systemId); const response = await fetch(`${apiUrl.replace(/\/$/, "")}/v1/referrals`, { method: "GET", headers: { Authorization: `Bearer ${apiKey}` }, }); if (!response.ok) { throw new Error(`[lagebild.referral.list] GET /v1/referrals failed: ${response.status}`); } const body = (await response.json().catch(() => ({}))) as { rows?: unknown[] }; return { data: { site: systemId, rows: body.rows ?? [] } }; }