diff --git a/DECISIONS.md b/DECISIONS.md index fcc1ee95..38e28114 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -627,3 +627,4 @@ One line per decision: `[YYYY-MM-DD] : `. Appended by agents and [2026-07-27] support_feedback_submitted reports both channels (email + ticket) with a derived `lost` flag, and the ticket call runs concurrently with a 4s cap instead of being awaited after the email: both channels fail silently from the user's side, so "did the ticket open?" was previously only answerable by reproducing the submission with devtools open, and awaiting the ticket sequentially let a hung sendMessage hold the confirmation dialog open despite the code comment claiming it could not. ticket: 'unavailable' stays distinct from 'failed' and 'timeout' because unavailable is the expected steady state (Support off, analytics off, self-hosted) while the other two mean conversations were live and the call still did not land; only those deserve an alert. `lost` (neither channel worked) is the single property to alert on. Still carries no message body, pinned by a test. [2026-07-27] Supplier-invoice edits (#1230): block invoice_date / supplier_invoice_number once registration_journal_entry_id is set, rather than propagating the change into the verifikat via correct_entry_metadata: the friendlier propagate option turns a metadata PUT into a bookkeeping write (voucher rättelse, rattelse-log, period-lock checks) and needs a deliberate product call; blocking is the minimal legally correct behaviour and leaves due_date/payment_reference/notes editable for the aged-invoice flow (#1206). [2026-07-27] SKV 403 classification (#1155 item 1): the MuleSoft APIGW body "The required scopes are not authorized" is checked BEFORE the token-scope patterns and maps to ACCESS_DENIED (user mode) / SYSTEM_AUTH_FAILED with a subscription message (system mode). Substring matching on 'required scope' collided with it and produced MISSING_SCOPE, which is a RECONSENT code, so every reconnect re-flagged the token row. Token-scope detection is now a positive match on invalid_scope or SKV's documented "required scope has been requested" sentence, not a loose substring. +[2026-07-27] Docs export (#1247): scripts/export-docs-to-website.mts stubs `server-only` via a Module._load hook instead of untangling the import chain. The chain is real (lib/api/v1/load-routes -> every v1 route -> lib/init -> posthog-observability -> posthog-server) and the script only reads exported markdown builders, so breaking the chain would mean restructuring route imports for a build-time script's benefit. diff --git a/scripts/export-docs-to-website.mts b/scripts/export-docs-to-website.mts index b5abe7ee..d85b81e2 100644 --- a/scripts/export-docs-to-website.mts +++ b/scripts/export-docs-to-website.mts @@ -8,11 +8,36 @@ * changes. */ import { writeFileSync, mkdirSync } from 'node:fs' +import { createRequire } from 'node:module' import { dirname, resolve } from 'node:path' -const errors = await import('@/lib/docs/content/errors') -const reference = await import('@/lib/docs/content/reference') -const connectClaude = await import('@/lib/docs/content/connect-claude') +// `server-only` throws on import outside a Next.js server-component graph. The +// reference builder pulls it in transitively (lib/api/v1/load-routes -> every +// v1 route -> lib/init -> lib/analytics/posthog-observability -> +// posthog-server), which broke this script the moment PostHog landed. Nothing +// here executes request-time code: it only reads exported markdown builders, +// so a no-op stub is the honest resolution. +const require = createRequire(import.meta.url) +const ModuleCtor = require('node:module') as { + _load: (request: string, ...rest: unknown[]) => unknown +} +const originalLoad = ModuleCtor._load +ModuleCtor._load = function (request: string, ...rest: unknown[]) { + if (request === 'server-only') return {} + return originalLoad.call(this, request, ...rest) +} + +let errors, reference, connectClaude +try { + errors = await import('@/lib/docs/content/errors') + reference = await import('@/lib/docs/content/reference') + connectClaude = await import('@/lib/docs/content/connect-claude') +} finally { + // Scope the stub to the imports that need it: leaving a global loader hook + // patched for the rest of the process would silently disarm the guard for + // anything imported later (compliance swarm, ISO 27001 A.8.28). + ModuleCtor._load = originalLoad +} const buildErrorReferenceMd = errors.buildErrorReferenceMd ?? (errors as { default?: typeof errors }).default?.buildErrorReferenceMd const buildResourcePages = reference.buildResourcePages ?? (reference as { default?: typeof reference }).default?.buildResourcePages