@c15t/backend 2.0.0-rc.0 → 2.0.0-rc.10

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.
Files changed (336) hide show
  1. package/README.md +3 -3
  2. package/dist/302.js +473 -0
  3. package/dist/583.js +540 -0
  4. package/dist/915.js +1771 -0
  5. package/dist/cache.cjs +5 -5
  6. package/dist/cache.js +4 -415
  7. package/dist/core.cjs +1356 -120
  8. package/dist/core.js +163 -1981
  9. package/dist/db/adapters/drizzle.cjs +1 -1
  10. package/dist/db/adapters/drizzle.js +1 -2
  11. package/dist/db/adapters/kysely.cjs +1 -1
  12. package/dist/db/adapters/kysely.js +1 -2
  13. package/dist/db/adapters/mongo.cjs +1 -1
  14. package/dist/db/adapters/mongo.js +1 -2
  15. package/dist/db/adapters/prisma.cjs +1 -1
  16. package/dist/db/adapters/prisma.js +1 -2
  17. package/dist/db/adapters/typeorm.cjs +1 -1
  18. package/dist/db/adapters/typeorm.js +1 -2
  19. package/dist/db/adapters.cjs +1 -1
  20. package/dist/db/migrator.cjs +1 -1
  21. package/dist/db/schema.cjs +43 -3
  22. package/dist/db/schema.js +35 -4
  23. package/dist/define-config.cjs +1 -1
  24. package/dist/edge.cjs +1106 -0
  25. package/dist/edge.js +190 -0
  26. package/dist/router.cjs +885 -123
  27. package/dist/router.js +1 -1507
  28. package/dist/{types.cjs → types/index.cjs} +1 -1
  29. package/{dist → dist-types}/cache/adapters/cloudflare-kv.d.ts +0 -1
  30. package/{dist → dist-types}/cache/adapters/index.d.ts +0 -1
  31. package/{dist → dist-types}/cache/adapters/memory.d.ts +0 -1
  32. package/{dist → dist-types}/cache/adapters/upstash-redis.d.ts +0 -1
  33. package/{dist → dist-types}/cache/gvl-resolver.d.ts +0 -1
  34. package/{dist → dist-types}/cache/index.d.ts +0 -1
  35. package/{dist → dist-types}/cache/keys.d.ts +0 -1
  36. package/{dist → dist-types}/cache/types.d.ts +0 -1
  37. package/{dist → dist-types}/core.d.ts +8 -1
  38. package/{dist → dist-types}/db/migrator/index.d.ts +0 -1
  39. package/dist-types/db/registry/consent-policy.d.ts +78 -0
  40. package/{dist → dist-types}/db/registry/consent-purpose.d.ts +0 -1
  41. package/{dist → dist-types}/db/registry/domain.d.ts +0 -1
  42. package/dist-types/db/registry/index.d.ts +118 -0
  43. package/dist-types/db/registry/runtime-policy-decision.d.ts +60 -0
  44. package/{dist → dist-types}/db/registry/subject.d.ts +0 -2
  45. package/{dist → dist-types}/db/registry/types.d.ts +1 -1
  46. package/{dist → dist-types}/db/registry/utils/generate-id.d.ts +0 -1
  47. package/{dist → dist-types}/db/registry/utils.d.ts +0 -1
  48. package/{dist → dist-types}/db/schema/1.0.0/audit-log.d.ts +0 -1
  49. package/{dist → dist-types}/db/schema/1.0.0/consent-policy.d.ts +0 -1
  50. package/{dist → dist-types}/db/schema/1.0.0/consent-purpose.d.ts +0 -1
  51. package/{dist → dist-types}/db/schema/1.0.0/consent-record.d.ts +0 -1
  52. package/{dist → dist-types}/db/schema/1.0.0/consent.d.ts +1 -2
  53. package/{dist → dist-types}/db/schema/1.0.0/domain.d.ts +0 -1
  54. package/{dist → dist-types}/db/schema/1.0.0/index.d.ts +0 -32
  55. package/{dist → dist-types}/db/schema/1.0.0/subject.d.ts +0 -2
  56. package/{dist → dist-types}/db/schema/2.0.0/audit-log.d.ts +1 -2
  57. package/{dist → dist-types}/db/schema/2.0.0/consent-policy.d.ts +3 -3
  58. package/{dist → dist-types}/db/schema/2.0.0/consent-purpose.d.ts +1 -2
  59. package/{dist → dist-types}/db/schema/2.0.0/consent.d.ts +7 -2
  60. package/{dist → dist-types}/db/schema/2.0.0/domain.d.ts +1 -2
  61. package/{dist → dist-types}/db/schema/2.0.0/index.d.ts +455 -28
  62. package/dist-types/db/schema/2.0.0/runtime-policy-decision.d.ts +23 -0
  63. package/{dist → dist-types}/db/schema/2.0.0/subject.d.ts +1 -3
  64. package/{dist → dist-types}/db/schema/index.d.ts +908 -86
  65. package/{dist → dist-types}/db/tenant-scope.d.ts +0 -1
  66. package/dist-types/define-config.d.ts +17 -0
  67. package/dist-types/edge/index.d.ts +5 -0
  68. package/dist-types/edge/init-handler.d.ts +40 -0
  69. package/dist-types/edge/resolve-consent.d.ts +80 -0
  70. package/dist-types/edge/types.d.ts +13 -0
  71. package/{dist → dist-types}/handlers/consent/check.handler.d.ts +0 -1
  72. package/{src/handlers/consent/index.ts → dist-types/handlers/consent/index.d.ts} +0 -1
  73. package/{dist → dist-types}/handlers/init/geo.d.ts +2 -3
  74. package/{dist → dist-types}/handlers/init/index.d.ts +2 -3
  75. package/dist-types/handlers/init/policy.d.ts +26 -0
  76. package/dist-types/handlers/init/resolve-init.d.ts +44 -0
  77. package/dist-types/handlers/init/translations.d.ts +48 -0
  78. package/dist-types/handlers/legal-document/current.handler.d.ts +11 -0
  79. package/dist-types/handlers/legal-document/snapshot.d.ts +39 -0
  80. package/dist-types/handlers/policy/snapshot.d.ts +99 -0
  81. package/{src/handlers/status/index.ts → dist-types/handlers/status/index.d.ts} +0 -1
  82. package/{dist → dist-types}/handlers/status/status.handler.d.ts +0 -1
  83. package/{dist → dist-types}/handlers/subject/get.handler.d.ts +3 -2
  84. package/{src/handlers/subject/index.ts → dist-types/handlers/subject/index.d.ts} +0 -1
  85. package/{dist → dist-types}/handlers/subject/list.handler.d.ts +3 -2
  86. package/{dist → dist-types}/handlers/subject/patch.handler.d.ts +0 -2
  87. package/{dist → dist-types}/handlers/subject/post.handler.d.ts +12 -1
  88. package/{dist → dist-types}/handlers/utils/consent-enrichment.d.ts +3 -1
  89. package/{dist → dist-types}/init.d.ts +4 -7
  90. package/{dist → dist-types}/middleware/auth/index.d.ts +0 -1
  91. package/{dist → dist-types}/middleware/auth/validate-api-key.d.ts +0 -1
  92. package/{dist → dist-types}/middleware/cors/cors.d.ts +0 -1
  93. package/{src/middleware/cors/index.ts → dist-types/middleware/cors/index.d.ts} +0 -1
  94. package/{dist → dist-types}/middleware/cors/is-origin-trusted.d.ts +0 -1
  95. package/{dist → dist-types}/middleware/cors/process-cors.d.ts +0 -1
  96. package/{dist → dist-types}/middleware/openapi/config.d.ts +0 -1
  97. package/{dist → dist-types}/middleware/openapi/handlers.d.ts +0 -1
  98. package/{src/middleware/openapi/index.ts → dist-types/middleware/openapi/index.d.ts} +0 -1
  99. package/{dist → dist-types}/middleware/process-ip/index.d.ts +0 -1
  100. package/dist-types/policies/builder.d.ts +127 -0
  101. package/dist-types/policies/defaults.d.ts +2 -0
  102. package/dist-types/policies/matchers.d.ts +3 -0
  103. package/{dist → dist-types}/router.d.ts +0 -1
  104. package/{dist → dist-types}/routes/consent.d.ts +0 -1
  105. package/{dist → dist-types}/routes/index.d.ts +1 -1
  106. package/{dist → dist-types}/routes/init.d.ts +0 -1
  107. package/dist-types/routes/legal-document.d.ts +7 -0
  108. package/{dist → dist-types}/routes/status.d.ts +0 -1
  109. package/{dist → dist-types}/routes/subject.d.ts +0 -1
  110. package/{dist → dist-types}/types/api.d.ts +0 -1
  111. package/dist-types/types/index.d.ts +464 -0
  112. package/dist-types/utils/background.d.ts +6 -0
  113. package/{dist → dist-types}/utils/create-telemetry-options.d.ts +1 -2
  114. package/{dist → dist-types}/utils/env.d.ts +0 -1
  115. package/{dist → dist-types}/utils/extract-error-message.d.ts +0 -1
  116. package/{dist → dist-types}/utils/instrumentation.d.ts +2 -3
  117. package/{dist → dist-types}/utils/logger.d.ts +0 -1
  118. package/{dist → dist-types}/utils/metrics.d.ts +0 -1
  119. package/dist-types/version.d.ts +1 -0
  120. package/docs/README.md +49 -0
  121. package/docs/api/configuration.md +208 -0
  122. package/docs/api/endpoints.md +211 -0
  123. package/docs/guides/caching.md +85 -0
  124. package/docs/guides/database-setup.md +128 -0
  125. package/docs/guides/edge-deployment.md +251 -0
  126. package/docs/guides/framework-integration.md +142 -0
  127. package/docs/guides/iab-tcf.md +89 -0
  128. package/docs/guides/observability.md +96 -0
  129. package/docs/guides/policy-packs.md +396 -0
  130. package/docs/quickstart.md +129 -0
  131. package/package.json +53 -39
  132. package/.turbo/turbo-build.log +0 -49
  133. package/CHANGELOG.md +0 -89
  134. package/dist/cache/adapters/cloudflare-kv.d.ts.map +0 -1
  135. package/dist/cache/adapters/index.d.ts.map +0 -1
  136. package/dist/cache/adapters/memory.d.ts.map +0 -1
  137. package/dist/cache/adapters/upstash-redis.d.ts.map +0 -1
  138. package/dist/cache/gvl-resolver.d.ts.map +0 -1
  139. package/dist/cache/index.d.ts.map +0 -1
  140. package/dist/cache/keys.d.ts.map +0 -1
  141. package/dist/cache/types.d.ts.map +0 -1
  142. package/dist/core.d.ts.map +0 -1
  143. package/dist/db/adapters/drizzle.d.ts +0 -2
  144. package/dist/db/adapters/drizzle.d.ts.map +0 -1
  145. package/dist/db/adapters/index.d.ts +0 -2
  146. package/dist/db/adapters/index.d.ts.map +0 -1
  147. package/dist/db/adapters/kysely.d.ts +0 -2
  148. package/dist/db/adapters/kysely.d.ts.map +0 -1
  149. package/dist/db/adapters/mongo.d.ts +0 -2
  150. package/dist/db/adapters/mongo.d.ts.map +0 -1
  151. package/dist/db/adapters/prisma.d.ts +0 -2
  152. package/dist/db/adapters/prisma.d.ts.map +0 -1
  153. package/dist/db/adapters/typeorm.d.ts +0 -2
  154. package/dist/db/adapters/typeorm.d.ts.map +0 -1
  155. package/dist/db/migrator/index.d.ts.map +0 -1
  156. package/dist/db/registry/consent-policy.d.ts +0 -23
  157. package/dist/db/registry/consent-policy.d.ts.map +0 -1
  158. package/dist/db/registry/consent-purpose.d.ts.map +0 -1
  159. package/dist/db/registry/domain.d.ts.map +0 -1
  160. package/dist/db/registry/index.d.ts +0 -57
  161. package/dist/db/registry/index.d.ts.map +0 -1
  162. package/dist/db/registry/subject.d.ts.map +0 -1
  163. package/dist/db/registry/types.d.ts.map +0 -1
  164. package/dist/db/registry/utils/generate-id.d.ts.map +0 -1
  165. package/dist/db/registry/utils.d.ts.map +0 -1
  166. package/dist/db/schema/1.0.0/audit-log.d.ts.map +0 -1
  167. package/dist/db/schema/1.0.0/consent-policy.d.ts.map +0 -1
  168. package/dist/db/schema/1.0.0/consent-purpose.d.ts.map +0 -1
  169. package/dist/db/schema/1.0.0/consent-record.d.ts.map +0 -1
  170. package/dist/db/schema/1.0.0/consent.d.ts.map +0 -1
  171. package/dist/db/schema/1.0.0/domain.d.ts.map +0 -1
  172. package/dist/db/schema/1.0.0/index.d.ts.map +0 -1
  173. package/dist/db/schema/1.0.0/subject.d.ts.map +0 -1
  174. package/dist/db/schema/2.0.0/audit-log.d.ts.map +0 -1
  175. package/dist/db/schema/2.0.0/consent-policy.d.ts.map +0 -1
  176. package/dist/db/schema/2.0.0/consent-purpose.d.ts.map +0 -1
  177. package/dist/db/schema/2.0.0/consent.d.ts.map +0 -1
  178. package/dist/db/schema/2.0.0/domain.d.ts.map +0 -1
  179. package/dist/db/schema/2.0.0/index.d.ts.map +0 -1
  180. package/dist/db/schema/2.0.0/subject.d.ts.map +0 -1
  181. package/dist/db/schema/index.d.ts.map +0 -1
  182. package/dist/db/tenant-scope.d.ts.map +0 -1
  183. package/dist/define-config.d.ts +0 -5
  184. package/dist/define-config.d.ts.map +0 -1
  185. package/dist/handlers/consent/check.handler.d.ts.map +0 -1
  186. package/dist/handlers/consent/index.d.ts +0 -12
  187. package/dist/handlers/consent/index.d.ts.map +0 -1
  188. package/dist/handlers/init/geo.d.ts.map +0 -1
  189. package/dist/handlers/init/index.d.ts.map +0 -1
  190. package/dist/handlers/init/translations.d.ts +0 -28
  191. package/dist/handlers/init/translations.d.ts.map +0 -1
  192. package/dist/handlers/status/index.d.ts +0 -7
  193. package/dist/handlers/status/index.d.ts.map +0 -1
  194. package/dist/handlers/status/status.handler.d.ts.map +0 -1
  195. package/dist/handlers/subject/get.handler.d.ts.map +0 -1
  196. package/dist/handlers/subject/index.d.ts +0 -10
  197. package/dist/handlers/subject/index.d.ts.map +0 -1
  198. package/dist/handlers/subject/list.handler.d.ts.map +0 -1
  199. package/dist/handlers/subject/patch.handler.d.ts.map +0 -1
  200. package/dist/handlers/subject/post.handler.d.ts.map +0 -1
  201. package/dist/handlers/utils/consent-enrichment.d.ts.map +0 -1
  202. package/dist/init.d.ts.map +0 -1
  203. package/dist/middleware/auth/index.d.ts.map +0 -1
  204. package/dist/middleware/auth/validate-api-key.d.ts.map +0 -1
  205. package/dist/middleware/cors/cors.d.ts.map +0 -1
  206. package/dist/middleware/cors/index.d.ts +0 -30
  207. package/dist/middleware/cors/index.d.ts.map +0 -1
  208. package/dist/middleware/cors/is-origin-trusted.d.ts.map +0 -1
  209. package/dist/middleware/cors/process-cors.d.ts.map +0 -1
  210. package/dist/middleware/openapi/config.d.ts.map +0 -1
  211. package/dist/middleware/openapi/handlers.d.ts.map +0 -1
  212. package/dist/middleware/openapi/index.d.ts +0 -12
  213. package/dist/middleware/openapi/index.d.ts.map +0 -1
  214. package/dist/middleware/process-ip/index.d.ts.map +0 -1
  215. package/dist/router.d.ts.map +0 -1
  216. package/dist/routes/consent.d.ts.map +0 -1
  217. package/dist/routes/index.d.ts.map +0 -1
  218. package/dist/routes/init.d.ts.map +0 -1
  219. package/dist/routes/status.d.ts.map +0 -1
  220. package/dist/routes/subject.d.ts.map +0 -1
  221. package/dist/types/api.d.ts.map +0 -1
  222. package/dist/types/index.d.ts +0 -255
  223. package/dist/types/index.d.ts.map +0 -1
  224. package/dist/utils/create-telemetry-options.d.ts.map +0 -1
  225. package/dist/utils/env.d.ts.map +0 -1
  226. package/dist/utils/extract-error-message.d.ts.map +0 -1
  227. package/dist/utils/index.d.ts +0 -4
  228. package/dist/utils/index.d.ts.map +0 -1
  229. package/dist/utils/instrumentation.d.ts.map +0 -1
  230. package/dist/utils/logger.d.ts.map +0 -1
  231. package/dist/utils/metrics.d.ts.map +0 -1
  232. package/dist/version.d.ts +0 -2
  233. package/dist/version.d.ts.map +0 -1
  234. package/knip.json +0 -31
  235. package/rslib.config.ts +0 -93
  236. package/src/cache/adapters/cloudflare-kv.ts +0 -71
  237. package/src/cache/adapters/index.ts +0 -22
  238. package/src/cache/adapters/memory.ts +0 -111
  239. package/src/cache/adapters/upstash-redis.ts +0 -113
  240. package/src/cache/gvl-resolver.ts +0 -289
  241. package/src/cache/index.ts +0 -34
  242. package/src/cache/keys.ts +0 -68
  243. package/src/cache/types.ts +0 -66
  244. package/src/core.ts +0 -368
  245. package/src/db/migrator/index.ts +0 -80
  246. package/src/db/registry/consent-policy.test.ts +0 -451
  247. package/src/db/registry/consent-policy.ts +0 -82
  248. package/src/db/registry/consent-purpose.test.ts +0 -428
  249. package/src/db/registry/consent-purpose.ts +0 -61
  250. package/src/db/registry/domain.test.ts +0 -445
  251. package/src/db/registry/domain.ts +0 -91
  252. package/src/db/registry/index.ts +0 -14
  253. package/src/db/registry/subject.test.ts +0 -388
  254. package/src/db/registry/subject.ts +0 -129
  255. package/src/db/registry/types.ts +0 -10
  256. package/src/db/registry/utils/generate-id.test.ts +0 -216
  257. package/src/db/registry/utils/generate-id.ts +0 -133
  258. package/src/db/registry/utils.ts +0 -133
  259. package/src/db/schema/1.0.0/audit-log.ts +0 -15
  260. package/src/db/schema/1.0.0/consent-policy.ts +0 -14
  261. package/src/db/schema/1.0.0/consent-purpose.ts +0 -14
  262. package/src/db/schema/1.0.0/consent-record.ts +0 -10
  263. package/src/db/schema/1.0.0/consent.ts +0 -20
  264. package/src/db/schema/1.0.0/domain.ts +0 -12
  265. package/src/db/schema/1.0.0/index.ts +0 -48
  266. package/src/db/schema/1.0.0/subject.ts +0 -12
  267. package/src/db/schema/2.0.0/audit-log.ts +0 -18
  268. package/src/db/schema/2.0.0/consent-policy.ts +0 -28
  269. package/src/db/schema/2.0.0/consent-purpose.ts +0 -12
  270. package/src/db/schema/2.0.0/consent.ts +0 -26
  271. package/src/db/schema/2.0.0/domain.ts +0 -12
  272. package/src/db/schema/2.0.0/index.ts +0 -47
  273. package/src/db/schema/2.0.0/subject.ts +0 -14
  274. package/src/db/schema/index.ts +0 -15
  275. package/src/db/tenant-scope.test.ts +0 -750
  276. package/src/db/tenant-scope.ts +0 -103
  277. package/src/define-config.ts +0 -5
  278. package/src/handlers/consent/check.handler.ts +0 -126
  279. package/src/handlers/init/geo.test.ts +0 -317
  280. package/src/handlers/init/geo.ts +0 -195
  281. package/src/handlers/init/index.test.ts +0 -205
  282. package/src/handlers/init/index.ts +0 -114
  283. package/src/handlers/init/translations.test.ts +0 -121
  284. package/src/handlers/init/translations.ts +0 -72
  285. package/src/handlers/status/status.handler.test.ts +0 -155
  286. package/src/handlers/status/status.handler.ts +0 -51
  287. package/src/handlers/subject/get.handler.ts +0 -93
  288. package/src/handlers/subject/list.handler.ts +0 -93
  289. package/src/handlers/subject/patch.handler.ts +0 -122
  290. package/src/handlers/subject/post.handler.test.ts +0 -294
  291. package/src/handlers/subject/post.handler.ts +0 -254
  292. package/src/handlers/utils/consent-enrichment.test.ts +0 -380
  293. package/src/handlers/utils/consent-enrichment.ts +0 -218
  294. package/src/init.test.ts +0 -126
  295. package/src/init.ts +0 -87
  296. package/src/middleware/auth/index.ts +0 -11
  297. package/src/middleware/auth/validate-api-key.test.ts +0 -86
  298. package/src/middleware/auth/validate-api-key.ts +0 -107
  299. package/src/middleware/cors/cors.test.ts +0 -135
  300. package/src/middleware/cors/cors.ts +0 -186
  301. package/src/middleware/cors/is-origin-trusted.test.ts +0 -164
  302. package/src/middleware/cors/is-origin-trusted.ts +0 -130
  303. package/src/middleware/cors/process-cors.ts +0 -91
  304. package/src/middleware/openapi/config.ts +0 -29
  305. package/src/middleware/openapi/handlers.ts +0 -34
  306. package/src/middleware/process-ip/index.test.ts +0 -195
  307. package/src/middleware/process-ip/index.ts +0 -199
  308. package/src/router.ts +0 -15
  309. package/src/routes/consent.ts +0 -52
  310. package/src/routes/index.ts +0 -10
  311. package/src/routes/init.ts +0 -102
  312. package/src/routes/status.ts +0 -46
  313. package/src/routes/subject.ts +0 -152
  314. package/src/types/api.ts +0 -48
  315. package/src/types/index.ts +0 -288
  316. package/src/utils/create-telemetry-options.test.ts +0 -302
  317. package/src/utils/create-telemetry-options.ts +0 -229
  318. package/src/utils/env.ts +0 -84
  319. package/src/utils/extract-error-message.ts +0 -21
  320. package/src/utils/instrumentation.test.ts +0 -185
  321. package/src/utils/instrumentation.ts +0 -196
  322. package/src/utils/logger.ts +0 -41
  323. package/src/utils/metrics.test.ts +0 -323
  324. package/src/utils/metrics.ts +0 -402
  325. package/src/utils/telemetry-pii.test.ts +0 -325
  326. package/src/version.ts +0 -2
  327. package/tsconfig.json +0 -11
  328. package/vitest.config.ts +0 -28
  329. /package/dist/{types.js → types/index.js} +0 -0
  330. /package/{src/db/adapters/drizzle.ts → dist-types/db/adapters/drizzle.d.ts} +0 -0
  331. /package/{src/db/adapters/index.ts → dist-types/db/adapters/index.d.ts} +0 -0
  332. /package/{src/db/adapters/kysely.ts → dist-types/db/adapters/kysely.d.ts} +0 -0
  333. /package/{src/db/adapters/mongo.ts → dist-types/db/adapters/mongo.d.ts} +0 -0
  334. /package/{src/db/adapters/prisma.ts → dist-types/db/adapters/prisma.d.ts} +0 -0
  335. /package/{src/db/adapters/typeorm.ts → dist-types/db/adapters/typeorm.d.ts} +0 -0
  336. /package/{src/utils/index.ts → dist-types/utils/index.d.ts} +0 -0
package/dist/router.cjs CHANGED
@@ -22,7 +22,7 @@ var __webpack_require__ = {};
22
22
  })();
23
23
  (()=>{
24
24
  __webpack_require__.r = (exports1)=>{
25
- if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
25
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
26
26
  value: 'Module'
27
27
  });
28
28
  Object.defineProperty(exports1, '__esModule', {
@@ -42,7 +42,7 @@ const schema_namespaceObject = require("@c15t/schema");
42
42
  const external_hono_namespaceObject = require("hono");
43
43
  const external_hono_openapi_namespaceObject = require("hono-openapi");
44
44
  const http_exception_namespaceObject = require("hono/http-exception");
45
- function extractErrorMessage(error) {
45
+ function extract_error_message_extractErrorMessage(error) {
46
46
  if (error instanceof AggregateError && error.errors?.length > 0) {
47
47
  const inner = error.errors.map((e)=>e instanceof Error ? e.message : String(e)).join('; ');
48
48
  return `AggregateError: ${inner}`;
@@ -54,18 +54,18 @@ const api_namespaceObject = require("@opentelemetry/api");
54
54
  let cachedConfig = null;
55
55
  let cachedDefaultAttributes = {};
56
56
  function create_telemetry_options_isTelemetryEnabled(options) {
57
- if (options) return options.advanced?.telemetry?.enabled === true;
57
+ if (options) return options.telemetry?.enabled === true;
58
58
  return cachedConfig?.enabled === true;
59
59
  }
60
60
  const create_telemetry_options_getTracer = (options)=>{
61
61
  if (!create_telemetry_options_isTelemetryEnabled(options)) return api_namespaceObject.trace.getTracer('c15t-noop');
62
- const tracer = options?.advanced?.telemetry?.tracer ?? cachedConfig?.tracer;
62
+ const tracer = options?.telemetry?.tracer ?? cachedConfig?.tracer;
63
63
  if (tracer) return tracer;
64
64
  return api_namespaceObject.trace.getTracer(options?.appName ?? 'c15t');
65
65
  };
66
66
  const getMeter = (options)=>{
67
67
  if (!create_telemetry_options_isTelemetryEnabled(options)) return api_namespaceObject.metrics.getMeter('c15t-noop');
68
- const meter = options?.advanced?.telemetry?.meter ?? cachedConfig?.meter;
68
+ const meter = options?.telemetry?.meter ?? cachedConfig?.meter;
69
69
  if (meter) return meter;
70
70
  return api_namespaceObject.metrics.getMeter(options?.appName ?? 'c15t');
71
71
  };
@@ -75,7 +75,7 @@ function getDefaultAttributes() {
75
75
  const handleSpanError = (span, error)=>{
76
76
  span.setStatus({
77
77
  code: api_namespaceObject.SpanStatusCode.ERROR,
78
- message: extractErrorMessage(error)
78
+ message: extract_error_message_extractErrorMessage(error)
79
79
  });
80
80
  if (error instanceof Error) span.setAttribute('error.type', error.name);
81
81
  };
@@ -244,7 +244,7 @@ function createMetrics(meter) {
244
244
  };
245
245
  }
246
246
  let metricsInstance = null;
247
- function getMetrics(options) {
247
+ function metrics_getMetrics(options) {
248
248
  if (metricsInstance) return metricsInstance;
249
249
  if (!create_telemetry_options_isTelemetryEnabled(options)) return null;
250
250
  metricsInstance = createMetrics(getMeter(options));
@@ -270,7 +270,7 @@ async function batchLoadPolicies(policyIds, ctx) {
270
270
  for (const p of policyMap.values())uniqueTypes.add(p.type);
271
271
  const latestPolicyByType = new Map();
272
272
  for (const type of uniqueTypes){
273
- const latest = await registry.findOrCreatePolicy(type);
273
+ const latest = await registry.findLatestPolicyByType(type);
274
274
  if (latest) latestPolicyByType.set(type, latest.id);
275
275
  }
276
276
  return {
@@ -296,11 +296,17 @@ async function enrichConsents(consents, ctx) {
296
296
  }
297
297
  return consents.map((consent)=>{
298
298
  let policyType = 'unknown';
299
+ let policyVersion;
300
+ let policyHash;
301
+ let policyEffectiveDate;
299
302
  let isLatestPolicy = false;
300
303
  if (consent.policyId) {
301
304
  const policy = policyMap.get(consent.policyId);
302
305
  if (policy) {
303
306
  policyType = policy.type;
307
+ policyVersion = policy.version;
308
+ policyHash = policy.hash ?? void 0;
309
+ policyEffectiveDate = policy.effectiveDate;
304
310
  isLatestPolicy = latestPolicyByType.get(policyType) === consent.policyId;
305
311
  }
306
312
  }
@@ -317,6 +323,9 @@ async function enrichConsents(consents, ctx) {
317
323
  id: consent.id,
318
324
  type: policyType,
319
325
  policyId: consent.policyId ?? void 0,
326
+ policyVersion,
327
+ policyHash,
328
+ policyEffectiveDate,
320
329
  isLatestPolicy,
321
330
  preferences,
322
331
  givenAt: consent.givenAt
@@ -408,14 +417,14 @@ const checkConsentHandler = async (c)=>{
408
417
  externalId,
409
418
  results
410
419
  });
411
- const metrics = getMetrics();
420
+ const metrics = metrics_getMetrics();
412
421
  if (metrics) for (const [type, result] of Object.entries(results))metrics.recordConsentCheck(type, result.hasConsent);
413
422
  return c.json({
414
423
  results
415
424
  });
416
425
  } catch (error) {
417
426
  logger.error('Error in GET /consents/check handler', {
418
- error: extractErrorMessage(error),
427
+ error: extract_error_message_extractErrorMessage(error),
419
428
  errorType: error instanceof Error ? error.constructor.name : typeof error
420
429
  });
421
430
  if (error instanceof http_exception_namespaceObject.HTTPException) throw error;
@@ -431,11 +440,7 @@ const createConsentRoutes = ()=>{
431
440
  const app = new external_hono_namespaceObject.Hono();
432
441
  app.get('/check', (0, external_hono_openapi_namespaceObject.describeRoute)({
433
442
  summary: 'Check consent by external user ID',
434
- description: `Pre-banner cross-device consent check. Use to avoid showing the banner when the user has already consented on another device.
435
-
436
- **Query parameters:**
437
- - \`externalId\` – External user ID to check
438
- - \`type\` – Consent type(s) to check (comma-separated)`,
443
+ description: "Pre-banner cross-device consent check. Use to avoid showing the banner when the user has already consented on another device.\n\n**Query parameters:**\n- `externalId` – External user ID to check\n- `type` – Consent type(s) to check (comma-separated)",
439
444
  tags: [
440
445
  'Consent'
441
446
  ],
@@ -470,7 +475,7 @@ async function executeWithSpan(span, operation) {
470
475
  }
471
476
  }
472
477
  function resolveDefaultAttributes(options) {
473
- return options?.advanced?.telemetry?.defaultAttributes || getDefaultAttributes();
478
+ return options?.telemetry?.defaultAttributes || getDefaultAttributes();
474
479
  }
475
480
  async function withExternalSpan(attributes, operation, options) {
476
481
  if (!create_telemetry_options_isTelemetryEnabled(options)) return operation();
@@ -596,14 +601,14 @@ async function fetchGVLWithLanguage(language, vendorIds, endpoint = GVL_ENDPOINT
596
601
  if (!parsed.vendorListVersion || !parsed.purposes || !parsed.vendors) throw new Error('Invalid GVL response: missing required fields');
597
602
  return parsed;
598
603
  });
599
- getMetrics()?.recordGvlFetch({
604
+ metrics_getMetrics()?.recordGvlFetch({
600
605
  language,
601
606
  source: 'fetch',
602
607
  status: 200
603
608
  }, Date.now() - fetchStart);
604
609
  return gvl;
605
610
  } catch (error) {
606
- getMetrics()?.recordGvlError({
611
+ metrics_getMetrics()?.recordGvlError({
607
612
  language,
608
613
  errorType: error instanceof Error ? error.name : 'UnknownError'
609
614
  });
@@ -624,18 +629,18 @@ function createGVLResolver(options) {
624
629
  if (bundled?.[language]) return bundled[language];
625
630
  const memoryHit = await withCacheSpan('get', 'memory', ()=>memoryCache.get(cacheKey));
626
631
  if (memoryHit) {
627
- getMetrics()?.recordCacheHit('memory');
632
+ metrics_getMetrics()?.recordCacheHit('memory');
628
633
  return memoryHit;
629
634
  }
630
- getMetrics()?.recordCacheMiss('memory');
635
+ metrics_getMetrics()?.recordCacheMiss('memory');
631
636
  if (cacheAdapter) {
632
637
  const externalHit = await withCacheSpan('get', 'external', ()=>cacheAdapter.get(cacheKey));
633
638
  if (externalHit) {
634
- getMetrics()?.recordCacheHit('external');
639
+ metrics_getMetrics()?.recordCacheHit('external');
635
640
  await withCacheSpan('set', 'memory', ()=>memoryCache.set(cacheKey, externalHit, 300000));
636
641
  return externalHit;
637
642
  }
638
- getMetrics()?.recordCacheMiss('external');
643
+ metrics_getMetrics()?.recordCacheMiss('external');
639
644
  }
640
645
  const gvl = await fetchGVLWithLanguage(language, vendorIds, endpoint);
641
646
  if (gvl) {
@@ -646,6 +651,124 @@ function createGVLResolver(options) {
646
651
  }
647
652
  };
648
653
  }
654
+ const external_jose_namespaceObject = require("jose");
655
+ const POLICY_SNAPSHOT_JWT_HEADER = {
656
+ alg: 'HS256',
657
+ typ: 'JWT'
658
+ };
659
+ const DEFAULT_POLICY_SNAPSHOT_ISSUER = 'c15t';
660
+ const DEFAULT_POLICY_SNAPSHOT_AUDIENCE = 'c15t-policy-snapshot';
661
+ function resolveSnapshotIssuer(options) {
662
+ return options?.issuer?.trim() || DEFAULT_POLICY_SNAPSHOT_ISSUER;
663
+ }
664
+ function resolveSnapshotAudience(params) {
665
+ const configuredAudience = params.options?.audience?.trim();
666
+ if (configuredAudience) return configuredAudience;
667
+ return params.tenantId ? `${DEFAULT_POLICY_SNAPSHOT_AUDIENCE}:${params.tenantId}` : DEFAULT_POLICY_SNAPSHOT_AUDIENCE;
668
+ }
669
+ function getSigningKey(secret) {
670
+ return new TextEncoder().encode(secret);
671
+ }
672
+ function isPolicySnapshotPayload(payload) {
673
+ return 'string' == typeof payload.policyId && 'string' == typeof payload.fingerprint && 'string' == typeof payload.matchedBy && 'string' == typeof payload.jurisdiction && 'string' == typeof payload.model && 'string' == typeof payload.iss && 'string' == typeof payload.aud && 'string' == typeof payload.sub && 'number' == typeof payload.iat && 'number' == typeof payload.exp;
674
+ }
675
+ async function createPolicySnapshotToken(params) {
676
+ const { options } = params;
677
+ if (!options?.signingKey) return;
678
+ const iat = Math.floor(Date.now() / 1000);
679
+ const ttlSeconds = options.ttlSeconds ?? 1800;
680
+ const exp = iat + ttlSeconds;
681
+ const iss = resolveSnapshotIssuer(options);
682
+ const aud = resolveSnapshotAudience({
683
+ options,
684
+ tenantId: params.tenantId
685
+ });
686
+ const payload = {
687
+ iss,
688
+ aud,
689
+ sub: params.policyId,
690
+ tenantId: params.tenantId,
691
+ policyId: params.policyId,
692
+ fingerprint: params.fingerprint,
693
+ matchedBy: params.matchedBy,
694
+ country: params.country,
695
+ region: params.region,
696
+ jurisdiction: params.jurisdiction,
697
+ language: params.language,
698
+ model: params.model,
699
+ policyI18n: params.policyI18n,
700
+ expiryDays: params.expiryDays,
701
+ scopeMode: params.scopeMode,
702
+ uiMode: params.uiMode,
703
+ bannerUi: params.bannerUi,
704
+ dialogUi: params.dialogUi,
705
+ categories: params.categories,
706
+ preselectedCategories: params.preselectedCategories,
707
+ gpc: params.gpc,
708
+ proofConfig: params.proofConfig,
709
+ iat,
710
+ exp
711
+ };
712
+ const token = await new external_jose_namespaceObject.SignJWT(payload).setProtectedHeader(POLICY_SNAPSHOT_JWT_HEADER).setIssuedAt(iat).setExpirationTime(exp).sign(getSigningKey(options.signingKey));
713
+ return {
714
+ token,
715
+ payload
716
+ };
717
+ }
718
+ async function verifyPolicySnapshotToken(params) {
719
+ const { token, options, tenantId } = params;
720
+ if (!options?.signingKey) return {
721
+ valid: false,
722
+ reason: 'missing'
723
+ };
724
+ if (!token) return {
725
+ valid: false,
726
+ reason: 'missing'
727
+ };
728
+ if (3 !== token.split('.').length) return {
729
+ valid: false,
730
+ reason: 'malformed'
731
+ };
732
+ try {
733
+ const { payload, protectedHeader } = await (0, external_jose_namespaceObject.jwtVerify)(token, getSigningKey(options.signingKey), {
734
+ issuer: resolveSnapshotIssuer(options),
735
+ audience: resolveSnapshotAudience({
736
+ options,
737
+ tenantId
738
+ })
739
+ });
740
+ const header = protectedHeader;
741
+ if ('HS256' !== header.alg || 'JWT' !== header.typ) return {
742
+ valid: false,
743
+ reason: 'invalid'
744
+ };
745
+ if (!isPolicySnapshotPayload(payload)) return {
746
+ valid: false,
747
+ reason: 'invalid'
748
+ };
749
+ if (payload.sub !== payload.policyId) return {
750
+ valid: false,
751
+ reason: 'invalid'
752
+ };
753
+ if ((tenantId ?? void 0) !== (payload.tenantId ?? void 0)) return {
754
+ valid: false,
755
+ reason: 'invalid'
756
+ };
757
+ return {
758
+ valid: true,
759
+ payload
760
+ };
761
+ } catch (error) {
762
+ if (error instanceof external_jose_namespaceObject.errors.JWTExpired) return {
763
+ valid: false,
764
+ reason: 'expired'
765
+ };
766
+ return {
767
+ valid: false,
768
+ reason: 'invalid'
769
+ };
770
+ }
771
+ }
649
772
  function geo_normalizeHeader(value) {
650
773
  if (!value) return null;
651
774
  return Array.isArray(value) ? value[0] ?? null : value;
@@ -787,7 +910,7 @@ function checkJurisdiction(countryCode, regionCode) {
787
910
  return jurisdiction;
788
911
  }
789
912
  async function getLocation(request, options) {
790
- if (options.advanced?.disableGeoLocation) return {
913
+ if (options.disableGeoLocation) return {
791
914
  countryCode: null,
792
915
  regionCode: null
793
916
  };
@@ -798,30 +921,253 @@ async function getLocation(request, options) {
798
921
  };
799
922
  }
800
923
  function getJurisdiction(location, options) {
801
- if (options.advanced?.disableGeoLocation) return 'GDPR';
924
+ if (options.disableGeoLocation) return 'GDPR';
802
925
  return checkJurisdiction(location.countryCode, location.regionCode);
803
926
  }
927
+ const schema_types_namespaceObject = require("@c15t/schema/types");
928
+ async function resolvePolicyDecision(params) {
929
+ return (0, schema_types_namespaceObject.resolvePolicyDecision)({
930
+ policies: params.policies,
931
+ countryCode: params.countryCode,
932
+ regionCode: params.regionCode,
933
+ jurisdiction: params.jurisdiction,
934
+ iabEnabled: params.iabEnabled
935
+ });
936
+ }
804
937
  const translations_namespaceObject = require("@c15t/translations");
938
+ const all_namespaceObject = require("@c15t/translations/all");
939
+ const DEFAULT_PROFILE = 'default';
940
+ const warnedKeys = new Set();
805
941
  function isSupportedBaseLanguage(lang) {
806
- return lang in translations_namespaceObject.baseTranslations;
942
+ return lang in all_namespaceObject.baseTranslations;
943
+ }
944
+ function warnOnce(logger, key, message, metadata) {
945
+ if (!logger || warnedKeys.has(key)) return;
946
+ warnedKeys.add(key);
947
+ logger.warn(message, metadata);
948
+ }
949
+ function normalizeLanguage(value) {
950
+ if (!value) return;
951
+ const normalized = value.split(',')[0]?.split(';')[0]?.trim().toLowerCase();
952
+ if (!normalized) return;
953
+ return normalized.split('-')[0] ?? void 0;
954
+ }
955
+ function normalizeProfiles(params) {
956
+ const profiles = params.i18n?.messages;
957
+ const legacy = params.customTranslations;
958
+ if (profiles && Object.keys(profiles).length > 0) {
959
+ if (legacy && Object.keys(legacy).length > 0) warnOnce(params.logger, 'i18n.customTranslations.ignored', '`customTranslations` is deprecated and ignored when `i18n.messages` is configured.');
960
+ return profiles;
961
+ }
962
+ if (legacy && Object.keys(legacy).length > 0) {
963
+ warnOnce(params.logger, 'i18n.customTranslations.deprecated', '`customTranslations` is deprecated. Use `i18n.messages` instead.');
964
+ return {
965
+ [DEFAULT_PROFILE]: {
966
+ translations: legacy
967
+ }
968
+ };
969
+ }
970
+ return {};
807
971
  }
808
- function translations_getTranslationsData(acceptLanguage, customTranslations) {
809
- const supportedDefaultLanguages = Object.keys(translations_namespaceObject.baseTranslations);
810
- const supportedCustomLanguages = Object.keys(customTranslations || {});
811
- const supportedLanguages = [
812
- ...supportedDefaultLanguages,
813
- ...supportedCustomLanguages
972
+ function buildCandidates(input) {
973
+ const raw = [
974
+ {
975
+ language: input.language,
976
+ reason: 'profile_language'
977
+ },
978
+ {
979
+ language: input.fallbackLanguage,
980
+ reason: 'profile_fallback'
981
+ }
814
982
  ];
815
- const preferredLanguage = (0, translations_namespaceObject.selectLanguage)(supportedLanguages, {
983
+ const dedupe = new Set();
984
+ return raw.filter((candidate)=>{
985
+ const key = candidate.language;
986
+ if (dedupe.has(key)) return false;
987
+ dedupe.add(key);
988
+ return true;
989
+ });
990
+ }
991
+ function getProfileLanguages(profiles, profile) {
992
+ return Object.keys(profiles[profile]?.translations ?? {}).sort();
993
+ }
994
+ function getSelectableLanguages(input) {
995
+ return getProfileLanguages(input.profiles, input.profile);
996
+ }
997
+ function resolveFallbackLanguage(input) {
998
+ const configuredFallbackLanguage = normalizeLanguage(input.profile?.fallbackLanguage) ?? 'en';
999
+ const profileLanguages = Object.keys(input.profile?.translations ?? {}).sort();
1000
+ if (profileLanguages.includes(configuredFallbackLanguage)) return configuredFallbackLanguage;
1001
+ if (profileLanguages.includes('en')) return 'en';
1002
+ return profileLanguages[0] ?? configuredFallbackLanguage;
1003
+ }
1004
+ function resolveActiveProfile(input) {
1005
+ const requestedProfile = input.policyProfile ?? input.defaultProfile;
1006
+ if (input.profiles[requestedProfile]) return requestedProfile;
1007
+ if (input.policyProfile) warnOnce(input.logger, `i18n.profile.missing:${requestedProfile}`, `Policy i18n profile '${requestedProfile}' does not exist. Falling back to default profile '${input.defaultProfile}'.`);
1008
+ return input.defaultProfile;
1009
+ }
1010
+ function translations_getTranslationsData(acceptLanguage, customTranslations, options) {
1011
+ const profiles = normalizeProfiles({
1012
+ customTranslations,
1013
+ i18n: options?.i18n,
1014
+ logger: options?.logger
1015
+ });
1016
+ const defaultProfile = options?.i18n?.defaultProfile ?? DEFAULT_PROFILE;
1017
+ const profile = resolveActiveProfile({
1018
+ profiles,
1019
+ defaultProfile,
1020
+ policyProfile: options?.policyI18n?.messageProfile,
1021
+ logger: options?.logger
1022
+ });
1023
+ const configuredLanguages = Object.keys(profiles).length > 0 ? getSelectableLanguages({
1024
+ profiles,
1025
+ profile
1026
+ }) : Object.keys(all_namespaceObject.baseTranslations);
1027
+ const fallbackLanguage = Object.keys(profiles).length > 0 ? resolveFallbackLanguage({
1028
+ profile: profiles[profile]
1029
+ }) : 'en';
1030
+ const policyLanguage = normalizeLanguage(options?.policyI18n?.language);
1031
+ const requestedLanguage = policyLanguage ?? (0, translations_namespaceObject.selectLanguage)(configuredLanguages, {
816
1032
  header: acceptLanguage,
817
- fallback: 'en'
1033
+ fallback: fallbackLanguage
1034
+ });
1035
+ const candidates = buildCandidates({
1036
+ language: requestedLanguage,
1037
+ fallbackLanguage
1038
+ });
1039
+ const selectedCandidate = candidates.find((candidate)=>!!profiles[profile]?.translations[candidate.language]);
1040
+ if (selectedCandidate && 'profile_language' !== selectedCandidate.reason) warnOnce(options?.logger, `i18n.fallback:${profile}:${requestedLanguage}:${selectedCandidate.language}`, `Policy translation fallback used (${selectedCandidate.reason}).`, {
1041
+ requestedProfile: profile,
1042
+ requestedLanguage,
1043
+ resolvedProfile: profile,
1044
+ resolvedLanguage: selectedCandidate.language
818
1045
  });
819
- const base = isSupportedBaseLanguage(preferredLanguage) ? translations_namespaceObject.baseTranslations[preferredLanguage] : translations_namespaceObject.baseTranslations.en;
820
- const custom = supportedCustomLanguages.includes(preferredLanguage) ? customTranslations?.[preferredLanguage] : {};
1046
+ let language = selectedCandidate?.language ?? requestedLanguage;
1047
+ if (!selectedCandidate && !isSupportedBaseLanguage(language)) {
1048
+ warnOnce(options?.logger, `i18n.base-fallback:${language}`, `No translation found for '${language}'. Falling back to base English translations.`);
1049
+ language = 'en';
1050
+ }
1051
+ const base = isSupportedBaseLanguage(language) ? all_namespaceObject.baseTranslations[language] : all_namespaceObject.baseTranslations.en;
1052
+ const custom = selectedCandidate ? profiles[profile]?.translations[selectedCandidate.language] : void 0;
821
1053
  const translations = custom ? (0, translations_namespaceObject.deepMergeTranslations)(base, custom) : base;
822
1054
  return {
823
1055
  translations: translations,
824
- language: preferredLanguage
1056
+ language
1057
+ };
1058
+ }
1059
+ function stripIabTranslations(translations) {
1060
+ const { iab: _iab, ...rest } = translations;
1061
+ return rest;
1062
+ }
1063
+ function resolveNoPolicyFallback() {
1064
+ return {
1065
+ id: 'no_banner',
1066
+ model: 'none',
1067
+ ui: {
1068
+ mode: 'none'
1069
+ }
1070
+ };
1071
+ }
1072
+ async function resolveInitPayload(request, options, logger) {
1073
+ const acceptLanguage = request.headers.get('accept-language') || 'en';
1074
+ const location = await getLocation(request, options);
1075
+ const jurisdiction = getJurisdiction(location, options);
1076
+ const hasExplicitPolicyPack = void 0 !== options.policyPacks;
1077
+ const isExplicitEmptyPolicyPack = hasExplicitPolicyPack && (options.policyPacks?.length ?? 0) === 0;
1078
+ const policyDecision = isExplicitEmptyPolicyPack ? void 0 : await resolvePolicyDecision({
1079
+ policies: options.policyPacks,
1080
+ countryCode: location.countryCode,
1081
+ regionCode: location.regionCode,
1082
+ jurisdiction,
1083
+ iabEnabled: options.iab?.enabled === true
1084
+ });
1085
+ if (hasExplicitPolicyPack && !isExplicitEmptyPolicyPack && !policyDecision) logger?.warn('Policy packs configured but no policy matched', {
1086
+ country: location.countryCode,
1087
+ region: location.regionCode
1088
+ });
1089
+ const resolvedPolicy = hasExplicitPolicyPack ? policyDecision?.policy ?? resolveNoPolicyFallback() : void 0;
1090
+ const iabOptions = options.iab;
1091
+ const shouldIncludeIabPayload = iabOptions?.enabled === true && (!hasExplicitPolicyPack || resolvedPolicy?.model === 'iab');
1092
+ const translationsResult = translations_getTranslationsData(acceptLanguage, options.customTranslations, {
1093
+ i18n: options.i18n,
1094
+ policyI18n: resolvedPolicy?.i18n,
1095
+ logger
1096
+ });
1097
+ const responseTranslations = shouldIncludeIabPayload ? translationsResult : {
1098
+ ...translationsResult,
1099
+ translations: stripIabTranslations(translationsResult.translations)
1100
+ };
1101
+ let gvl = null;
1102
+ if (shouldIncludeIabPayload && iabOptions) {
1103
+ const language = translationsResult.language.split('-')[0] || 'en';
1104
+ const gvlResolver = createGVLResolver({
1105
+ appName: options.appName || 'c15t',
1106
+ bundled: iabOptions.bundled,
1107
+ cacheAdapter: options.cache?.adapter,
1108
+ vendorIds: iabOptions.vendorIds,
1109
+ endpoint: iabOptions.endpoint
1110
+ });
1111
+ gvl = await gvlResolver.get(language);
1112
+ }
1113
+ const customVendors = shouldIncludeIabPayload ? iabOptions?.customVendors : void 0;
1114
+ const snapshot = policyDecision ? await createPolicySnapshotToken({
1115
+ options: options.policySnapshot,
1116
+ tenantId: options.tenantId,
1117
+ policyId: policyDecision.policy.id,
1118
+ fingerprint: policyDecision.fingerprint,
1119
+ matchedBy: policyDecision.matchedBy,
1120
+ country: location?.countryCode ?? null,
1121
+ region: location?.regionCode ?? null,
1122
+ jurisdiction,
1123
+ language: translationsResult.language,
1124
+ model: policyDecision.policy.model,
1125
+ policyI18n: policyDecision.policy.i18n,
1126
+ expiryDays: policyDecision.policy.consent?.expiryDays,
1127
+ scopeMode: policyDecision.policy.consent?.scopeMode,
1128
+ uiMode: policyDecision.policy.ui?.mode,
1129
+ bannerUi: policyDecision.policy.ui?.banner,
1130
+ dialogUi: policyDecision.policy.ui?.dialog,
1131
+ categories: policyDecision.policy.consent?.categories,
1132
+ preselectedCategories: policyDecision.policy.consent?.preselectedCategories,
1133
+ gpc: policyDecision.policy.consent?.gpc,
1134
+ proofConfig: policyDecision.policy.proof
1135
+ }) : void 0;
1136
+ const gpc = '1' === request.headers.get('sec-gpc');
1137
+ metrics_getMetrics()?.recordInit({
1138
+ jurisdiction,
1139
+ country: location?.countryCode ?? void 0,
1140
+ region: location?.regionCode ?? void 0,
1141
+ gpc
1142
+ });
1143
+ return {
1144
+ jurisdiction,
1145
+ location,
1146
+ translations: responseTranslations,
1147
+ branding: options.branding || 'c15t',
1148
+ ...shouldIncludeIabPayload && {
1149
+ gvl,
1150
+ customVendors
1151
+ },
1152
+ ...resolvedPolicy && {
1153
+ policy: resolvedPolicy
1154
+ },
1155
+ ...policyDecision && {
1156
+ policyDecision: {
1157
+ policyId: policyDecision.policy.id,
1158
+ fingerprint: policyDecision.fingerprint,
1159
+ matchedBy: policyDecision.matchedBy,
1160
+ country: location.countryCode,
1161
+ region: location.regionCode,
1162
+ jurisdiction
1163
+ }
1164
+ },
1165
+ ...snapshot?.token && {
1166
+ policySnapshotToken: snapshot.token
1167
+ },
1168
+ ...shouldIncludeIabPayload && iabOptions?.cmpId != null && {
1169
+ cmpId: iabOptions.cmpId
1170
+ }
825
1171
  };
826
1172
  }
827
1173
  const createInitRoute = (options)=>{
@@ -834,7 +1180,7 @@ const createInitRoute = (options)=>{
834
1180
  - **Location** – User's location (null if geo-location is disabled)
835
1181
  - **Translations** – Consent manager copy (from \`Accept-Language\` header)
836
1182
  - **Branding** – Configured branding key
837
- - **GVL** – Global Vendor List when enabled
1183
+ - **GVL** – Global Vendor List when IAB is active for the request
838
1184
 
839
1185
  Use for geo-targeted consent banners and regional compliance.`,
840
1186
  tags: [
@@ -851,43 +1197,22 @@ Use for geo-targeted consent banners and regional compliance.`,
851
1197
  }
852
1198
  }
853
1199
  }), async (c)=>{
854
- const request = c.req.raw;
855
- const acceptLanguage = request.headers.get('accept-language') || 'en';
856
- const location = await getLocation(request, options);
857
- const jurisdiction = getJurisdiction(location, options);
858
- const translationsResult = translations_getTranslationsData(acceptLanguage, options.advanced?.customTranslations);
859
- let gvl = null;
860
- if (options.advanced?.gvl?.enabled) {
861
- const language = translationsResult.language.split('-')[0] || 'en';
862
- const gvlResolver = createGVLResolver({
863
- appName: options.appName || 'c15t',
864
- bundled: options.advanced.gvl.bundled,
865
- cacheAdapter: options.advanced.cache?.adapter,
866
- vendorIds: options.advanced.gvl.vendorIds,
867
- endpoint: options.advanced.gvl.endpoint
868
- });
869
- gvl = await gvlResolver.get(language);
870
- }
871
- const customVendors = options.advanced?.gvl?.customVendors;
872
- const gpc = '1' === request.headers.get('sec-gpc');
873
- getMetrics()?.recordInit({
874
- jurisdiction,
875
- country: location?.countryCode ?? void 0,
876
- region: location?.regionCode ?? void 0,
877
- gpc
878
- });
879
- return c.json({
880
- jurisdiction,
881
- location,
882
- translations: translationsResult,
883
- branding: options.advanced?.branding || 'c15t',
884
- gvl,
885
- customVendors
886
- });
1200
+ const ctx = c.get('c15tContext');
1201
+ const payload = await resolveInitPayload(c.req.raw, options, ctx?.logger);
1202
+ return c.json(payload);
887
1203
  });
888
1204
  return app;
889
1205
  };
890
- const version_version = '2.0.0-rc.0';
1206
+ const external_base_x_namespaceObject = require("base-x");
1207
+ var external_base_x_default = /*#__PURE__*/ __webpack_require__.n(external_base_x_namespaceObject);
1208
+ external_base_x_default()('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
1209
+ class consent_policy_LegalDocumentPolicyConflictError extends Error {
1210
+ constructor(message){
1211
+ super(message);
1212
+ this.name = 'LegalDocumentPolicyConflictError';
1213
+ }
1214
+ }
1215
+ const version_version = '2.0.0-rc.10';
891
1216
  function getHeaders(headers) {
892
1217
  if (!headers) return {
893
1218
  countryCode: null,
@@ -974,6 +1299,12 @@ const getSubjectHandler = async (c)=>{
974
1299
  const subjectId = c.req.param('id');
975
1300
  const type = c.req.query('type');
976
1301
  const typeFilter = type?.split(',').map((t)=>t.trim()) || [];
1302
+ if (!subjectId) throw new http_exception_namespaceObject.HTTPException(400, {
1303
+ message: 'Subject ID is required',
1304
+ cause: {
1305
+ code: 'SUBJECT_ID_REQUIRED'
1306
+ }
1307
+ });
977
1308
  logger.debug('Request parameters', {
978
1309
  subjectId,
979
1310
  typeFilter
@@ -1002,7 +1333,6 @@ const getSubjectHandler = async (c)=>{
1002
1333
  subject: {
1003
1334
  id: subject.id,
1004
1335
  externalId: subject.externalId ?? void 0,
1005
- isIdentified: subject.isIdentified,
1006
1336
  createdAt: subject.createdAt
1007
1337
  },
1008
1338
  consents: filteredConsents,
@@ -1010,7 +1340,7 @@ const getSubjectHandler = async (c)=>{
1010
1340
  });
1011
1341
  } catch (error) {
1012
1342
  logger.error('Error in GET /subjects/:id handler', {
1013
- error: extractErrorMessage(error),
1343
+ error: extract_error_message_extractErrorMessage(error),
1014
1344
  errorType: error instanceof Error ? error.constructor.name : typeof error
1015
1345
  });
1016
1346
  if (error instanceof http_exception_namespaceObject.HTTPException) throw error;
@@ -1058,7 +1388,6 @@ const listSubjectsHandler = async (c)=>{
1058
1388
  return {
1059
1389
  id: subject.id,
1060
1390
  externalId: subject.externalId ?? externalId,
1061
- isIdentified: subject.isIdentified,
1062
1391
  createdAt: subject.createdAt,
1063
1392
  consents: consentItems
1064
1393
  };
@@ -1072,7 +1401,7 @@ const listSubjectsHandler = async (c)=>{
1072
1401
  });
1073
1402
  } catch (error) {
1074
1403
  logger.error('Error in GET /subjects handler', {
1075
- error: extractErrorMessage(error),
1404
+ error: extract_error_message_extractErrorMessage(error),
1076
1405
  errorType: error instanceof Error ? error.constructor.name : typeof error
1077
1406
  });
1078
1407
  if (error instanceof http_exception_namespaceObject.HTTPException) throw error;
@@ -1084,9 +1413,7 @@ const listSubjectsHandler = async (c)=>{
1084
1413
  });
1085
1414
  }
1086
1415
  };
1087
- const external_base_x_namespaceObject = require("base-x");
1088
- var external_base_x_default = /*#__PURE__*/ __webpack_require__.n(external_base_x_namespaceObject);
1089
- const prefixes = {
1416
+ const utils_prefixes = {
1090
1417
  auditLog: 'log',
1091
1418
  consent: 'cns',
1092
1419
  consentPolicy: 'pol',
@@ -1094,10 +1421,10 @@ const prefixes = {
1094
1421
  domain: 'dom',
1095
1422
  subject: 'sub'
1096
1423
  };
1097
- const b58 = external_base_x_default()('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
1098
- function generateId(model) {
1424
+ const utils_b58 = external_base_x_default()('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
1425
+ function utils_generateId(model) {
1099
1426
  const buf = crypto.getRandomValues(new Uint8Array(20));
1100
- const prefix = prefixes[model];
1427
+ const prefix = utils_prefixes[model];
1101
1428
  const EPOCH_TIMESTAMP = 1700000000000;
1102
1429
  const t = Date.now() - EPOCH_TIMESTAMP;
1103
1430
  const high = Math.floor(t / 0x100000000);
@@ -1110,9 +1437,9 @@ function generateId(model) {
1110
1437
  buf[5] = low >>> 16 & 255;
1111
1438
  buf[6] = low >>> 8 & 255;
1112
1439
  buf[7] = 255 & low;
1113
- return `${prefix}_${b58.encode(buf)}`;
1440
+ return `${prefix}_${utils_b58.encode(buf)}`;
1114
1441
  }
1115
- async function generateUniqueId(db, model, ctx, options = {}) {
1442
+ async function utils_generateUniqueId(db, model, ctx, options = {}) {
1116
1443
  const { maxRetries = 10, attempt = 0, baseDelay = 5 } = options;
1117
1444
  if (attempt >= maxRetries) {
1118
1445
  const error = new Error(`Failed to generate unique ID for ${model} after ${maxRetries} attempts`);
@@ -1122,7 +1449,7 @@ async function generateUniqueId(db, model, ctx, options = {}) {
1122
1449
  });
1123
1450
  throw error;
1124
1451
  }
1125
- const id = generateId(model);
1452
+ const id = utils_generateId(model);
1126
1453
  try {
1127
1454
  const existing = await db.findFirst(model, {
1128
1455
  where: (b)=>b('id', '=', id)
@@ -1136,7 +1463,7 @@ async function generateUniqueId(db, model, ctx, options = {}) {
1136
1463
  });
1137
1464
  const delay = Math.min(baseDelay * 2 ** attempt, 1000);
1138
1465
  await new Promise((resolve)=>setTimeout(resolve, delay));
1139
- return generateUniqueId(db, model, ctx, {
1466
+ return utils_generateUniqueId(db, model, ctx, {
1140
1467
  maxRetries,
1141
1468
  attempt: attempt + 1,
1142
1469
  baseDelay
@@ -1152,7 +1479,7 @@ async function generateUniqueId(db, model, ctx, options = {}) {
1152
1479
  if (attempt < maxRetries - 1) {
1153
1480
  const delay = Math.min(baseDelay * 2 ** attempt, 2000);
1154
1481
  await new Promise((resolve)=>setTimeout(resolve, delay));
1155
- return generateUniqueId(db, model, ctx, {
1482
+ return utils_generateUniqueId(db, model, ctx, {
1156
1483
  maxRetries,
1157
1484
  attempt: attempt + 1,
1158
1485
  baseDelay
@@ -1169,6 +1496,12 @@ const patchSubjectHandler = async (c)=>{
1169
1496
  const subjectId = c.req.param('id');
1170
1497
  const body = await c.req.json();
1171
1498
  const { externalId, identityProvider = 'external' } = body;
1499
+ if (!subjectId) throw new http_exception_namespaceObject.HTTPException(400, {
1500
+ message: 'Subject ID is required',
1501
+ cause: {
1502
+ code: 'SUBJECT_ID_REQUIRED'
1503
+ }
1504
+ });
1172
1505
  logger.debug('Request parameters', {
1173
1506
  subjectId,
1174
1507
  externalId,
@@ -1191,12 +1524,11 @@ const patchSubjectHandler = async (c)=>{
1191
1524
  set: {
1192
1525
  externalId,
1193
1526
  identityProvider,
1194
- isIdentified: true,
1195
1527
  updatedAt: new Date()
1196
1528
  }
1197
1529
  });
1198
1530
  await tx.create('auditLog', {
1199
- id: await generateUniqueId(tx, 'auditLog', ctx),
1531
+ id: await utils_generateUniqueId(tx, 'auditLog', ctx),
1200
1532
  subjectId,
1201
1533
  entityType: 'subject',
1202
1534
  entityId: subjectId,
@@ -1211,10 +1543,6 @@ const patchSubjectHandler = async (c)=>{
1211
1543
  identityProvider: {
1212
1544
  from: subject.identityProvider,
1213
1545
  to: identityProvider
1214
- },
1215
- isIdentified: {
1216
- from: subject.isIdentified,
1217
- to: true
1218
1546
  }
1219
1547
  },
1220
1548
  metadata: {
@@ -1228,18 +1556,17 @@ const patchSubjectHandler = async (c)=>{
1228
1556
  externalId,
1229
1557
  identityProvider
1230
1558
  });
1231
- getMetrics()?.recordSubjectLinked(identityProvider);
1559
+ metrics_getMetrics()?.recordSubjectLinked(identityProvider);
1232
1560
  return c.json({
1233
1561
  success: true,
1234
1562
  subject: {
1235
1563
  id: subjectId,
1236
- externalId,
1237
- isIdentified: true
1564
+ externalId
1238
1565
  }
1239
1566
  });
1240
1567
  } catch (error) {
1241
1568
  logger.error('Error in PATCH /subjects/:id handler', {
1242
- error: extractErrorMessage(error),
1569
+ error: extract_error_message_extractErrorMessage(error),
1243
1570
  errorType: error instanceof Error ? error.constructor.name : typeof error
1244
1571
  });
1245
1572
  if (error instanceof http_exception_namespaceObject.HTTPException) throw error;
@@ -1251,6 +1578,234 @@ const patchSubjectHandler = async (c)=>{
1251
1578
  });
1252
1579
  }
1253
1580
  };
1581
+ const DEFAULT_ISSUER = 'c15t';
1582
+ const DEFAULT_AUDIENCE = 'c15t-legal-document-snapshot';
1583
+ function isLegalDocumentPolicyType(type) {
1584
+ return 'privacy_policy' === type || 'terms_and_conditions' === type || 'dpa' === type;
1585
+ }
1586
+ function snapshot_resolveSnapshotIssuer(options) {
1587
+ return options?.issuer?.trim() || DEFAULT_ISSUER;
1588
+ }
1589
+ function snapshot_resolveSnapshotAudience(params) {
1590
+ const configuredAudience = params.options?.audience?.trim();
1591
+ if (configuredAudience) return configuredAudience;
1592
+ return params.tenantId ? `${DEFAULT_AUDIENCE}:${params.tenantId}` : DEFAULT_AUDIENCE;
1593
+ }
1594
+ function snapshot_getSigningKey(secret) {
1595
+ return new TextEncoder().encode(secret);
1596
+ }
1597
+ function isLegalDocumentSnapshotPayload(payload) {
1598
+ return 'string' == typeof payload.iss && 'string' == typeof payload.aud && 'string' == typeof payload.sub && isLegalDocumentPolicyType(payload.type) && 'string' == typeof payload.version && 'string' == typeof payload.hash && 'string' == typeof payload.effectiveDate && 'number' == typeof payload.iat && 'number' == typeof payload.exp;
1599
+ }
1600
+ async function verifyLegalDocumentSnapshotToken(params) {
1601
+ const { token, options, tenantId } = params;
1602
+ if (!options?.signingKey) return {
1603
+ valid: false,
1604
+ reason: 'missing'
1605
+ };
1606
+ if (!token) return {
1607
+ valid: false,
1608
+ reason: 'missing'
1609
+ };
1610
+ if (3 !== token.split('.').length) return {
1611
+ valid: false,
1612
+ reason: 'malformed'
1613
+ };
1614
+ try {
1615
+ const { payload, protectedHeader } = await (0, external_jose_namespaceObject.jwtVerify)(token, snapshot_getSigningKey(options.signingKey), {
1616
+ issuer: snapshot_resolveSnapshotIssuer(options),
1617
+ audience: snapshot_resolveSnapshotAudience({
1618
+ options,
1619
+ tenantId
1620
+ })
1621
+ });
1622
+ const header = protectedHeader;
1623
+ if ('HS256' !== header.alg || 'JWT' !== header.typ) return {
1624
+ valid: false,
1625
+ reason: 'invalid'
1626
+ };
1627
+ if (!isLegalDocumentSnapshotPayload(payload)) return {
1628
+ valid: false,
1629
+ reason: 'invalid'
1630
+ };
1631
+ if (payload.sub !== payload.hash) return {
1632
+ valid: false,
1633
+ reason: 'invalid'
1634
+ };
1635
+ if ((tenantId ?? void 0) !== (payload.tenantId ?? void 0)) return {
1636
+ valid: false,
1637
+ reason: 'invalid'
1638
+ };
1639
+ return {
1640
+ valid: true,
1641
+ payload
1642
+ };
1643
+ } catch (error) {
1644
+ if (error instanceof external_jose_namespaceObject.errors.JWTExpired) return {
1645
+ valid: false,
1646
+ reason: 'expired'
1647
+ };
1648
+ return {
1649
+ valid: false,
1650
+ reason: 'invalid'
1651
+ };
1652
+ }
1653
+ }
1654
+ function buildRuntimeDecisionDedupeKey(input) {
1655
+ return [
1656
+ input.tenantId ?? 'default',
1657
+ input.fingerprint,
1658
+ input.matchedBy,
1659
+ input.countryCode ?? 'none',
1660
+ input.regionCode ?? 'none',
1661
+ input.jurisdiction,
1662
+ input.language ?? 'none'
1663
+ ].join('|');
1664
+ }
1665
+ function buildDecisionPayload(params) {
1666
+ const { tenantId, snapshot, decision, location, jurisdiction, language, proofConfig } = params;
1667
+ if (snapshot?.valid && snapshot.payload) {
1668
+ const sp = snapshot.payload;
1669
+ return {
1670
+ tenantId,
1671
+ policyId: sp.policyId,
1672
+ fingerprint: sp.fingerprint,
1673
+ matchedBy: sp.matchedBy,
1674
+ countryCode: sp.country,
1675
+ regionCode: sp.region,
1676
+ jurisdiction: sp.jurisdiction,
1677
+ language: sp.language,
1678
+ model: sp.model,
1679
+ policyI18n: sp.policyI18n,
1680
+ uiMode: sp.uiMode,
1681
+ bannerUi: sp.bannerUi,
1682
+ dialogUi: sp.dialogUi,
1683
+ categories: sp.categories,
1684
+ preselectedCategories: sp.preselectedCategories,
1685
+ proofConfig: sp.proofConfig,
1686
+ dedupeKey: buildRuntimeDecisionDedupeKey({
1687
+ tenantId,
1688
+ fingerprint: sp.fingerprint,
1689
+ matchedBy: sp.matchedBy,
1690
+ countryCode: sp.country,
1691
+ regionCode: sp.region,
1692
+ jurisdiction: sp.jurisdiction,
1693
+ language: sp.language
1694
+ }),
1695
+ source: 'snapshot_token'
1696
+ };
1697
+ }
1698
+ if (decision) return {
1699
+ tenantId,
1700
+ policyId: decision.policy.id,
1701
+ fingerprint: decision.fingerprint,
1702
+ matchedBy: decision.matchedBy,
1703
+ countryCode: location.countryCode,
1704
+ regionCode: location.regionCode,
1705
+ jurisdiction,
1706
+ language,
1707
+ model: decision.policy.model,
1708
+ policyI18n: decision.policy.i18n,
1709
+ uiMode: decision.policy.ui?.mode,
1710
+ bannerUi: decision.policy.ui?.banner,
1711
+ dialogUi: decision.policy.ui?.dialog,
1712
+ categories: decision.policy.consent?.categories,
1713
+ preselectedCategories: decision.policy.consent?.preselectedCategories,
1714
+ proofConfig,
1715
+ dedupeKey: buildRuntimeDecisionDedupeKey({
1716
+ tenantId,
1717
+ fingerprint: decision.fingerprint,
1718
+ matchedBy: decision.matchedBy,
1719
+ countryCode: location.countryCode,
1720
+ regionCode: location.regionCode,
1721
+ jurisdiction,
1722
+ language
1723
+ }),
1724
+ source: 'write_time_fallback'
1725
+ };
1726
+ }
1727
+ function parseLanguageFromHeader(header) {
1728
+ if (!header) return;
1729
+ const firstLanguage = header.split(',')[0]?.split(';')[0]?.trim();
1730
+ if (!firstLanguage) return;
1731
+ return firstLanguage.split('-')[0]?.toLowerCase();
1732
+ }
1733
+ function isLegalDocumentType(type) {
1734
+ return 'privacy_policy' === type || 'terms_and_conditions' === type || 'dpa' === type;
1735
+ }
1736
+ function resolveSnapshotFailureMode(ctx) {
1737
+ return ctx.policySnapshot?.onValidationFailure ?? 'reject';
1738
+ }
1739
+ function buildSnapshotHttpException(reason) {
1740
+ switch(reason){
1741
+ case 'missing':
1742
+ return new http_exception_namespaceObject.HTTPException(409, {
1743
+ message: 'Policy snapshot token is required',
1744
+ cause: {
1745
+ code: 'POLICY_SNAPSHOT_REQUIRED'
1746
+ }
1747
+ });
1748
+ case 'expired':
1749
+ return new http_exception_namespaceObject.HTTPException(409, {
1750
+ message: 'Policy snapshot token has expired',
1751
+ cause: {
1752
+ code: 'POLICY_SNAPSHOT_EXPIRED'
1753
+ }
1754
+ });
1755
+ case 'malformed':
1756
+ case 'invalid':
1757
+ return new http_exception_namespaceObject.HTTPException(409, {
1758
+ message: 'Policy snapshot token is invalid',
1759
+ cause: {
1760
+ code: 'POLICY_SNAPSHOT_INVALID'
1761
+ }
1762
+ });
1763
+ default:
1764
+ {
1765
+ const _exhaustive = reason;
1766
+ throw new Error(`Unhandled policy snapshot verification failure reason: ${_exhaustive}`);
1767
+ }
1768
+ }
1769
+ }
1770
+ function buildLegalDocumentSnapshotHttpException(reason) {
1771
+ switch(reason){
1772
+ case 'missing':
1773
+ return new http_exception_namespaceObject.HTTPException(409, {
1774
+ message: 'Legal document snapshot token is required',
1775
+ cause: {
1776
+ code: 'LEGAL_DOCUMENT_SNAPSHOT_REQUIRED'
1777
+ }
1778
+ });
1779
+ case 'expired':
1780
+ return new http_exception_namespaceObject.HTTPException(409, {
1781
+ message: 'Legal document snapshot token has expired',
1782
+ cause: {
1783
+ code: 'LEGAL_DOCUMENT_SNAPSHOT_EXPIRED'
1784
+ }
1785
+ });
1786
+ case 'malformed':
1787
+ case 'invalid':
1788
+ return new http_exception_namespaceObject.HTTPException(409, {
1789
+ message: 'Legal document snapshot token is invalid',
1790
+ cause: {
1791
+ code: 'LEGAL_DOCUMENT_SNAPSHOT_INVALID'
1792
+ }
1793
+ });
1794
+ default:
1795
+ {
1796
+ const _exhaustive = reason;
1797
+ throw new Error(`Unhandled legal document snapshot verification failure reason: ${_exhaustive}`);
1798
+ }
1799
+ }
1800
+ }
1801
+ function buildLegalDocumentProofHttpException(message) {
1802
+ return new http_exception_namespaceObject.HTTPException(409, {
1803
+ message,
1804
+ cause: {
1805
+ code: 'LEGAL_DOCUMENT_PROOF_REQUIRED'
1806
+ }
1807
+ });
1808
+ }
1254
1809
  const postSubjectHandler = async (c)=>{
1255
1810
  const ctx = c.get('c15tContext');
1256
1811
  const logger = ctx.logger;
@@ -1260,6 +1815,8 @@ const postSubjectHandler = async (c)=>{
1260
1815
  const { type, subjectId, identityProvider, externalSubjectId, domain, metadata, givenAt: givenAtEpoch } = input;
1261
1816
  const preferences = 'preferences' in input ? input.preferences : void 0;
1262
1817
  const givenAt = new Date(givenAtEpoch);
1818
+ const rawConsentAction = 'consentAction' in input ? input.consentAction : void 0;
1819
+ let derivedConsentAction;
1263
1820
  logger.debug('Request parameters', {
1264
1821
  type,
1265
1822
  subjectId,
@@ -1268,6 +1825,64 @@ const postSubjectHandler = async (c)=>{
1268
1825
  domain
1269
1826
  });
1270
1827
  try {
1828
+ if ('cookie_banner' === type) logger.warn('`cookie_banner` policy type is deprecated in 2.0 RC and will be removed in 2.0 GA. Use backend runtime `policyPacks` for banner behavior.');
1829
+ const request = c.req.raw ?? new Request('https://c15t.local/subjects');
1830
+ const acceptLanguage = request.headers.get('accept-language');
1831
+ const requestLanguage = parseLanguageFromHeader(acceptLanguage);
1832
+ const location = await getLocation(request, ctx);
1833
+ const resolvedJurisdiction = getJurisdiction(location, ctx);
1834
+ const legalDocumentConsent = isLegalDocumentType(type);
1835
+ const runtimeSnapshotVerification = legalDocumentConsent ? {
1836
+ valid: false,
1837
+ reason: 'missing'
1838
+ } : await verifyPolicySnapshotToken({
1839
+ token: input.policySnapshotToken,
1840
+ options: ctx.policySnapshot,
1841
+ tenantId: ctx.tenantId
1842
+ });
1843
+ const legalDocumentSnapshotVerification = legalDocumentConsent ? await verifyLegalDocumentSnapshotToken({
1844
+ token: input.documentSnapshotToken,
1845
+ options: ctx.legalDocumentSnapshot,
1846
+ tenantId: ctx.tenantId
1847
+ }) : {
1848
+ valid: false,
1849
+ reason: 'missing'
1850
+ };
1851
+ const hasValidSnapshot = runtimeSnapshotVerification.valid;
1852
+ const snapshotPayload = runtimeSnapshotVerification.valid ? runtimeSnapshotVerification.payload : null;
1853
+ const shouldRequireSnapshot = !legalDocumentConsent && !!ctx.policySnapshot?.signingKey && 'reject' === resolveSnapshotFailureMode(ctx);
1854
+ if (!hasValidSnapshot && shouldRequireSnapshot) throw buildSnapshotHttpException(runtimeSnapshotVerification.reason);
1855
+ const shouldRequireLegalDocumentSnapshot = legalDocumentConsent && !!ctx.legalDocumentSnapshot?.signingKey;
1856
+ if (shouldRequireLegalDocumentSnapshot && !legalDocumentSnapshotVerification.valid) throw buildLegalDocumentSnapshotHttpException(legalDocumentSnapshotVerification.reason);
1857
+ const resolvedPolicyDecision = hasValidSnapshot ? void 0 : legalDocumentConsent ? void 0 : await resolvePolicyDecision({
1858
+ policies: ctx.policyPacks,
1859
+ countryCode: location.countryCode,
1860
+ regionCode: location.regionCode,
1861
+ jurisdiction: resolvedJurisdiction,
1862
+ iabEnabled: ctx.iab?.enabled === true
1863
+ });
1864
+ const effectivePolicy = hasValidSnapshot && snapshotPayload ? {
1865
+ id: snapshotPayload.policyId,
1866
+ model: snapshotPayload.model,
1867
+ i18n: snapshotPayload.policyI18n,
1868
+ consent: {
1869
+ expiryDays: snapshotPayload.expiryDays,
1870
+ scopeMode: snapshotPayload.scopeMode,
1871
+ categories: snapshotPayload.categories,
1872
+ preselectedCategories: snapshotPayload.preselectedCategories,
1873
+ gpc: snapshotPayload.gpc
1874
+ },
1875
+ ui: {
1876
+ mode: snapshotPayload.uiMode,
1877
+ banner: snapshotPayload.bannerUi,
1878
+ dialog: snapshotPayload.dialogUi
1879
+ },
1880
+ proof: snapshotPayload.proofConfig
1881
+ } : resolvedPolicyDecision?.policy;
1882
+ const effectiveModel = effectivePolicy?.model ?? ('opt-in' === input.jurisdictionModel || 'opt-out' === input.jurisdictionModel || 'iab' === input.jurisdictionModel ? input.jurisdictionModel : void 0);
1883
+ if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
1884
+ else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === effectiveModel ? 'opt_out' : 'reject_all';
1885
+ else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
1271
1886
  const subject = await registry.findOrCreateSubject({
1272
1887
  subjectId,
1273
1888
  externalSubjectId,
@@ -1294,8 +1909,63 @@ const postSubjectHandler = async (c)=>{
1294
1909
  });
1295
1910
  let policyId;
1296
1911
  let purposeIds = [];
1912
+ let appliedPreferences;
1297
1913
  const inputPolicyId = 'policyId' in input ? input.policyId : void 0;
1298
- if (inputPolicyId) {
1914
+ const inputPolicyHash = 'policyHash' in input ? input.policyHash : void 0;
1915
+ if (legalDocumentConsent && legalDocumentSnapshotVerification.valid) {
1916
+ if (legalDocumentSnapshotVerification.payload.type !== type) throw buildLegalDocumentSnapshotHttpException('invalid');
1917
+ const effectiveDate = new Date(legalDocumentSnapshotVerification.payload.effectiveDate);
1918
+ if (Number.isNaN(effectiveDate.getTime())) throw buildLegalDocumentSnapshotHttpException('invalid');
1919
+ const documentPolicy = await registry.findOrCreateLegalDocumentPolicy({
1920
+ type,
1921
+ version: legalDocumentSnapshotVerification.payload.version,
1922
+ hash: legalDocumentSnapshotVerification.payload.hash,
1923
+ effectiveDate
1924
+ });
1925
+ policyId = documentPolicy.id;
1926
+ } else if (legalDocumentConsent) {
1927
+ if (!ctx.legalDocumentSnapshot?.signingKey && !inputPolicyId && !inputPolicyHash) throw buildLegalDocumentProofHttpException('Legal document consent requires policyId or policyHash when snapshot verification is disabled');
1928
+ if (inputPolicyId) {
1929
+ policyId = inputPolicyId;
1930
+ const policy = await registry.findConsentPolicyById(inputPolicyId);
1931
+ if (!policy) throw new http_exception_namespaceObject.HTTPException(404, {
1932
+ message: 'Policy not found',
1933
+ cause: {
1934
+ code: 'POLICY_NOT_FOUND',
1935
+ policyId,
1936
+ type
1937
+ }
1938
+ });
1939
+ if (!policy.isActive) throw new http_exception_namespaceObject.HTTPException(400, {
1940
+ message: 'Policy is inactive',
1941
+ cause: {
1942
+ code: 'POLICY_INACTIVE',
1943
+ policyId,
1944
+ type
1945
+ }
1946
+ });
1947
+ } else if (inputPolicyHash) {
1948
+ const policy = await registry.findLegalDocumentPolicyByHash(type, inputPolicyHash);
1949
+ if (!policy) throw new http_exception_namespaceObject.HTTPException(404, {
1950
+ message: 'Policy not found',
1951
+ cause: {
1952
+ code: 'POLICY_NOT_FOUND',
1953
+ type,
1954
+ policyHash: inputPolicyHash
1955
+ }
1956
+ });
1957
+ if (!policy.isActive) throw new http_exception_namespaceObject.HTTPException(400, {
1958
+ message: 'Policy is inactive',
1959
+ cause: {
1960
+ code: 'POLICY_INACTIVE',
1961
+ policyId: policy.id,
1962
+ type,
1963
+ policyHash: inputPolicyHash
1964
+ }
1965
+ });
1966
+ policyId = policy.id;
1967
+ }
1968
+ } else if (inputPolicyId) {
1299
1969
  policyId = inputPolicyId;
1300
1970
  const policy = await registry.findConsentPolicyById(inputPolicyId);
1301
1971
  if (!policy) throw new http_exception_namespaceObject.HTTPException(404, {
@@ -1326,20 +1996,73 @@ const postSubjectHandler = async (c)=>{
1326
1996
  policyId = policy.id;
1327
1997
  }
1328
1998
  if (preferences) {
1329
- const consentedPurposes = Object.entries(preferences).filter(([_, isConsented])=>isConsented).map(([purposeCode])=>purposeCode);
1999
+ const allowedCategories = effectivePolicy?.consent?.categories;
2000
+ const effectiveScopeMode = effectivePolicy?.consent?.scopeMode ?? 'permissive';
2001
+ const hasWildcardCategoryScope = allowedCategories?.includes('*') === true;
2002
+ const appliedPreferenceEntries = Object.entries(preferences);
2003
+ let filteredAppliedPreferenceEntries = appliedPreferenceEntries;
2004
+ if (allowedCategories && allowedCategories.length > 0 && !hasWildcardCategoryScope) {
2005
+ const disallowed = appliedPreferenceEntries.map(([purpose])=>purpose).filter((purpose)=>!allowedCategories.includes(purpose));
2006
+ filteredAppliedPreferenceEntries = appliedPreferenceEntries.filter(([purpose])=>allowedCategories.includes(purpose));
2007
+ if (disallowed.length > 0 && 'strict' === effectiveScopeMode) throw new http_exception_namespaceObject.HTTPException(400, {
2008
+ message: 'Preferences include categories not allowed by policy',
2009
+ cause: {
2010
+ code: 'PURPOSE_NOT_ALLOWED',
2011
+ disallowed
2012
+ }
2013
+ });
2014
+ }
2015
+ appliedPreferences = Object.fromEntries(filteredAppliedPreferenceEntries);
2016
+ const filteredConsentedPurposeCodes = filteredAppliedPreferenceEntries.filter(([_, isConsented])=>isConsented).map(([purposeCode])=>purposeCode);
1330
2017
  logger.debug('Consented purposes', {
1331
- consentedPurposes
2018
+ consentedPurposes: filteredConsentedPurposeCodes
1332
2019
  });
1333
- const purposesRaw = await Promise.all(consentedPurposes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
2020
+ const purposesRaw = await Promise.all(filteredConsentedPurposeCodes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
1334
2021
  const purposes = purposesRaw.map((purpose)=>purpose?.id ?? null).filter((id)=>Boolean(id));
1335
2022
  logger.debug('Filtered purposes', {
1336
2023
  purposes
1337
2024
  });
1338
2025
  if (0 === purposes.length) logger.warn('No valid purpose IDs found after filtering. Using empty list.', {
1339
- consentedPurposes
2026
+ consentedPurposes: filteredConsentedPurposeCodes
1340
2027
  });
1341
2028
  purposeIds = purposes;
1342
2029
  }
2030
+ if (!policyId) throw new http_exception_namespaceObject.HTTPException(500, {
2031
+ message: 'Failed to resolve policy',
2032
+ cause: {
2033
+ code: 'POLICY_RESOLUTION_FAILED',
2034
+ type
2035
+ }
2036
+ });
2037
+ const expiryDays = effectivePolicy?.consent?.expiryDays;
2038
+ const validUntil = 'number' == typeof expiryDays && Number.isFinite(expiryDays) ? new Date(givenAt.getTime() + 86400000 * Math.max(0, expiryDays)) : void 0;
2039
+ const proofConfig = effectivePolicy?.proof;
2040
+ const shouldStoreIp = proofConfig?.storeIp ?? true;
2041
+ const shouldStoreUserAgent = proofConfig?.storeUserAgent ?? true;
2042
+ const shouldStoreLanguage = proofConfig?.storeLanguage ?? false;
2043
+ const effectiveLanguage = (snapshotPayload?.language && hasValidSnapshot ? snapshotPayload.language : requestLanguage) ?? void 0;
2044
+ const metadataWithPolicy = {
2045
+ ...metadata ?? {},
2046
+ ...shouldStoreLanguage && effectiveLanguage ? {
2047
+ policyLanguage: effectiveLanguage
2048
+ } : {}
2049
+ };
2050
+ const effectiveJurisdiction = hasValidSnapshot && snapshotPayload ? snapshotPayload.jurisdiction : resolvedJurisdiction;
2051
+ const decisionPayload = buildDecisionPayload({
2052
+ tenantId: ctx.tenantId,
2053
+ snapshot: hasValidSnapshot && snapshotPayload ? {
2054
+ valid: true,
2055
+ payload: snapshotPayload
2056
+ } : null,
2057
+ decision: resolvedPolicyDecision,
2058
+ location: {
2059
+ countryCode: location.countryCode,
2060
+ regionCode: location.regionCode
2061
+ },
2062
+ jurisdiction: resolvedJurisdiction,
2063
+ language: effectiveLanguage,
2064
+ proofConfig
2065
+ });
1343
2066
  const existingConsent = await db.findFirst('consent', {
1344
2067
  where: (b)=>b.and(b('subjectId', '=', subject.id), b('domainId', '=', domainRecord.id), b('policyId', '=', policyId), b('givenAt', '=', givenAt))
1345
2068
  });
@@ -1354,6 +2077,7 @@ const postSubjectHandler = async (c)=>{
1354
2077
  domain: domainRecord.name,
1355
2078
  type,
1356
2079
  metadata,
2080
+ appliedPreferences,
1357
2081
  uiSource: input.uiSource,
1358
2082
  givenAt: existingConsent.givenAt
1359
2083
  });
@@ -1365,24 +2089,64 @@ const postSubjectHandler = async (c)=>{
1365
2089
  policyId,
1366
2090
  purposeIds
1367
2091
  });
2092
+ const runtimePolicyDecision = decisionPayload ? await tx.findFirst('runtimePolicyDecision', {
2093
+ where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
2094
+ }) ?? await tx.create('runtimePolicyDecision', {
2095
+ id: `rpd_${crypto.randomUUID().replaceAll('-', '')}`,
2096
+ tenantId: decisionPayload.tenantId,
2097
+ policyId: decisionPayload.policyId,
2098
+ fingerprint: decisionPayload.fingerprint,
2099
+ matchedBy: decisionPayload.matchedBy,
2100
+ countryCode: decisionPayload.countryCode,
2101
+ regionCode: decisionPayload.regionCode,
2102
+ jurisdiction: decisionPayload.jurisdiction,
2103
+ language: decisionPayload.language,
2104
+ model: decisionPayload.model,
2105
+ policyI18n: decisionPayload.policyI18n ? {
2106
+ json: decisionPayload.policyI18n
2107
+ } : void 0,
2108
+ uiMode: decisionPayload.uiMode,
2109
+ bannerUi: decisionPayload.bannerUi ? {
2110
+ json: decisionPayload.bannerUi
2111
+ } : void 0,
2112
+ dialogUi: decisionPayload.dialogUi ? {
2113
+ json: decisionPayload.dialogUi
2114
+ } : void 0,
2115
+ categories: decisionPayload.categories ? {
2116
+ json: decisionPayload.categories
2117
+ } : void 0,
2118
+ preselectedCategories: decisionPayload.preselectedCategories ? {
2119
+ json: decisionPayload.preselectedCategories
2120
+ } : void 0,
2121
+ proofConfig: decisionPayload.proofConfig ? {
2122
+ json: decisionPayload.proofConfig
2123
+ } : void 0,
2124
+ dedupeKey: decisionPayload.dedupeKey
2125
+ }).catch(async ()=>tx.findFirst('runtimePolicyDecision', {
2126
+ where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
2127
+ })) : void 0;
1368
2128
  const consentRecord = await tx.create('consent', {
1369
- id: await generateUniqueId(tx, 'consent', ctx),
2129
+ id: await utils_generateUniqueId(tx, 'consent', ctx),
1370
2130
  subjectId: subject.id,
1371
2131
  domainId: domainRecord.id,
1372
2132
  policyId,
1373
2133
  purposeIds: {
1374
2134
  json: purposeIds
1375
2135
  },
1376
- metadata: metadata ? {
1377
- json: metadata
2136
+ metadata: Object.keys(metadataWithPolicy).length > 0 ? {
2137
+ json: metadataWithPolicy
1378
2138
  } : void 0,
1379
- ipAddress: ctx.ipAddress,
1380
- userAgent: ctx.userAgent,
1381
- jurisdiction: input.jurisdiction,
1382
- jurisdictionModel: input.jurisdictionModel,
2139
+ ipAddress: shouldStoreIp ? ctx.ipAddress : null,
2140
+ userAgent: shouldStoreUserAgent ? ctx.userAgent : null,
2141
+ jurisdiction: effectiveJurisdiction,
2142
+ jurisdictionModel: effectiveModel,
1383
2143
  tcString: input.tcString,
1384
2144
  uiSource: input.uiSource,
1385
- givenAt
2145
+ consentAction: derivedConsentAction,
2146
+ givenAt,
2147
+ validUntil,
2148
+ runtimePolicyDecisionId: runtimePolicyDecision?.id,
2149
+ runtimePolicySource: decisionPayload?.source
1386
2150
  });
1387
2151
  logger.debug('Created consent', {
1388
2152
  consentRecord: consentRecord.id
@@ -1399,9 +2163,9 @@ const postSubjectHandler = async (c)=>{
1399
2163
  consent: consentRecord
1400
2164
  };
1401
2165
  });
1402
- const metrics = getMetrics();
2166
+ const metrics = metrics_getMetrics();
1403
2167
  if (metrics) {
1404
- const jurisdiction = input.jurisdiction;
2168
+ const jurisdiction = effectiveJurisdiction;
1405
2169
  metrics.recordConsentCreated({
1406
2170
  type,
1407
2171
  jurisdiction
@@ -1423,15 +2187,22 @@ const postSubjectHandler = async (c)=>{
1423
2187
  domain: domainRecord.name,
1424
2188
  type,
1425
2189
  metadata,
2190
+ appliedPreferences,
1426
2191
  uiSource: input.uiSource,
1427
2192
  givenAt: result.consent.givenAt
1428
2193
  });
1429
2194
  } catch (error) {
1430
2195
  logger.error('Error in POST /subjects handler', {
1431
- error: extractErrorMessage(error),
2196
+ error: extract_error_message_extractErrorMessage(error),
1432
2197
  errorType: error instanceof Error ? error.constructor.name : typeof error
1433
2198
  });
1434
2199
  if (error instanceof http_exception_namespaceObject.HTTPException) throw error;
2200
+ if (error instanceof consent_policy_LegalDocumentPolicyConflictError) throw new http_exception_namespaceObject.HTTPException(409, {
2201
+ message: error.message,
2202
+ cause: {
2203
+ code: 'LEGAL_DOCUMENT_RELEASE_CONFLICT'
2204
+ }
2205
+ });
1435
2206
  throw new http_exception_namespaceObject.HTTPException(500, {
1436
2207
  message: 'Internal server error',
1437
2208
  cause: {
@@ -1444,11 +2215,7 @@ const createSubjectRoutes = ()=>{
1444
2215
  const app = new external_hono_namespaceObject.Hono();
1445
2216
  app.get('/:id', (0, external_hono_openapi_namespaceObject.describeRoute)({
1446
2217
  summary: 'Get subject consent status',
1447
- description: `Returns the subject's consent status for this device. Use to check if the subject has valid consent for given policy types.
1448
-
1449
- **Query:** \`type\` – Filter by consent type(s), comma-separated (e.g. \`privacy_policy,cookie_banner\`).
1450
-
1451
- **Response:** \`subject\`, \`consents\` (matching filter), \`isValid\` (valid consent for requested type(s)).`,
2218
+ description: "Returns the subject's consent status for this device. Use to check if the subject has valid consent for given policy types.\n\n**Query:** `type` – Filter by consent type(s), comma-separated (e.g. `privacy_policy,cookie_banner`).\n\n**Response:** `subject`, `consents` (matching filter), `isValid` (valid consent for requested type(s)).",
1452
2219
  tags: [
1453
2220
  'Subject',
1454
2221
  'Consent'
@@ -1469,12 +2236,7 @@ const createSubjectRoutes = ()=>{
1469
2236
  }), (0, external_hono_openapi_namespaceObject.validator)('param', schema_namespaceObject.getSubjectInputSchema), getSubjectHandler);
1470
2237
  app.post('/', (0, external_hono_openapi_namespaceObject.describeRoute)({
1471
2238
  summary: 'Record consent for a subject',
1472
- description: `Creates a new consent record (append-only). Creates the subject if it does not exist.
1473
-
1474
- **Request body by \`type\`:**
1475
- - \`cookie_banner\` – Requires \`preferences\` object
1476
- - \`privacy_policy\`, \`dpa\`, \`terms_and_conditions\` – Optional \`policyId\`
1477
- - \`marketing_communications\`, \`age_verification\`, \`other\` – Optional \`preferences\``,
2239
+ description: "Creates a new consent record (append-only). Creates the subject if it does not exist.\n\n**Request body by `type`:**\n- `cookie_banner` – Requires `preferences` object\n- `privacy_policy`, `dpa`, `terms_and_conditions` – Prefer a signed `documentSnapshotToken`; otherwise use a release `policyHash`, with `policyId` kept only for compatibility\n- `marketing_communications`, `age_verification`, `other` – Optional `preferences`",
1478
2240
  tags: [
1479
2241
  'Subject',
1480
2242
  'Consent'