@precisa-saude/fhir-rnds-sandbox 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/auth.ts","../src/jwks.ts","../src/keys.ts","../src/validation/helpers.ts","../src/validation/profiles.ts","../src/validation/index.ts","../src/router.ts","../src/scenarios/internacao.ts","../src/scenarios/paciente-com-exames.ts","../src/scenarios/vacina.ts","../src/scenarios/index.ts","../src/server.ts","../src/store.ts"],"sourcesContent":["/**\n * @precisa-saude/fhir-rnds-sandbox\n *\n * Mock local da RNDS para desenvolvimento, ensino e demos.\n * Sem certificado ICP-Brasil. Sem credenciamento DATASUS.\n */\n\nexport type {\n IssueTokenOptions,\n JwtClaims,\n TokenPayload,\n VerifyOptions,\n VerifyResult,\n} from './auth';\nexport { issueToken, verifyToken } from './auth';\nexport type { Jwk, JwksDocument } from './jwks';\nexport { buildJwks } from './jwks';\nexport type { KeyOptions, SigningKeys } from './keys';\nexport { resetSigningKeysCache, resolveSigningKeys } from './keys';\nexport type { PeerCertInfo, RouteContext, RouteResponse } from './router';\nexport { dispatch } from './router';\nexport { internacao, pacienteComExames, resolveScenario, SCENARIOS, vacina } from './scenarios';\nexport type { SandboxServer } from './server';\nexport { createSandboxServer } from './server';\nexport { SandboxStore } from './store';\nexport type {\n SandboxOptions,\n SandboxOrganization,\n SandboxPractitioner,\n Scenario,\n ScenarioData,\n ScenarioName,\n} from './types';\nexport type { IssueSeverity, ValidationIssue, ValidationResult } from './validation';\nexport {\n validateBRDiagnosticReport,\n validateBRLabObservation,\n validateBRPatient,\n validateBundle,\n validateResource,\n} from './validation';\n","/**\n * Emissor e verificador de tokens JWT (RS256) para o sandbox.\n *\n * A RNDS real assina seus tokens com RS256 e expõe a chave pública\n * para validação. O sandbox faz o mesmo: gera (ou carrega) um par\n * RSA-2048, assina com a privada e expõe a pública via JWKS em\n * `/.well-known/jwks.json`.\n *\n * Claims seguem o formato observado em respostas da RNDS:\n * `iss`, `sub`, `aud`, `iat`, `exp`, e — quando o cliente apresenta\n * certificado mTLS — `cn`/`cnes` extraídos do certificado.\n */\n\nimport { createSign, createVerify } from 'node:crypto';\n\nimport type { SigningKeys } from './keys';\n\nconst TOKEN_TTL_MS = 30 * 60 * 1000;\n\nexport interface TokenPayload {\n access_token: string;\n expires_in: number;\n scope: string;\n token_type: string;\n}\n\nexport interface JwtClaims {\n /** Audience — identifica quem deve consumir o token */\n aud: string;\n /** Common Name do certificado mTLS, se houver */\n cn?: string;\n /** CNES extraído do certificado mTLS apresentado, se houver */\n cnes?: string;\n /** Expiração (epoch seconds) */\n exp: number;\n /** Issued at (epoch seconds) */\n iat: number;\n /** Issuer */\n iss: string;\n /** Subject — identidade do cliente (CN ou 'sandbox') */\n sub: string;\n}\n\nexport interface IssueTokenOptions {\n /** Audience — padrão 'rnds-sandbox' */\n audience?: string;\n /** Identidade extraída do certificado mTLS, se aplicável */\n identity?: { cn?: string; cnes?: string };\n /** Issuer — padrão 'https://rnds-sandbox.local' */\n issuer?: string;\n /** Override de tempo (testes) */\n now?: number;\n}\n\nexport function issueToken(keys: SigningKeys, options: IssueTokenOptions = {}): TokenPayload {\n const now = options.now ?? Date.now();\n const audience = options.audience ?? 'rnds-sandbox';\n const issuer = options.issuer ?? 'https://rnds-sandbox.local';\n\n const claims: JwtClaims = {\n aud: audience,\n exp: Math.floor((now + TOKEN_TTL_MS) / 1000),\n iat: Math.floor(now / 1000),\n iss: issuer,\n sub: options.identity?.cn ?? 'sandbox',\n ...(options.identity?.cn ? { cn: options.identity.cn } : {}),\n ...(options.identity?.cnes ? { cnes: options.identity.cnes } : {}),\n };\n\n const accessToken = signJwt(claims, keys);\n\n return {\n access_token: accessToken,\n expires_in: Math.floor(TOKEN_TTL_MS / 1000),\n scope: 'rnds:read rnds:write',\n token_type: 'Bearer',\n };\n}\n\nexport type VerifyResult =\n | { valid: true; claims: JwtClaims }\n | {\n valid: false;\n reason: 'malformed' | 'bad-signature' | 'expired' | 'wrong-key' | 'wrong-audience';\n };\n\nexport interface VerifyOptions {\n /** Audience esperada — falha quando claim `aud` não casa. Padrão: 'rnds-sandbox' */\n audience?: string;\n /** Override de tempo para testes */\n now?: number;\n}\n\nexport function verifyToken(\n token: string,\n keys: SigningKeys,\n options: VerifyOptions = {},\n): VerifyResult {\n const now = options.now ?? Date.now();\n const expectedAudience = options.audience ?? 'rnds-sandbox';\n const parts = token.split('.');\n if (parts.length !== 3) {\n return { reason: 'malformed', valid: false };\n }\n const [headerB64, payloadB64, signatureB64] = parts as [string, string, string];\n\n let header: { alg?: string; kid?: string; typ?: string };\n let payload: JwtClaims;\n try {\n header = JSON.parse(Buffer.from(headerB64, 'base64url').toString('utf-8')) as typeof header;\n payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf-8')) as JwtClaims;\n } catch {\n return { reason: 'malformed', valid: false };\n }\n\n if (header.alg !== 'RS256') {\n return { reason: 'malformed', valid: false };\n }\n if (header.kid && header.kid !== keys.keyId) {\n return { reason: 'wrong-key', valid: false };\n }\n\n const signingInput = `${headerB64}.${payloadB64}`;\n const verifier = createVerify('RSA-SHA256').update(signingInput);\n const signatureValid = verifier.verify(keys.publicKey, Buffer.from(signatureB64, 'base64url'));\n if (!signatureValid) {\n return { reason: 'bad-signature', valid: false };\n }\n\n if (payload.exp * 1000 < now) {\n return { reason: 'expired', valid: false };\n }\n\n if (payload.aud !== expectedAudience) {\n return { reason: 'wrong-audience', valid: false };\n }\n\n return { claims: payload, valid: true };\n}\n\nfunction signJwt(claims: JwtClaims, keys: SigningKeys): string {\n const header = { alg: 'RS256', kid: keys.keyId, typ: 'JWT' };\n const headerB64 = base64UrlJson(header);\n const payloadB64 = base64UrlJson(claims);\n const signingInput = `${headerB64}.${payloadB64}`;\n const signer = createSign('RSA-SHA256').update(signingInput);\n const signature = signer.sign(keys.privateKey).toString('base64url');\n return `${signingInput}.${signature}`;\n}\n\nfunction base64UrlJson(value: unknown): string {\n return Buffer.from(JSON.stringify(value)).toString('base64url');\n}\n","/**\n * Geração do documento JWKS (`/.well-known/jwks.json`).\n *\n * Permite que clientes (incluindo jwt.io) busquem a chave pública do\n * sandbox e validem a assinatura do token sem nenhuma configuração\n * fora-de-banda.\n */\n\nimport type { SigningKeys } from './keys';\n\nexport interface Jwk {\n alg: 'RS256';\n e: string;\n kid: string;\n kty: 'RSA';\n n: string;\n use: 'sig';\n}\n\nexport interface JwksDocument {\n keys: Jwk[];\n}\n\nexport function buildJwks(keys: SigningKeys): JwksDocument {\n const exported = keys.publicKey.export({ format: 'jwk' });\n if (typeof exported !== 'object' || exported === null || exported.kty !== 'RSA') {\n throw new Error('Chave pública não é RSA — JWKS só suporta RS256 no sandbox');\n }\n if (typeof exported.n !== 'string' || typeof exported.e !== 'string') {\n throw new Error('Chave pública RSA exportada está incompleta (n/e ausentes)');\n }\n return {\n keys: [\n {\n alg: 'RS256',\n e: exported.e,\n kid: keys.keyId,\n kty: 'RSA',\n n: exported.n,\n use: 'sig',\n },\n ],\n };\n}\n","/**\n * Geração e carregamento de chaves RSA para assinatura RS256 do JWT.\n *\n * Em produção a RNDS usa chaves estáveis (com `kid` publicado em JWKS).\n * No sandbox, geramos um par RSA-2048 efêmero no boot por padrão; quem\n * precisa de tokens estáveis entre reinícios passa `--jwt-private-key`\n * (PEM) — útil em CI ou em demos onde o token é capturado e reutilizado.\n */\n\nimport {\n createPrivateKey,\n createPublicKey,\n generateKeyPairSync,\n type KeyObject,\n} from 'node:crypto';\nimport fs from 'node:fs';\n\nexport interface SigningKeys {\n /** Identificador da chave (claim `kid` no JWT, `kid` no JWKS) */\n keyId: string;\n /** Chave privada — usada para assinar */\n privateKey: KeyObject;\n /** Chave pública — exposta via JWKS */\n publicKey: KeyObject;\n}\n\nexport interface KeyOptions {\n /** Identificador da chave. Padrão: 'rnds-sandbox-1' */\n keyId?: string;\n /** PEM da chave privada (Buffer ou caminho). Se omitido, gera-se um par novo. */\n privateKey?: Buffer | string;\n /** PEM da chave pública (Buffer ou caminho). Opcional — derivada da privada quando omitida. */\n publicKey?: Buffer | string;\n}\n\nconst DEFAULT_KEY_ID = 'rnds-sandbox-1';\n\n// Singleton lazy do par RSA gerado — caro (~50ms) e não-determinístico.\n// Cacheamos pra que múltiplos `resolveSigningKeys()` sem opções (caso\n// típico de testes que rodam à parte) compartilhem o mesmo par e o\n// `kid`. Quem precisar de chaves distintas passa `keyId` ou `privateKey`\n// diferentes.\nlet cachedDefaultKeys: SigningKeys | undefined;\n\nexport function resolveSigningKeys(options: KeyOptions = {}): SigningKeys {\n const keyId = options.keyId ?? DEFAULT_KEY_ID;\n\n if (options.privateKey) {\n const privatePem = readPem(options.privateKey);\n const privateKey = createPrivateKey(privatePem);\n const publicKey = options.publicKey\n ? createPublicKey(readPem(options.publicKey))\n : createPublicKey(privateKey);\n return { keyId, privateKey, publicKey };\n }\n\n // Cache atinge somente o caminho default (sem PEM, com keyId padrão).\n // Qualquer override de keyId exige par novo (testes podem querer kids distintos).\n if (keyId === DEFAULT_KEY_ID && cachedDefaultKeys) {\n return cachedDefaultKeys;\n }\n\n const generated = generateKeyPairSync('rsa', { modulusLength: 2048 });\n const result: SigningKeys = {\n keyId,\n privateKey: generated.privateKey,\n publicKey: generated.publicKey,\n };\n if (keyId === DEFAULT_KEY_ID) {\n cachedDefaultKeys = result;\n }\n return result;\n}\n\n/** Limpa o cache do par default — útil em testes que precisam reset. */\nexport function resetSigningKeysCache(): void {\n cachedDefaultKeys = undefined;\n}\n\nfunction readPem(value: Buffer | string): Buffer | string {\n if (typeof value !== 'string') {\n return value;\n }\n if (value.includes('-----BEGIN')) {\n return value;\n }\n return fs.readFileSync(value);\n}\n","/**\n * Helpers compartilhados pelos validadores de perfil.\n *\n * Cada helper retorna `ValidationIssue[]` (vazio quando passa) para que\n * os validadores de perfil possam compor checagens com `[].concat(...)`.\n */\n\nimport type { IssueSeverity, ValidationIssue } from './types';\n\nexport function issue(\n severity: IssueSeverity,\n code: string,\n diagnostics: string,\n path: string,\n): ValidationIssue {\n // Apenas `expression` (FHIRPath) — `location` foi marcado deprecated\n // no R4 em favor de `expression`. Conteúdo idêntico em ambos só\n // poluiria a saída.\n return { code, diagnostics, expression: [path], severity };\n}\n\n/** Retorna issue `error/required` se `value` for null/undefined. */\nexport function requireField(value: unknown, label: string, path: string): ValidationIssue[] {\n if (value === undefined || value === null) {\n return [issue('error', 'required', `${label} é obrigatório`, path)];\n }\n return [];\n}\n\n/** Retorna issue se `value` não for um array com pelo menos `min` elementos. */\nexport function requireMinCardinality(\n value: unknown,\n min: number,\n label: string,\n path: string,\n): ValidationIssue[] {\n if (!Array.isArray(value) || value.length < min) {\n return [\n issue(\n 'error',\n 'required',\n `${label} requer pelo menos ${min} elemento(s); recebido ${\n Array.isArray(value) ? value.length : 0\n }`,\n path,\n ),\n ];\n }\n return [];\n}\n\n/** Retorna issue se `actual` não estiver no conjunto `allowed`. */\nexport function requireOneOf(\n actual: unknown,\n allowed: readonly string[],\n label: string,\n path: string,\n): ValidationIssue[] {\n if (typeof actual !== 'string' || !allowed.includes(actual)) {\n return [\n issue(\n 'error',\n 'invalid',\n `${label} deve ser um de [${allowed.join(', ')}]; recebido \"${String(actual)}\"`,\n path,\n ),\n ];\n }\n return [];\n}\n\n/** Retorna issue se a string não casar com o regex. */\nexport function requirePattern(\n value: unknown,\n pattern: RegExp,\n label: string,\n path: string,\n): ValidationIssue[] {\n if (typeof value !== 'string' || !pattern.test(value)) {\n return [\n issue(\n 'error',\n 'invalid',\n `${label} não obedece ao formato esperado (${pattern.source})`,\n path,\n ),\n ];\n }\n return [];\n}\n\n/**\n * Retorna o valor de `obj[key]` quando obj é um objeto não-null, ou\n * undefined caso contrário (sem lançar).\n */\nexport function get<T = unknown>(obj: unknown, key: string): T | undefined {\n if (obj && typeof obj === 'object') {\n return (obj as Record<string, unknown>)[key] as T | undefined;\n }\n return undefined;\n}\n","/**\n * Validadores de perfil para os recursos cobertos pelo IG do fhir-brasil\n * (BRPatient, BRLabObservation, BRDiagnosticReport).\n *\n * As regras espelham as restrições do FSH em `ig/input/fsh/profiles/`.\n * Não pretendem ser uma implementação completa de FHIR validation —\n * cobrem o que a RNDS real rejeita na prática para esses três perfis.\n */\n\nimport { get, issue, requireField, requireMinCardinality, requireOneOf } from './helpers';\nimport type { ValidationIssue } from './types';\n\n// ============================================================================\n// Constantes do IG (alinhadas com aliases.fsh / sushi-config.yaml)\n// ============================================================================\n\nconst NS_CPF = 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cpf';\nconst NS_CNS = 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cns';\nconst SYS_LOINC = 'http://loinc.org';\nconst SYS_UCUM = 'http://unitsofmeasure.org';\nconst SYS_OBS_CATEGORY = 'http://terminology.hl7.org/CodeSystem/observation-category';\nconst SYS_V2_0074 = 'http://terminology.hl7.org/CodeSystem/v2-0074';\n\nconst PATIENT_GENDERS = ['male', 'female', 'other', 'unknown'] as const;\nconst LAB_OBS_STATUS = ['final', 'amended', 'corrected'] as const;\nconst DIAG_REPORT_STATUS = ['final', 'amended', 'corrected'] as const;\n\n// ============================================================================\n// BRPatient\n// ============================================================================\n\nexport function validateBRPatient(resource: unknown, path: string): ValidationIssue[] {\n const issues: ValidationIssue[] = [];\n\n const identifiers = get<unknown[]>(resource, 'identifier');\n issues.push(...requireMinCardinality(identifiers, 1, 'Patient.identifier', `${path}.identifier`));\n\n if (Array.isArray(identifiers)) {\n const hasBrazilianId = identifiers.some((id) => {\n const sys = get<string>(id, 'system');\n return sys === NS_CPF || sys === NS_CNS;\n });\n if (!hasBrazilianId) {\n issues.push(\n issue(\n 'error',\n 'invariant',\n 'Patient deve ter pelo menos um identificador CPF ou CNS (br-patient-identifier)',\n `${path}.identifier`,\n ),\n );\n }\n }\n\n const names = get<unknown[]>(resource, 'name');\n issues.push(...requireMinCardinality(names, 1, 'Patient.name', `${path}.name`));\n\n issues.push(\n ...requireField(get(resource, 'birthDate'), 'Patient.birthDate', `${path}.birthDate`),\n );\n const gender = get(resource, 'gender');\n issues.push(...requireField(gender, 'Patient.gender', `${path}.gender`));\n if (gender !== undefined) {\n issues.push(...requireOneOf(gender, PATIENT_GENDERS, 'Patient.gender', `${path}.gender`));\n }\n\n return issues;\n}\n\n// ============================================================================\n// BRLabObservation\n// ============================================================================\n\nexport function validateBRLabObservation(resource: unknown, path: string): ValidationIssue[] {\n const issues: ValidationIssue[] = [];\n\n const status = get(resource, 'status');\n issues.push(...requireField(status, 'Observation.status', `${path}.status`));\n if (status !== undefined) {\n issues.push(...requireOneOf(status, LAB_OBS_STATUS, 'Observation.status', `${path}.status`));\n }\n\n const categories = get<unknown[]>(resource, 'category');\n issues.push(...requireMinCardinality(categories, 1, 'Observation.category', `${path}.category`));\n if (Array.isArray(categories)) {\n const hasLab = categories.some((cat) => {\n const codings = get<unknown[]>(cat, 'coding');\n return (\n Array.isArray(codings) &&\n codings.some(\n (c) =>\n get<string>(c, 'system') === SYS_OBS_CATEGORY &&\n get<string>(c, 'code') === 'laboratory',\n )\n );\n });\n if (!hasLab) {\n issues.push(\n issue(\n 'error',\n 'invalid',\n 'Observation.category deve incluir { system: observation-category, code: laboratory }',\n `${path}.category`,\n ),\n );\n }\n }\n\n const code = get(resource, 'code');\n issues.push(...requireField(code, 'Observation.code', `${path}.code`));\n if (code) {\n const codings = get<unknown[]>(code, 'coding');\n const hasLoinc =\n Array.isArray(codings) && codings.some((c) => get<string>(c, 'system') === SYS_LOINC);\n if (!hasLoinc) {\n issues.push(\n issue(\n 'error',\n 'required',\n 'Observation.code.coding deve incluir um coding com system http://loinc.org',\n `${path}.code.coding`,\n ),\n );\n }\n }\n\n const value = get(resource, 'valueQuantity');\n issues.push(...requireField(value, 'Observation.valueQuantity', `${path}.valueQuantity`));\n if (value) {\n issues.push(\n ...requireField(get(value, 'value'), 'valueQuantity.value', `${path}.valueQuantity.value`),\n );\n issues.push(\n ...requireField(get(value, 'unit'), 'valueQuantity.unit', `${path}.valueQuantity.unit`),\n );\n issues.push(\n ...requireField(get(value, 'code'), 'valueQuantity.code', `${path}.valueQuantity.code`),\n );\n // `system` é simultaneamente obrigatório (1..1) e fixo em UCUM. Tratamos\n // os dois casos explicitamente em vez de empilhar checagens — fica\n // claro que ausente vira `required` e errado vira `invalid`.\n const sys = get(value, 'system');\n if (sys === undefined) {\n issues.push(\n issue(\n 'error',\n 'required',\n 'valueQuantity.system é obrigatório',\n `${path}.valueQuantity.system`,\n ),\n );\n } else if (sys !== SYS_UCUM) {\n issues.push(\n issue(\n 'error',\n 'invalid',\n `valueQuantity.system deve ser ${SYS_UCUM}; recebido \"${String(sys)}\"`,\n `${path}.valueQuantity.system`,\n ),\n );\n }\n }\n\n issues.push(...requireField(get(resource, 'subject'), 'Observation.subject', `${path}.subject`));\n issues.push(\n ...requireField(\n get(resource, 'effectiveDateTime'),\n 'Observation.effectiveDateTime',\n `${path}.effectiveDateTime`,\n ),\n );\n\n return issues;\n}\n\n// ============================================================================\n// BRDiagnosticReport\n// ============================================================================\n\nexport function validateBRDiagnosticReport(resource: unknown, path: string): ValidationIssue[] {\n const issues: ValidationIssue[] = [];\n\n const status = get(resource, 'status');\n issues.push(...requireField(status, 'DiagnosticReport.status', `${path}.status`));\n if (status !== undefined) {\n issues.push(\n ...requireOneOf(status, DIAG_REPORT_STATUS, 'DiagnosticReport.status', `${path}.status`),\n );\n }\n\n const categories = get<unknown[]>(resource, 'category');\n issues.push(\n ...requireMinCardinality(categories, 1, 'DiagnosticReport.category', `${path}.category`),\n );\n if (Array.isArray(categories)) {\n const hasLab = categories.some((cat) => {\n const codings = get<unknown[]>(cat, 'coding');\n return (\n Array.isArray(codings) &&\n codings.some(\n (c) => get<string>(c, 'system') === SYS_V2_0074 && get<string>(c, 'code') === 'LAB',\n )\n );\n });\n if (!hasLab) {\n issues.push(\n issue(\n 'error',\n 'invalid',\n 'DiagnosticReport.category deve incluir { system: v2-0074, code: LAB }',\n `${path}.category`,\n ),\n );\n }\n }\n\n issues.push(...requireField(get(resource, 'code'), 'DiagnosticReport.code', `${path}.code`));\n issues.push(\n ...requireField(get(resource, 'subject'), 'DiagnosticReport.subject', `${path}.subject`),\n );\n issues.push(\n ...requireField(\n get(resource, 'effectiveDateTime'),\n 'DiagnosticReport.effectiveDateTime',\n `${path}.effectiveDateTime`,\n ),\n );\n const results = get<unknown[]>(resource, 'result');\n issues.push(...requireMinCardinality(results, 1, 'DiagnosticReport.result', `${path}.result`));\n const performers = get<unknown[]>(resource, 'performer');\n issues.push(\n ...requireMinCardinality(performers, 1, 'DiagnosticReport.performer', `${path}.performer`),\n );\n\n return issues;\n}\n","/**\n * Validação de Bundles FHIR contra os perfis do IG fhir-brasil.\n *\n * O sandbox roteia cada `Bundle.entry[i].resource` para o validador do\n * perfil correspondente baseado no `meta.profile` declarado OU no\n * `resourceType` quando ausente. Resultado é uma lista plana de\n * `ValidationIssue` com `expression`/`location` no formato FHIR.\n *\n * Não cobre o spec inteiro de FHIR validation — foca no subset que a\n * RNDS rejeita na prática (cardinalidade, identifiers brasileiros,\n * coding LOINC/UCUM, value sets de status).\n */\n\nimport {\n validateBRDiagnosticReport,\n validateBRLabObservation,\n validateBRPatient,\n} from './profiles';\nimport type { ValidationIssue, ValidationResult } from './types';\n\nconst PROFILE_URI = {\n diagnosticReport: 'https://fhir-brasil.dev.br/ig/StructureDefinition/br-diagnostic-report',\n labObservation: 'https://fhir-brasil.dev.br/ig/StructureDefinition/br-lab-observation',\n patient: 'https://fhir-brasil.dev.br/ig/StructureDefinition/br-patient',\n} as const;\n\ninterface ResourceLike {\n meta?: { profile?: string[] };\n resourceType?: string;\n}\n\ninterface BundleLike {\n entry?: Array<{ resource?: ResourceLike }>;\n resourceType?: string;\n}\n\nexport function validateBundle(bundle: unknown, basePath = 'Bundle'): ValidationResult {\n const issues: ValidationIssue[] = [];\n const b = bundle as BundleLike | undefined;\n const entries = b?.entry ?? [];\n\n entries.forEach((entry, index) => {\n const resource = entry?.resource;\n if (!resource) return;\n const entryPath = `${basePath}.entry[${index}].resource`;\n issues.push(...validateResource(resource, entryPath));\n });\n\n return { issues, valid: !issues.some((i) => i.severity === 'error' || i.severity === 'fatal') };\n}\n\nexport function validateResource(resource: ResourceLike, path: string): ValidationIssue[] {\n const declaredProfiles = resource?.meta?.profile ?? [];\n\n // Quando o cliente declara explicitamente um perfil do IG, validamos contra ele.\n if (declaredProfiles.includes(PROFILE_URI.patient)) {\n return validateBRPatient(resource, path);\n }\n if (declaredProfiles.includes(PROFILE_URI.labObservation)) {\n return validateBRLabObservation(resource, path);\n }\n if (declaredProfiles.includes(PROFILE_URI.diagnosticReport)) {\n return validateBRDiagnosticReport(resource, path);\n }\n\n // Sem `meta.profile`, fallback por resourceType para os tipos cobertos.\n switch (resource?.resourceType) {\n case 'Patient':\n return validateBRPatient(resource, path);\n case 'Observation':\n // BRLabObservation só se aplica a observações laboratoriais. Vital-signs,\n // social-history etc. usam outros perfis e seriam rejeitados injustamente\n // se forçados ao perfil BR.\n //\n // Heurística: aplicar BR apenas quando `category.coding` contém o código\n // `laboratory`. Caso contrário (categoria diferente, OU sem categoria\n // alguma — possível em dados legados), o recurso passa sem checagem de\n // perfil. Esse silêncio é deliberado: rejeitar Observations sem\n // category implicaria adivinhar a intenção do cliente.\n //\n // Para garantir conformidade estrita em ambos os casos, declare\n // `meta.profile` apontando para `br-lab-observation`.\n return looksLikeLabObservation(resource) ? validateBRLabObservation(resource, path) : [];\n case 'DiagnosticReport':\n return validateBRDiagnosticReport(resource, path);\n default:\n // Outros tipos passam sem validação de perfil — é responsabilidade do\n // chamador declarar `meta.profile` se quiser conformidade estrita.\n return [];\n }\n}\n\nconst SYS_OBS_CATEGORY = 'http://terminology.hl7.org/CodeSystem/observation-category';\n\nfunction looksLikeLabObservation(resource: unknown): boolean {\n const r = resource as { category?: { coding?: { system?: string; code?: string }[] }[] };\n const cats = r?.category;\n if (!Array.isArray(cats)) return false;\n return cats.some((cat) =>\n (cat?.coding ?? []).some((c) => c?.system === SYS_OBS_CATEGORY && c?.code === 'laboratory'),\n );\n}\n\nexport {\n validateBRDiagnosticReport,\n validateBRLabObservation,\n validateBRPatient,\n} from './profiles';\nexport type { IssueSeverity, ValidationIssue, ValidationResult } from './types';\n","/**\n * Roteador HTTP do sandbox.\n *\n * Mapeia o subset de endpoints da RNDS consumidos por\n * @precisa-saude/fhir-rnds. Aplica autenticação por rota:\n *\n * - /api/token: exige certificado mTLS quando o sandbox roda em modo\n * `--mtls` (RNDS de fato exige nesse endpoint).\n * - /api/fhir/r4/*: em modo `strict`, exige bearer token assinado +\n * header `Authorization: <CNS>` (igual à API real). Em modo\n * permissivo (padrão sem flags), aceita sem auth — útil para\n * curl / aulas sem fricção.\n * - /.well-known/jwks.json: público sempre.\n */\n\nimport { issueToken, verifyToken } from './auth';\nimport { buildJwks } from './jwks';\nimport type { SigningKeys } from './keys';\nimport type { SandboxStore } from './store';\nimport type { SandboxBundle, SandboxBundleEntry } from './types';\nimport { validateBundle } from './validation';\n\nconst FHIR_PREFIX = '/api/fhir/r4';\nconst TOKEN_PATH = '/api/token';\nconst JWKS_PATH = '/.well-known/jwks.json';\n\nconst NS = {\n cns: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cns',\n cpf: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cpf',\n} as const;\n\nexport interface PeerCertInfo {\n /** Common Name (subject.CN) do cert apresentado, se houver */\n cn?: string;\n /** CNES de 7 dígitos extraído do CN (heurística), se encontrado */\n cnes?: string;\n /** True quando o cliente apresentou um certificado válido (qualquer CA) */\n presented: boolean;\n}\n\nexport interface RouteContext {\n body?: unknown;\n /** Cabeçalhos HTTP recebidos (chaves em lowercase) */\n headers: Record<string, string | undefined>;\n keys: SigningKeys;\n method: string;\n path: string;\n /** Informação extraída do certificado mTLS, quando o servidor está em modo mtls */\n peerCert?: PeerCertInfo;\n query: URLSearchParams;\n store: SandboxStore;\n /** Quando true, /api/token requer cert apresentado e FHIR exige bearer + CNS */\n strict: boolean;\n /**\n * Quando true (padrão), `POST /Bundle` valida cada entry contra os perfis\n * BR* do IG fhir-brasil e rejeita com 422 se houver erros. Em modo\n * permissivo (`false`), aceita qualquer Bundle com estrutura mínima.\n */\n validateProfiles: boolean;\n}\n\nexport interface RouteResponse {\n body: unknown;\n status: number;\n}\n\nexport function dispatch(ctx: RouteContext): RouteResponse {\n if (ctx.method === 'GET' && ctx.path === JWKS_PATH) {\n return { body: buildJwks(ctx.keys), status: 200 };\n }\n\n if (ctx.method === 'POST' && ctx.path === TOKEN_PATH) {\n return handleToken(ctx);\n }\n\n if (!ctx.path.startsWith(FHIR_PREFIX)) {\n return notFound(`Recurso não encontrado: ${ctx.path}`);\n }\n\n if (ctx.strict) {\n const authError = enforceFhirAuth(ctx);\n if (authError) {\n return authError;\n }\n }\n\n const fhirPath = ctx.path.slice(FHIR_PREFIX.length);\n\n if (ctx.method === 'GET') {\n return handleFhirGet(fhirPath, ctx);\n }\n if (ctx.method === 'POST' && fhirPath === '/Bundle') {\n return handleSubmitBundle(ctx);\n }\n\n return {\n body: operationOutcome('error', 'not-supported', `${ctx.method} ${ctx.path} não suportado`),\n status: 405,\n };\n}\n\nfunction handleToken(ctx: RouteContext): RouteResponse {\n if (ctx.strict && !ctx.peerCert?.presented) {\n return {\n body: operationOutcome(\n 'error',\n 'security',\n 'Certificado mTLS é obrigatório em /api/token quando o sandbox está em modo --mtls',\n ),\n status: 401,\n };\n }\n const tokenPayload = issueToken(ctx.keys, {\n identity: ctx.peerCert?.presented\n ? { cn: ctx.peerCert.cn, cnes: ctx.peerCert.cnes }\n : undefined,\n });\n return { body: tokenPayload, status: 200 };\n}\n\nfunction enforceFhirAuth(ctx: RouteContext): RouteResponse | null {\n const bearerHeader = ctx.headers['x-authorization-server'];\n if (!bearerHeader) {\n return {\n body: operationOutcome(\n 'error',\n 'security',\n 'header X-Authorization-Server: Bearer <token> é obrigatório',\n ),\n status: 401,\n };\n }\n const match = /^Bearer\\s+(.+)$/i.exec(bearerHeader);\n if (!match) {\n return {\n body: operationOutcome(\n 'error',\n 'security',\n 'X-Authorization-Server deve usar o esquema \"Bearer <token>\"',\n ),\n status: 401,\n };\n }\n const token = match[1]!.trim();\n const verification = verifyToken(token, ctx.keys);\n if (!verification.valid) {\n return {\n body: operationOutcome('error', 'security', `Token inválido (${verification.reason})`),\n status: 401,\n };\n }\n\n const cnsHeader = ctx.headers['authorization'];\n if (!cnsHeader) {\n return {\n body: operationOutcome(\n 'error',\n 'required',\n 'header Authorization: <CNS-do-profissional> é obrigatório',\n ),\n status: 400,\n };\n }\n if (!/^\\d{15}$/.test(cnsHeader.trim())) {\n return {\n body: operationOutcome('error', 'invalid', 'Authorization deve conter um CNS de 15 dígitos'),\n status: 400,\n };\n }\n\n return null;\n}\n\n/**\n * Tipos cuja busca por paciente o sandbox suporta. Cada um aceita\n * `?subject=Patient/{cns}` ou `?patient=Patient/{cns}` (também tolera\n * só o CNS sem prefixo).\n */\nconst SEARCHABLE_BY_PATIENT = new Set([\n 'Observation',\n 'DiagnosticReport',\n 'Immunization',\n 'Condition',\n 'Encounter',\n]);\n\nfunction handleFhirGet(fhirPath: string, ctx: RouteContext): RouteResponse {\n if (fhirPath === '/Patient') {\n return handlePatientSearch(ctx);\n }\n const patientMatch = /^\\/Patient\\/([^/]+)$/.exec(fhirPath);\n if (patientMatch) {\n const cns = decodeURIComponent(patientMatch[1]!);\n const patient = ctx.store.findPatientByCns(cns);\n return patient\n ? { body: patient, status: 200 }\n : notFound(`Paciente CNS ${cns} não encontrado`);\n }\n const orgMatch = /^\\/Organization\\/([^/]+)$/.exec(fhirPath);\n if (orgMatch) {\n const cnes = decodeURIComponent(orgMatch[1]!);\n const org = ctx.store.findOrganizationByCnes(cnes);\n return org ? { body: org, status: 200 } : notFound(`Organização CNES ${cnes} não encontrada`);\n }\n const practMatch = /^\\/Practitioner\\/([^/]+)$/.exec(fhirPath);\n if (practMatch) {\n const cns = decodeURIComponent(practMatch[1]!);\n const pract = ctx.store.findPractitionerByCns(cns);\n return pract\n ? { body: pract, status: 200 }\n : notFound(`Profissional CNS ${cns} não encontrado`);\n }\n const searchMatch = /^\\/([A-Z][A-Za-z]+)$/.exec(fhirPath);\n if (searchMatch) {\n const resourceType = searchMatch[1]!;\n if (SEARCHABLE_BY_PATIENT.has(resourceType)) {\n return handleResourceSearch(resourceType, ctx);\n }\n }\n return notFound(`Recurso FHIR não suportado: ${fhirPath}`);\n}\n\nfunction handleResourceSearch(resourceType: string, ctx: RouteContext): RouteResponse {\n const ref = ctx.query.get('subject') ?? ctx.query.get('patient');\n if (!ref) {\n return {\n body: operationOutcome(\n 'error',\n 'required',\n `${resourceType} search exige ?subject=Patient/{cns} ou ?patient=Patient/{cns}`,\n ),\n status: 400,\n };\n }\n const cns = ref.startsWith('Patient/') ? ref.slice('Patient/'.length) : ref;\n const matches = ctx.store.searchResourcesByPatient(resourceType, cns);\n const entry: SandboxBundleEntry[] = matches.map((resource) => ({ resource }));\n return {\n body: {\n entry,\n resourceType: 'Bundle',\n total: matches.length,\n type: 'searchset',\n },\n status: 200,\n };\n}\n\nfunction handlePatientSearch(ctx: RouteContext): RouteResponse {\n const identifier = ctx.query.get('identifier');\n if (!identifier) {\n return {\n body: operationOutcome('error', 'required', 'parâmetro identifier é obrigatório em /Patient'),\n status: 400,\n };\n }\n const [system, value] = identifier.split('|');\n if (!system || !value) {\n return {\n body: operationOutcome('error', 'invalid', 'identifier deve ser no formato system|value'),\n status: 400,\n };\n }\n\n let patient = undefined;\n if (system === NS.cpf) {\n patient = ctx.store.findPatientByCpf(value);\n } else if (system === NS.cns) {\n patient = ctx.store.findPatientByCns(value);\n } else {\n return notFound(`NamingSystem não suportado: ${system}`);\n }\n\n return patient\n ? { body: patient, status: 200 }\n : notFound(`Paciente ${system}|${value} não encontrado`);\n}\n\nfunction handleSubmitBundle(ctx: RouteContext): RouteResponse {\n if (typeof ctx.body === 'symbol') {\n // sentinela INVALID_BODY de readBody — JSON malformado\n return {\n body: operationOutcome('error', 'structure', 'corpo da requisição não é JSON válido'),\n status: 400,\n };\n }\n const bundle = ctx.body as SandboxBundle | undefined;\n if (!bundle || typeof bundle !== 'object' || bundle.resourceType !== 'Bundle') {\n return {\n body: operationOutcome('error', 'structure', 'corpo da requisição não é um Bundle FHIR'),\n status: 400,\n };\n }\n if (bundle.type !== 'transaction' && bundle.type !== 'batch') {\n return {\n body: operationOutcome(\n 'error',\n 'invalid',\n `Bundle.type \"${String(bundle.type)}\" não suportado — use transaction ou batch`,\n ),\n status: 400,\n };\n }\n\n // Valida cada entry contra os perfis BR* do IG. Erros disparam 422\n // (FHIR-canônico para validation failure) com OperationOutcome\n // multi-issue, igual à RNDS real.\n if (ctx.validateProfiles) {\n const result = validateBundle(bundle);\n if (!result.valid) {\n return {\n body: {\n issue: result.issues.map((i) => ({\n code: i.code,\n diagnostics: i.diagnostics,\n expression: i.expression,\n severity: i.severity,\n })),\n resourceType: 'OperationOutcome',\n },\n status: 422,\n };\n }\n }\n\n ctx.store.recordBundle(bundle);\n\n // As `location`s retornadas são puramente sintéticas — servem só para\n // imitar o formato de transaction-response da RNDS. O sandbox não\n // resolve esses IDs de volta; os recursos submetidos são consultados\n // via `GET /{ResourceType}?subject=Patient/{cns}` (ou similar).\n const responseEntries: SandboxBundleEntry[] = (bundle.entry ?? []).map(() => ({\n response: {\n location: `Resource/sandbox-${ctx.store.nextResourceId()}`,\n status: '201 Created',\n },\n }));\n const response: SandboxBundle = {\n entry: responseEntries,\n resourceType: 'Bundle',\n type: 'transaction-response',\n };\n return { body: response, status: 200 };\n}\n\nfunction notFound(diagnostics: string): RouteResponse {\n return {\n body: operationOutcome('error', 'not-found', diagnostics),\n status: 404,\n };\n}\n\nfunction operationOutcome(\n severity: 'fatal' | 'error' | 'warning' | 'information',\n code: string,\n diagnostics: string,\n): unknown {\n return {\n issue: [{ code, diagnostics, severity }],\n resourceType: 'OperationOutcome',\n };\n}\n","/**\n * Cenário: paciente com episódio de internação recente.\n *\n * Carlos Henrique Souza, 67 anos, internado por descompensação de\n * insuficiência cardíaca. Bundle pré-submetido contém Encounter\n * (admissão) + Observations (BNP, troponina) + Condition (CID I50.0).\n */\n\nimport type { Scenario } from '../types';\n\nconst CPF = '98765432100';\nconst CNS = '700000000000020';\nconst CNES_HOSPITAL = '3456789';\nconst CNS_PROFISSIONAL = '700000000000030';\n\nexport const internacao: Scenario = {\n data: {\n organizations: [\n {\n active: true,\n id: CNES_HOSPITAL,\n identifier: [\n { system: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cnes', value: CNES_HOSPITAL },\n ],\n name: 'Hospital Sintético do Coração (sandbox)',\n resourceType: 'Organization',\n },\n ],\n patients: [\n {\n birthDate: '1958-02-28',\n gender: 'male',\n id: CNS,\n identifier: [\n { system: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cpf', value: CPF },\n { system: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cns', value: CNS },\n ],\n name: [{ family: 'Souza', given: ['Carlos', 'Henrique'] }],\n resourceType: 'Patient',\n },\n ],\n practitioners: [\n {\n active: true,\n id: CNS_PROFISSIONAL,\n identifier: [\n {\n system: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cns',\n value: CNS_PROFISSIONAL,\n },\n ],\n name: [{ family: 'Mendes', given: ['Roberto'] }],\n resourceType: 'Practitioner',\n },\n ],\n submittedBundles: [\n {\n entry: [\n {\n request: { method: 'POST', url: 'Encounter' },\n resource: {\n class: { code: 'IMP', display: 'inpatient encounter' },\n period: { start: '2026-04-15T10:30:00-03:00' },\n resourceType: 'Encounter',\n serviceProvider: { reference: `Organization/${CNES_HOSPITAL}` },\n status: 'in-progress',\n subject: { reference: `Patient/${CNS}` },\n },\n },\n {\n request: { method: 'POST', url: 'Condition' },\n resource: {\n code: {\n coding: [\n {\n code: 'I50.0',\n display: 'Insuficiência cardíaca congestiva',\n system: 'http://hl7.org/fhir/sid/icd-10',\n },\n ],\n },\n recordedDate: '2026-04-15',\n resourceType: 'Condition',\n subject: { reference: `Patient/${CNS}` },\n },\n },\n {\n request: { method: 'POST', url: 'Observation' },\n resource: {\n code: {\n coding: [{ code: '33762-6', display: 'NT-proBNP', system: 'http://loinc.org' }],\n },\n effectiveDateTime: '2026-04-15T11:15:00-03:00',\n resourceType: 'Observation',\n status: 'final',\n subject: { reference: `Patient/${CNS}` },\n valueQuantity: {\n code: 'pg/mL',\n system: 'http://unitsofmeasure.org',\n unit: 'pg/mL',\n value: 4820,\n },\n },\n },\n {\n request: { method: 'POST', url: 'Observation' },\n resource: {\n code: {\n coding: [{ code: '49563-0', display: 'Troponina I', system: 'http://loinc.org' }],\n },\n effectiveDateTime: '2026-04-15T11:15:00-03:00',\n resourceType: 'Observation',\n status: 'final',\n subject: { reference: `Patient/${CNS}` },\n valueQuantity: {\n code: 'ng/mL',\n system: 'http://unitsofmeasure.org',\n unit: 'ng/mL',\n value: 0.08,\n },\n },\n },\n ],\n resourceType: 'Bundle',\n type: 'transaction',\n },\n ],\n },\n description:\n 'Carlos Henrique Souza (CPF 987.654.321-00, CNS 700 0000 0000 0020), 67 anos, internado por insuficiência cardíaca descompensada (CID I50.0). Hospital CNES 3456789.',\n name: 'internacao',\n};\n","/**\n * Cenário: paciente com histórico de exames laboratoriais.\n *\n * Joana Maria da Silva, 42 anos, com Bundle pré-submetido de\n * lipidograma, glicemia e hemograma. Útil para demonstrar\n * o fluxo \"buscar paciente → consultar exames anteriores → submeter novos\".\n *\n * IDs sintéticos — não correspondem a CPF/CNS reais.\n */\n\nimport type { Scenario } from '../types';\n\nconst CPF = '12345678901';\nconst CNS = '700000000000001';\nconst CNES_LAB = '2345678';\nconst CNS_PROFISSIONAL = '700000000000010';\n\nexport const pacienteComExames: Scenario = {\n data: {\n organizations: [\n {\n active: true,\n id: CNES_LAB,\n identifier: [\n {\n system: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cnes',\n value: CNES_LAB,\n },\n ],\n name: 'Laboratório Sintético São Paulo (sandbox)',\n resourceType: 'Organization',\n },\n ],\n patients: [\n {\n birthDate: '1983-08-12',\n gender: 'female',\n id: CNS,\n identifier: [\n { system: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cpf', value: CPF },\n { system: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cns', value: CNS },\n ],\n name: [{ family: 'Silva', given: ['Joana', 'Maria', 'da'] }],\n resourceType: 'Patient',\n },\n ],\n practitioners: [\n {\n active: true,\n id: CNS_PROFISSIONAL,\n identifier: [\n {\n system: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cns',\n value: CNS_PROFISSIONAL,\n },\n ],\n name: [{ family: 'Pereira', given: ['Ana', 'Carolina'] }],\n resourceType: 'Practitioner',\n },\n ],\n submittedBundles: [\n {\n entry: [\n {\n request: { method: 'POST', url: 'Observation' },\n resource: {\n code: {\n coding: [\n {\n code: '2093-3',\n display: 'Colesterol total',\n system: 'http://loinc.org',\n },\n ],\n },\n effectiveDateTime: '2025-09-10',\n resourceType: 'Observation',\n status: 'final',\n subject: { reference: `Patient/${CNS}` },\n valueQuantity: {\n code: 'mg/dL',\n system: 'http://unitsofmeasure.org',\n unit: 'mg/dL',\n value: 198,\n },\n },\n },\n {\n request: { method: 'POST', url: 'Observation' },\n resource: {\n code: {\n coding: [{ code: '2089-1', display: 'LDL-Colesterol', system: 'http://loinc.org' }],\n },\n effectiveDateTime: '2025-09-10',\n resourceType: 'Observation',\n status: 'final',\n subject: { reference: `Patient/${CNS}` },\n valueQuantity: {\n code: 'mg/dL',\n system: 'http://unitsofmeasure.org',\n unit: 'mg/dL',\n value: 124,\n },\n },\n },\n {\n request: { method: 'POST', url: 'Observation' },\n resource: {\n code: {\n coding: [{ code: '2085-9', display: 'HDL-Colesterol', system: 'http://loinc.org' }],\n },\n effectiveDateTime: '2025-09-10',\n resourceType: 'Observation',\n status: 'final',\n subject: { reference: `Patient/${CNS}` },\n valueQuantity: {\n code: 'mg/dL',\n system: 'http://unitsofmeasure.org',\n unit: 'mg/dL',\n value: 56,\n },\n },\n },\n {\n request: { method: 'POST', url: 'Observation' },\n resource: {\n code: {\n coding: [{ code: '2345-7', display: 'Glicose', system: 'http://loinc.org' }],\n },\n effectiveDateTime: '2025-09-10',\n resourceType: 'Observation',\n status: 'final',\n subject: { reference: `Patient/${CNS}` },\n valueQuantity: {\n code: 'mg/dL',\n system: 'http://unitsofmeasure.org',\n unit: 'mg/dL',\n value: 96,\n },\n },\n },\n ],\n resourceType: 'Bundle',\n type: 'transaction',\n },\n ],\n },\n description:\n 'Joana Maria da Silva (CPF 123.456.789-01, CNS 700 0000 0000 0001), 42 anos, com histórico de lipidograma e glicemia. Laboratório CNES 2345678. Profissional Dr(a). Pereira (CNS 700 0000 0000 0010).',\n name: 'paciente-com-exames',\n};\n","/**\n * Cenário: paciente com calendário vacinal aplicado.\n *\n * Pedro Lima Almeida, 8 anos, com Bundle pré-submetido contendo\n * Immunization (BCG, hepatite B, pentavalente, tríplice viral).\n */\n\nimport type { Scenario } from '../types';\n\nconst CNS = '700000000000040';\nconst CNES_UBS = '4567890';\nconst CNS_PROFISSIONAL = '700000000000050';\n\nexport const vacina: Scenario = {\n data: {\n organizations: [\n {\n active: true,\n id: CNES_UBS,\n identifier: [\n { system: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cnes', value: CNES_UBS },\n ],\n name: 'UBS Sintética Vila Saúde (sandbox)',\n resourceType: 'Organization',\n },\n ],\n patients: [\n {\n birthDate: '2017-11-04',\n gender: 'male',\n id: CNS,\n identifier: [{ system: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cns', value: CNS }],\n name: [{ family: 'Almeida', given: ['Pedro', 'Lima'] }],\n resourceType: 'Patient',\n },\n ],\n practitioners: [\n {\n active: true,\n id: CNS_PROFISSIONAL,\n identifier: [\n {\n system: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cns',\n value: CNS_PROFISSIONAL,\n },\n ],\n name: [{ family: 'Costa', given: ['Fernanda'] }],\n resourceType: 'Practitioner',\n },\n ],\n submittedBundles: [\n {\n entry: [\n {\n request: { method: 'POST', url: 'Immunization' },\n resource: {\n occurrenceDateTime: '2017-11-05',\n patient: { reference: `Patient/${CNS}` },\n resourceType: 'Immunization',\n status: 'completed',\n vaccineCode: {\n coding: [{ code: 'BCG', display: 'BCG', system: 'urn:oid:2.16.840.1.113883.6.59' }],\n },\n },\n },\n {\n request: { method: 'POST', url: 'Immunization' },\n resource: {\n occurrenceDateTime: '2017-11-05',\n patient: { reference: `Patient/${CNS}` },\n resourceType: 'Immunization',\n status: 'completed',\n vaccineCode: {\n coding: [\n {\n code: 'HBV',\n display: 'Hepatite B (RN)',\n system: 'urn:oid:2.16.840.1.113883.6.59',\n },\n ],\n },\n },\n },\n {\n request: { method: 'POST', url: 'Immunization' },\n resource: {\n occurrenceDateTime: '2018-12-12',\n patient: { reference: `Patient/${CNS}` },\n resourceType: 'Immunization',\n status: 'completed',\n vaccineCode: {\n coding: [\n {\n code: 'MMR',\n display: 'Tríplice viral (SCR)',\n system: 'urn:oid:2.16.840.1.113883.6.59',\n },\n ],\n },\n },\n },\n ],\n resourceType: 'Bundle',\n type: 'transaction',\n },\n ],\n },\n description:\n 'Pedro Lima Almeida (CNS 700 0000 0000 0040), 8 anos, com calendário vacinal aplicado em UBS CNES 4567890.',\n name: 'vacina',\n};\n","/**\n * Registro de cenários disponíveis no sandbox.\n */\n\nimport type { Scenario, ScenarioName } from '../types';\nimport { internacao } from './internacao';\nimport { pacienteComExames } from './paciente-com-exames';\nimport { vacina } from './vacina';\n\nexport const SCENARIOS: Record<ScenarioName, Scenario> = {\n internacao,\n 'paciente-com-exames': pacienteComExames,\n vacina,\n vazio: {\n data: { organizations: [], patients: [], practitioners: [] },\n description: 'Estado limpo. Útil para testar fluxos do zero.',\n name: 'vazio',\n },\n};\n\nexport function resolveScenario(name: ScenarioName | undefined): Scenario {\n return SCENARIOS[name ?? 'paciente-com-exames'];\n}\n\nexport { internacao, pacienteComExames, vacina };\n","/**\n * Servidor HTTP(S) do sandbox.\n *\n * Cria um http.Server (sem mTLS) ou https.Server (com mTLS opcional)\n * que despacha requisições para o roteador. Em modo mTLS, o cert do\n * cliente apresentado no handshake é repassado ao roteador para que\n * /api/token possa exigir + extrair identidade dele.\n */\n\nimport fs from 'node:fs';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport http from 'node:http';\nimport https from 'node:https';\nimport type { TLSSocket } from 'node:tls';\n\nimport { resolveSigningKeys, type SigningKeys } from './keys';\nimport { dispatch, type PeerCertInfo, type RouteContext } from './router';\nimport { resolveScenario } from './scenarios';\nimport { SandboxStore } from './store';\nimport type { SandboxOptions } from './types';\n\nexport interface SandboxServer {\n /** Servidor HTTP(S) subjacente. */\n server: http.Server | https.Server;\n /** Chaves RSA usadas para assinar/validar tokens. */\n signingKeys: SigningKeys;\n /** Inicia o servidor. Retorna quando estiver escutando. */\n start: () => Promise<{ host: string; port: number }>;\n /** Encerra o servidor. */\n stop: () => Promise<void>;\n /** Store em memória — útil para inspeção em testes. */\n store: SandboxStore;\n}\n\nexport function createSandboxServer(options: SandboxOptions = {}): SandboxServer {\n // eslint-disable-next-line no-console\n const log = options.log ?? ((msg: string) => console.log(`[rnds-sandbox] ${msg}`));\n const useMtls = options.mtls === true;\n const strict = options.strict ?? useMtls;\n const validateProfiles = options.validateSubmissions ?? true;\n const port = options.port ?? (useMtls ? 8443 : 8080);\n const host = options.host ?? '127.0.0.1';\n\n const store = new SandboxStore();\n store.load(resolveScenario(options.scenario));\n\n const signingKeys = resolveSigningKeys({\n keyId: options.jwtKeyId,\n privateKey: options.jwtPrivateKey,\n publicKey: options.jwtPublicKey,\n });\n\n const handler = (req: IncomingMessage, res: ServerResponse) => {\n handleRequest(req, res, {\n keys: signingKeys,\n log,\n store,\n strict,\n useMtls,\n validateProfiles,\n }).catch((err: unknown) => {\n log(`erro inesperado: ${err instanceof Error ? err.message : String(err)}`);\n if (res.headersSent) {\n // Headers já foram para o socket — só garante que o response feche\n // para o cliente não pendurar. Sem body novo.\n if (!res.writableEnded) res.end();\n return;\n }\n res.writeHead(500, { 'Content-Type': 'application/fhir+json; charset=utf-8' });\n res.end(\n JSON.stringify({\n issue: [{ code: 'exception', diagnostics: String(err), severity: 'fatal' }],\n resourceType: 'OperationOutcome',\n }),\n );\n });\n };\n\n const server: http.Server | https.Server = useMtls\n ? https.createServer(buildTlsOptions(options), handler)\n : http.createServer(handler);\n\n return {\n server,\n signingKeys,\n start: () =>\n new Promise<{ host: string; port: number }>((resolve, reject) => {\n const onError = (err: Error) => {\n server.off('error', onError);\n reject(err);\n };\n server.once('error', onError);\n server.listen(port, host, () => {\n server.off('error', onError);\n const address = server.address();\n const actualPort = typeof address === 'object' && address ? address.port : port;\n log(\n `${useMtls ? 'HTTPS+mTLS' : 'HTTP'} ouvindo em ${host}:${actualPort} ` +\n `(cenário: ${options.scenario ?? 'paciente-com-exames'}, ` +\n `strict=${strict}, validate=${validateProfiles}, kid=${signingKeys.keyId})`,\n );\n resolve({ host, port: actualPort });\n });\n }),\n stop: () =>\n new Promise<void>((resolve, reject) => {\n // server.close() para de aceitar novas conexões mas não derruba\n // keep-alive existentes — em testes isso pode prender o vitest.\n server.closeAllConnections?.();\n server.close((err) => (err ? reject(err) : resolve()));\n }),\n store,\n };\n}\n\ninterface HandlerContext {\n keys: SigningKeys;\n log: (msg: string) => void;\n store: SandboxStore;\n strict: boolean;\n useMtls: boolean;\n validateProfiles: boolean;\n}\n\nasync function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: HandlerContext,\n): Promise<void> {\n const url = new URL(req.url ?? '/', 'http://sandbox.local');\n const body = await readBody(req);\n const peerCert = ctx.useMtls ? extractPeerCert(req) : undefined;\n const headers = lowercaseHeaders(req.headers);\n\n const routeCtx: RouteContext = {\n body,\n headers,\n keys: ctx.keys,\n method: req.method ?? 'GET',\n path: url.pathname,\n peerCert,\n query: url.searchParams,\n store: ctx.store,\n strict: ctx.strict,\n validateProfiles: ctx.validateProfiles,\n };\n\n const response = dispatch(routeCtx);\n ctx.log(`${routeCtx.method} ${routeCtx.path} → ${response.status}`);\n\n res.writeHead(response.status, {\n 'Content-Type': 'application/fhir+json; charset=utf-8',\n });\n res.end(JSON.stringify(response.body));\n}\n\n/** Sentinela retornada quando o corpo da requisição não é JSON válido. */\nconst INVALID_BODY = Symbol.for('rnds-sandbox.invalidBody');\n\nasync function readBody(req: IncomingMessage): Promise<unknown> {\n if (req.method !== 'POST' && req.method !== 'PUT') {\n return undefined;\n }\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(chunk as Buffer);\n }\n if (chunks.length === 0) {\n return undefined;\n }\n const text = Buffer.concat(chunks).toString('utf-8');\n if (!text) {\n return undefined;\n }\n try {\n return JSON.parse(text);\n } catch {\n // JSON malformado — retorna sentinela. Handler de POST detecta e\n // devolve OperationOutcome com `code: 'structure'` em vez de tentar\n // tratar a string como Bundle.\n return INVALID_BODY;\n }\n}\n\n/** True quando o body é a sentinela INVALID_BODY (JSON malformado). */\nexport function isInvalidBody(body: unknown): boolean {\n return body === INVALID_BODY;\n}\n\nfunction extractPeerCert(req: IncomingMessage): PeerCertInfo {\n const socket = req.socket as TLSSocket;\n if (typeof socket.getPeerCertificate !== 'function') {\n return { presented: false };\n }\n const cert = socket.getPeerCertificate();\n if (!cert || Object.keys(cert).length === 0) {\n return { presented: false };\n }\n const cn = readCommonName(cert.subject);\n return {\n cn,\n cnes: cn ? extractCnesFromCn(cn) : undefined,\n presented: true,\n };\n}\n\nfunction readCommonName(subject: unknown): string | undefined {\n if (!subject || typeof subject !== 'object') {\n return undefined;\n }\n const cnValue = (subject as Record<string, unknown>).CN;\n if (typeof cnValue === 'string') {\n return cnValue;\n }\n return undefined;\n}\n\nfunction extractCnesFromCn(cn: string): string | undefined {\n // Heurística: CN de cert ICP-Brasil PJ frequentemente embute CNES como\n // sequência de 7 dígitos; se não for o caso, simplesmente não retorna nada.\n const match = /\\b(\\d{7})\\b/.exec(cn);\n return match ? match[1] : undefined;\n}\n\nfunction lowercaseHeaders(headers: http.IncomingHttpHeaders): Record<string, string | undefined> {\n const out: Record<string, string | undefined> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (Array.isArray(value)) {\n out[key.toLowerCase()] = value.join(', ');\n } else {\n out[key.toLowerCase()] = value;\n }\n }\n return out;\n}\n\nfunction buildTlsOptions(options: SandboxOptions): https.ServerOptions {\n const tlsOptions: https.ServerOptions = {\n rejectUnauthorized: false,\n requestCert: true,\n };\n if (options.serverPfx) {\n tlsOptions.pfx = resolveBuffer(options.serverPfx);\n if (options.serverPfxPassword) {\n tlsOptions.passphrase = options.serverPfxPassword;\n }\n } else if (options.serverKey && options.serverCert) {\n tlsOptions.key = resolveBuffer(options.serverKey);\n tlsOptions.cert = resolveBuffer(options.serverCert);\n } else {\n throw new Error('mTLS requer --pfx + --pfx-password OU --server-key + --server-cert (PEM)');\n }\n return tlsOptions;\n}\n\nfunction resolveBuffer(value: Buffer | string): Buffer {\n if (typeof value !== 'string') {\n return value;\n }\n if (value.includes('-----BEGIN')) {\n return Buffer.from(value);\n }\n return fs.readFileSync(value);\n}\n","/**\n * Store em memória dos cenários carregados no sandbox.\n *\n * Indexa pacientes por CPF e CNS, organizações por CNES e\n * profissionais por CNS. Bundles submetidos via POST /Bundle\n * são acumulados em memória (acessíveis via getSubmittedBundles).\n */\n\nimport type {\n SandboxBundle,\n SandboxOrganization,\n SandboxPatient,\n SandboxPractitioner,\n SandboxResource,\n Scenario,\n} from './types';\n\nconst NS = {\n cnes: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cnes',\n cns: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cns',\n cpf: 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cpf',\n} as const;\n\nexport class SandboxStore {\n private patientsByCpf = new Map<string, SandboxPatient>();\n private patientsByCns = new Map<string, SandboxPatient>();\n private organizationsByCnes = new Map<string, SandboxOrganization>();\n private practitionersByCns = new Map<string, SandboxPractitioner>();\n private submittedBundles: SandboxBundle[] = [];\n private resourceSeq = 0;\n\n load(scenario: Scenario): void {\n this.reset();\n for (const patient of scenario.data.patients) {\n this.indexPatient(patient);\n }\n for (const org of scenario.data.organizations) {\n this.indexOrganization(org);\n }\n for (const pract of scenario.data.practitioners) {\n this.indexPractitioner(pract);\n }\n for (const bundle of scenario.data.submittedBundles ?? []) {\n this.submittedBundles.push(bundle);\n }\n }\n\n reset(): void {\n this.patientsByCpf.clear();\n this.patientsByCns.clear();\n this.organizationsByCnes.clear();\n this.practitionersByCns.clear();\n this.submittedBundles = [];\n this.resourceSeq = 0;\n }\n\n /** Próximo número de sequência global para gerar `Resource/sandbox-N`. */\n nextResourceId(): number {\n this.resourceSeq += 1;\n return this.resourceSeq;\n }\n\n findPatientByCpf(cpf: string): SandboxPatient | undefined {\n return this.patientsByCpf.get(stripFormatting(cpf));\n }\n\n findPatientByCns(cns: string): SandboxPatient | undefined {\n return this.patientsByCns.get(stripFormatting(cns));\n }\n\n findOrganizationByCnes(cnes: string): SandboxOrganization | undefined {\n return this.organizationsByCnes.get(stripFormatting(cnes));\n }\n\n findPractitionerByCns(cns: string): SandboxPractitioner | undefined {\n return this.practitionersByCns.get(stripFormatting(cns));\n }\n\n recordBundle(bundle: SandboxBundle): void {\n this.submittedBundles.push(bundle);\n }\n\n getSubmittedBundles(): readonly SandboxBundle[] {\n return this.submittedBundles;\n }\n\n /**\n * Lista todos os recursos de um tipo presentes nos bundles submetidos\n * (incluindo os pré-carregados pelo cenário). Usado pelos endpoints\n * de busca FHIR (`GET /Observation?subject=...`, etc.).\n */\n listResourcesByType(resourceType: string): SandboxResource[] {\n const out: SandboxResource[] = [];\n for (const bundle of this.submittedBundles) {\n for (const entry of bundle.entry ?? []) {\n if (entry.resource && entry.resource.resourceType === resourceType) {\n out.push(entry.resource);\n }\n }\n }\n return out;\n }\n\n /**\n * Filtra recursos cujo `subject.reference` ou `patient.reference`\n * aponta para o paciente indicado (aceita tanto `Patient/{cns}` quanto\n * apenas o CNS sem prefixo).\n */\n searchResourcesByPatient(resourceType: string, patientCns: string): SandboxResource[] {\n const cns = stripFormatting(patientCns);\n return this.listResourcesByType(resourceType).filter((resource) => {\n const subjectRef = resource.subject?.reference;\n const patientRef = resource.patient?.reference;\n return (\n (subjectRef && referenceMatchesCns(subjectRef, cns)) ||\n (patientRef && referenceMatchesCns(patientRef, cns))\n );\n });\n }\n\n private indexPatient(patient: SandboxPatient): void {\n const cns = patient.id ?? findIdentifier(patient.identifier, NS.cns);\n if (cns) {\n this.patientsByCns.set(stripFormatting(cns), patient);\n }\n const cpf = findIdentifier(patient.identifier, NS.cpf);\n if (cpf) {\n this.patientsByCpf.set(stripFormatting(cpf), patient);\n }\n }\n\n private indexOrganization(org: SandboxOrganization): void {\n const cnes = org.id ?? findIdentifier(org.identifier, NS.cnes);\n if (cnes) {\n this.organizationsByCnes.set(stripFormatting(cnes), org);\n }\n }\n\n private indexPractitioner(pract: SandboxPractitioner): void {\n const cns = pract.id ?? findIdentifier(pract.identifier, NS.cns);\n if (cns) {\n this.practitionersByCns.set(stripFormatting(cns), pract);\n }\n }\n}\n\nfunction findIdentifier(\n ids: { system?: string; value?: string }[] | undefined,\n system: string,\n): string | undefined {\n return ids?.find((id) => id.system === system)?.value;\n}\n\nfunction stripFormatting(value: string): string {\n return value.replace(/[^0-9A-Za-z]/g, '');\n}\n\n/**\n * Casa um reference FHIR contra um CNS. Aceita:\n * - `\"700000000000001\"` (CNS puro)\n * - `\"Patient/700000000000001\"` (relative reference)\n * - `\"http://example.com/Patient/700000000000001\"` (absolute URL)\n *\n * Compara o último segmento estritamente para evitar false-match por sufixo\n * (ex.: `Patient/1700000000000001` não deve casar com `700000000000001`).\n */\nfunction referenceMatchesCns(ref: string, cns: string): boolean {\n const lastSegment = ref.includes('/') ? ref.slice(ref.lastIndexOf('/') + 1) : ref;\n return lastSegment === cns;\n}\n"],"mappings":"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","names":["import_node_crypto","fs","SYS_OBS_CATEGORY","CPF","CNS","CNS_PROFISSIONAL","CNS","CNS_PROFISSIONAL","import_node_fs","NS","https","http","fs"]}
@@ -0,0 +1,338 @@
1
+ import { KeyObject } from 'node:crypto';
2
+ import { a as Scenario, b as SandboxPatient, c as SandboxOrganization, d as SandboxPractitioner, e as SandboxBundle, f as SandboxResource, g as ScenarioName, S as SandboxOptions } from './types-C61Coait.cjs';
3
+ export { h as ScenarioData } from './types-C61Coait.cjs';
4
+ import http from 'node:http';
5
+ import https from 'node:https';
6
+
7
+ /**
8
+ * Geração e carregamento de chaves RSA para assinatura RS256 do JWT.
9
+ *
10
+ * Em produção a RNDS usa chaves estáveis (com `kid` publicado em JWKS).
11
+ * No sandbox, geramos um par RSA-2048 efêmero no boot por padrão; quem
12
+ * precisa de tokens estáveis entre reinícios passa `--jwt-private-key`
13
+ * (PEM) — útil em CI ou em demos onde o token é capturado e reutilizado.
14
+ */
15
+
16
+ interface SigningKeys {
17
+ /** Identificador da chave (claim `kid` no JWT, `kid` no JWKS) */
18
+ keyId: string;
19
+ /** Chave privada — usada para assinar */
20
+ privateKey: KeyObject;
21
+ /** Chave pública — exposta via JWKS */
22
+ publicKey: KeyObject;
23
+ }
24
+ interface KeyOptions {
25
+ /** Identificador da chave. Padrão: 'rnds-sandbox-1' */
26
+ keyId?: string;
27
+ /** PEM da chave privada (Buffer ou caminho). Se omitido, gera-se um par novo. */
28
+ privateKey?: Buffer | string;
29
+ /** PEM da chave pública (Buffer ou caminho). Opcional — derivada da privada quando omitida. */
30
+ publicKey?: Buffer | string;
31
+ }
32
+ declare function resolveSigningKeys(options?: KeyOptions): SigningKeys;
33
+ /** Limpa o cache do par default — útil em testes que precisam reset. */
34
+ declare function resetSigningKeysCache(): void;
35
+
36
+ /**
37
+ * Emissor e verificador de tokens JWT (RS256) para o sandbox.
38
+ *
39
+ * A RNDS real assina seus tokens com RS256 e expõe a chave pública
40
+ * para validação. O sandbox faz o mesmo: gera (ou carrega) um par
41
+ * RSA-2048, assina com a privada e expõe a pública via JWKS em
42
+ * `/.well-known/jwks.json`.
43
+ *
44
+ * Claims seguem o formato observado em respostas da RNDS:
45
+ * `iss`, `sub`, `aud`, `iat`, `exp`, e — quando o cliente apresenta
46
+ * certificado mTLS — `cn`/`cnes` extraídos do certificado.
47
+ */
48
+
49
+ interface TokenPayload {
50
+ access_token: string;
51
+ expires_in: number;
52
+ scope: string;
53
+ token_type: string;
54
+ }
55
+ interface JwtClaims {
56
+ /** Audience — identifica quem deve consumir o token */
57
+ aud: string;
58
+ /** Common Name do certificado mTLS, se houver */
59
+ cn?: string;
60
+ /** CNES extraído do certificado mTLS apresentado, se houver */
61
+ cnes?: string;
62
+ /** Expiração (epoch seconds) */
63
+ exp: number;
64
+ /** Issued at (epoch seconds) */
65
+ iat: number;
66
+ /** Issuer */
67
+ iss: string;
68
+ /** Subject — identidade do cliente (CN ou 'sandbox') */
69
+ sub: string;
70
+ }
71
+ interface IssueTokenOptions {
72
+ /** Audience — padrão 'rnds-sandbox' */
73
+ audience?: string;
74
+ /** Identidade extraída do certificado mTLS, se aplicável */
75
+ identity?: {
76
+ cn?: string;
77
+ cnes?: string;
78
+ };
79
+ /** Issuer — padrão 'https://rnds-sandbox.local' */
80
+ issuer?: string;
81
+ /** Override de tempo (testes) */
82
+ now?: number;
83
+ }
84
+ declare function issueToken(keys: SigningKeys, options?: IssueTokenOptions): TokenPayload;
85
+ type VerifyResult = {
86
+ valid: true;
87
+ claims: JwtClaims;
88
+ } | {
89
+ valid: false;
90
+ reason: 'malformed' | 'bad-signature' | 'expired' | 'wrong-key' | 'wrong-audience';
91
+ };
92
+ interface VerifyOptions {
93
+ /** Audience esperada — falha quando claim `aud` não casa. Padrão: 'rnds-sandbox' */
94
+ audience?: string;
95
+ /** Override de tempo para testes */
96
+ now?: number;
97
+ }
98
+ declare function verifyToken(token: string, keys: SigningKeys, options?: VerifyOptions): VerifyResult;
99
+
100
+ /**
101
+ * Geração do documento JWKS (`/.well-known/jwks.json`).
102
+ *
103
+ * Permite que clientes (incluindo jwt.io) busquem a chave pública do
104
+ * sandbox e validem a assinatura do token sem nenhuma configuração
105
+ * fora-de-banda.
106
+ */
107
+
108
+ interface Jwk {
109
+ alg: 'RS256';
110
+ e: string;
111
+ kid: string;
112
+ kty: 'RSA';
113
+ n: string;
114
+ use: 'sig';
115
+ }
116
+ interface JwksDocument {
117
+ keys: Jwk[];
118
+ }
119
+ declare function buildJwks(keys: SigningKeys): JwksDocument;
120
+
121
+ /**
122
+ * Store em memória dos cenários carregados no sandbox.
123
+ *
124
+ * Indexa pacientes por CPF e CNS, organizações por CNES e
125
+ * profissionais por CNS. Bundles submetidos via POST /Bundle
126
+ * são acumulados em memória (acessíveis via getSubmittedBundles).
127
+ */
128
+
129
+ declare class SandboxStore {
130
+ private patientsByCpf;
131
+ private patientsByCns;
132
+ private organizationsByCnes;
133
+ private practitionersByCns;
134
+ private submittedBundles;
135
+ private resourceSeq;
136
+ load(scenario: Scenario): void;
137
+ reset(): void;
138
+ /** Próximo número de sequência global para gerar `Resource/sandbox-N`. */
139
+ nextResourceId(): number;
140
+ findPatientByCpf(cpf: string): SandboxPatient | undefined;
141
+ findPatientByCns(cns: string): SandboxPatient | undefined;
142
+ findOrganizationByCnes(cnes: string): SandboxOrganization | undefined;
143
+ findPractitionerByCns(cns: string): SandboxPractitioner | undefined;
144
+ recordBundle(bundle: SandboxBundle): void;
145
+ getSubmittedBundles(): readonly SandboxBundle[];
146
+ /**
147
+ * Lista todos os recursos de um tipo presentes nos bundles submetidos
148
+ * (incluindo os pré-carregados pelo cenário). Usado pelos endpoints
149
+ * de busca FHIR (`GET /Observation?subject=...`, etc.).
150
+ */
151
+ listResourcesByType(resourceType: string): SandboxResource[];
152
+ /**
153
+ * Filtra recursos cujo `subject.reference` ou `patient.reference`
154
+ * aponta para o paciente indicado (aceita tanto `Patient/{cns}` quanto
155
+ * apenas o CNS sem prefixo).
156
+ */
157
+ searchResourcesByPatient(resourceType: string, patientCns: string): SandboxResource[];
158
+ private indexPatient;
159
+ private indexOrganization;
160
+ private indexPractitioner;
161
+ }
162
+
163
+ /**
164
+ * Roteador HTTP do sandbox.
165
+ *
166
+ * Mapeia o subset de endpoints da RNDS consumidos por
167
+ * @precisa-saude/fhir-rnds. Aplica autenticação por rota:
168
+ *
169
+ * - /api/token: exige certificado mTLS quando o sandbox roda em modo
170
+ * `--mtls` (RNDS de fato exige nesse endpoint).
171
+ * - /api/fhir/r4/*: em modo `strict`, exige bearer token assinado +
172
+ * header `Authorization: <CNS>` (igual à API real). Em modo
173
+ * permissivo (padrão sem flags), aceita sem auth — útil para
174
+ * curl / aulas sem fricção.
175
+ * - /.well-known/jwks.json: público sempre.
176
+ */
177
+
178
+ interface PeerCertInfo {
179
+ /** Common Name (subject.CN) do cert apresentado, se houver */
180
+ cn?: string;
181
+ /** CNES de 7 dígitos extraído do CN (heurística), se encontrado */
182
+ cnes?: string;
183
+ /** True quando o cliente apresentou um certificado válido (qualquer CA) */
184
+ presented: boolean;
185
+ }
186
+ interface RouteContext {
187
+ body?: unknown;
188
+ /** Cabeçalhos HTTP recebidos (chaves em lowercase) */
189
+ headers: Record<string, string | undefined>;
190
+ keys: SigningKeys;
191
+ method: string;
192
+ path: string;
193
+ /** Informação extraída do certificado mTLS, quando o servidor está em modo mtls */
194
+ peerCert?: PeerCertInfo;
195
+ query: URLSearchParams;
196
+ store: SandboxStore;
197
+ /** Quando true, /api/token requer cert apresentado e FHIR exige bearer + CNS */
198
+ strict: boolean;
199
+ /**
200
+ * Quando true (padrão), `POST /Bundle` valida cada entry contra os perfis
201
+ * BR* do IG fhir-brasil e rejeita com 422 se houver erros. Em modo
202
+ * permissivo (`false`), aceita qualquer Bundle com estrutura mínima.
203
+ */
204
+ validateProfiles: boolean;
205
+ }
206
+ interface RouteResponse {
207
+ body: unknown;
208
+ status: number;
209
+ }
210
+ declare function dispatch(ctx: RouteContext): RouteResponse;
211
+
212
+ /**
213
+ * Cenário: paciente com episódio de internação recente.
214
+ *
215
+ * Carlos Henrique Souza, 67 anos, internado por descompensação de
216
+ * insuficiência cardíaca. Bundle pré-submetido contém Encounter
217
+ * (admissão) + Observations (BNP, troponina) + Condition (CID I50.0).
218
+ */
219
+
220
+ declare const internacao: Scenario;
221
+
222
+ /**
223
+ * Cenário: paciente com histórico de exames laboratoriais.
224
+ *
225
+ * Joana Maria da Silva, 42 anos, com Bundle pré-submetido de
226
+ * lipidograma, glicemia e hemograma. Útil para demonstrar
227
+ * o fluxo "buscar paciente → consultar exames anteriores → submeter novos".
228
+ *
229
+ * IDs sintéticos — não correspondem a CPF/CNS reais.
230
+ */
231
+
232
+ declare const pacienteComExames: Scenario;
233
+
234
+ /**
235
+ * Cenário: paciente com calendário vacinal aplicado.
236
+ *
237
+ * Pedro Lima Almeida, 8 anos, com Bundle pré-submetido contendo
238
+ * Immunization (BCG, hepatite B, pentavalente, tríplice viral).
239
+ */
240
+
241
+ declare const vacina: Scenario;
242
+
243
+ /**
244
+ * Registro de cenários disponíveis no sandbox.
245
+ */
246
+
247
+ declare const SCENARIOS: Record<ScenarioName, Scenario>;
248
+ declare function resolveScenario(name: ScenarioName | undefined): Scenario;
249
+
250
+ /**
251
+ * Servidor HTTP(S) do sandbox.
252
+ *
253
+ * Cria um http.Server (sem mTLS) ou https.Server (com mTLS opcional)
254
+ * que despacha requisições para o roteador. Em modo mTLS, o cert do
255
+ * cliente apresentado no handshake é repassado ao roteador para que
256
+ * /api/token possa exigir + extrair identidade dele.
257
+ */
258
+
259
+ interface SandboxServer {
260
+ /** Servidor HTTP(S) subjacente. */
261
+ server: http.Server | https.Server;
262
+ /** Chaves RSA usadas para assinar/validar tokens. */
263
+ signingKeys: SigningKeys;
264
+ /** Inicia o servidor. Retorna quando estiver escutando. */
265
+ start: () => Promise<{
266
+ host: string;
267
+ port: number;
268
+ }>;
269
+ /** Encerra o servidor. */
270
+ stop: () => Promise<void>;
271
+ /** Store em memória — útil para inspeção em testes. */
272
+ store: SandboxStore;
273
+ }
274
+ declare function createSandboxServer(options?: SandboxOptions): SandboxServer;
275
+
276
+ /**
277
+ * Tipos compartilhados pelo validador de perfis FHIR.
278
+ *
279
+ * Espelham o subset de `OperationOutcome` do FHIR R4 que retornamos
280
+ * em respostas de validação. Não pretendem cobrir o spec inteiro.
281
+ */
282
+ type IssueSeverity = 'fatal' | 'error' | 'warning' | 'information';
283
+ interface ValidationIssue {
284
+ /** Código FHIR R4 do tipo do problema (ex: 'required', 'invariant', 'invalid'). */
285
+ code: string;
286
+ /** Mensagem em pt-BR explicando o problema. */
287
+ diagnostics: string;
288
+ /**
289
+ * FHIRPath até o elemento problemático
290
+ * (ex: `Bundle.entry[0].resource.identifier`).
291
+ */
292
+ expression?: string[];
293
+ /** Caminho legível em texto, equivalente a `expression`. */
294
+ location?: string[];
295
+ severity: IssueSeverity;
296
+ }
297
+ interface ValidationResult {
298
+ issues: ValidationIssue[];
299
+ /** True quando não há issues `error` ou `fatal`. */
300
+ valid: boolean;
301
+ }
302
+
303
+ /**
304
+ * Validadores de perfil para os recursos cobertos pelo IG do fhir-brasil
305
+ * (BRPatient, BRLabObservation, BRDiagnosticReport).
306
+ *
307
+ * As regras espelham as restrições do FSH em `ig/input/fsh/profiles/`.
308
+ * Não pretendem ser uma implementação completa de FHIR validation —
309
+ * cobrem o que a RNDS real rejeita na prática para esses três perfis.
310
+ */
311
+
312
+ declare function validateBRPatient(resource: unknown, path: string): ValidationIssue[];
313
+ declare function validateBRLabObservation(resource: unknown, path: string): ValidationIssue[];
314
+ declare function validateBRDiagnosticReport(resource: unknown, path: string): ValidationIssue[];
315
+
316
+ /**
317
+ * Validação de Bundles FHIR contra os perfis do IG fhir-brasil.
318
+ *
319
+ * O sandbox roteia cada `Bundle.entry[i].resource` para o validador do
320
+ * perfil correspondente baseado no `meta.profile` declarado OU no
321
+ * `resourceType` quando ausente. Resultado é uma lista plana de
322
+ * `ValidationIssue` com `expression`/`location` no formato FHIR.
323
+ *
324
+ * Não cobre o spec inteiro de FHIR validation — foca no subset que a
325
+ * RNDS rejeita na prática (cardinalidade, identifiers brasileiros,
326
+ * coding LOINC/UCUM, value sets de status).
327
+ */
328
+
329
+ interface ResourceLike {
330
+ meta?: {
331
+ profile?: string[];
332
+ };
333
+ resourceType?: string;
334
+ }
335
+ declare function validateBundle(bundle: unknown, basePath?: string): ValidationResult;
336
+ declare function validateResource(resource: ResourceLike, path: string): ValidationIssue[];
337
+
338
+ export { type IssueSeverity, type IssueTokenOptions, type Jwk, type JwksDocument, type JwtClaims, type KeyOptions, type PeerCertInfo, type RouteContext, type RouteResponse, SCENARIOS, SandboxOptions, SandboxOrganization, SandboxPractitioner, type SandboxServer, SandboxStore, Scenario, ScenarioName, type SigningKeys, type TokenPayload, type ValidationIssue, type ValidationResult, type VerifyOptions, type VerifyResult, buildJwks, createSandboxServer, dispatch, internacao, issueToken, pacienteComExames, resetSigningKeysCache, resolveScenario, resolveSigningKeys, vacina, validateBRDiagnosticReport, validateBRLabObservation, validateBRPatient, validateBundle, validateResource, verifyToken };