@c15t/backend 2.0.0-rc.3 → 2.0.0-rc.5

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 (314) hide show
  1. package/dist/cache.cjs +4 -4
  2. package/dist/cache.js +4 -4
  3. package/dist/core.cjs +845 -87
  4. package/dist/core.js +821 -87
  5. package/dist/db/schema.cjs +37 -0
  6. package/dist/db/schema.js +33 -2
  7. package/dist/edge.cjs +1106 -0
  8. package/dist/edge.js +1069 -0
  9. package/dist/router.cjs +621 -71
  10. package/dist/router.js +621 -71
  11. package/{dist → dist-types}/cache/adapters/cloudflare-kv.d.ts +0 -1
  12. package/{dist → dist-types}/cache/adapters/index.d.ts +0 -1
  13. package/{dist → dist-types}/cache/adapters/memory.d.ts +0 -1
  14. package/{dist → dist-types}/cache/adapters/upstash-redis.d.ts +0 -1
  15. package/{dist → dist-types}/cache/gvl-resolver.d.ts +1 -2
  16. package/{dist → dist-types}/cache/index.d.ts +0 -1
  17. package/{dist → dist-types}/cache/keys.d.ts +0 -1
  18. package/{dist → dist-types}/cache/types.d.ts +0 -1
  19. package/{dist → dist-types}/core.d.ts +8 -1
  20. package/{dist → dist-types}/db/migrator/index.d.ts +0 -1
  21. package/{dist → dist-types}/db/registry/consent-policy.d.ts +0 -1
  22. package/{dist → dist-types}/db/registry/consent-purpose.d.ts +0 -1
  23. package/{dist → dist-types}/db/registry/domain.d.ts +0 -1
  24. package/{dist → dist-types}/db/registry/index.d.ts +22 -2
  25. package/dist-types/db/registry/runtime-policy-decision.d.ts +60 -0
  26. package/{dist → dist-types}/db/registry/subject.d.ts +0 -1
  27. package/{dist → dist-types}/db/registry/types.d.ts +1 -2
  28. package/{dist → dist-types}/db/registry/utils/generate-id.d.ts +0 -1
  29. package/{dist → dist-types}/db/registry/utils.d.ts +0 -1
  30. package/{dist → dist-types}/db/schema/1.0.0/audit-log.d.ts +0 -1
  31. package/{dist → dist-types}/db/schema/1.0.0/consent-policy.d.ts +0 -1
  32. package/{dist → dist-types}/db/schema/1.0.0/consent-purpose.d.ts +0 -1
  33. package/{dist → dist-types}/db/schema/1.0.0/consent-record.d.ts +0 -1
  34. package/{dist → dist-types}/db/schema/1.0.0/consent.d.ts +1 -2
  35. package/{dist → dist-types}/db/schema/1.0.0/domain.d.ts +0 -1
  36. package/{dist → dist-types}/db/schema/1.0.0/index.d.ts +0 -1
  37. package/{dist → dist-types}/db/schema/1.0.0/subject.d.ts +0 -1
  38. package/{dist → dist-types}/db/schema/2.0.0/audit-log.d.ts +1 -2
  39. package/{dist → dist-types}/db/schema/2.0.0/consent-policy.d.ts +1 -2
  40. package/{dist → dist-types}/db/schema/2.0.0/consent-purpose.d.ts +1 -2
  41. package/{dist → dist-types}/db/schema/2.0.0/consent.d.ts +5 -2
  42. package/{dist → dist-types}/db/schema/2.0.0/domain.d.ts +1 -2
  43. package/{dist → dist-types}/db/schema/2.0.0/index.d.ts +432 -17
  44. package/dist-types/db/schema/2.0.0/runtime-policy-decision.d.ts +23 -0
  45. package/{dist → dist-types}/db/schema/2.0.0/subject.d.ts +1 -2
  46. package/{dist → dist-types}/db/schema/index.d.ts +862 -33
  47. package/{dist → dist-types}/db/tenant-scope.d.ts +0 -1
  48. package/dist-types/define-config.d.ts +17 -0
  49. package/dist-types/edge/index.d.ts +5 -0
  50. package/dist-types/edge/init-handler.d.ts +38 -0
  51. package/dist-types/edge/resolve-consent.d.ts +80 -0
  52. package/dist-types/edge/types.d.ts +13 -0
  53. package/{dist → dist-types}/handlers/consent/check.handler.d.ts +0 -1
  54. package/{src/handlers/consent/index.ts → dist-types/handlers/consent/index.d.ts} +0 -1
  55. package/{dist → dist-types}/handlers/init/geo.d.ts +2 -3
  56. package/{dist → dist-types}/handlers/init/index.d.ts +4 -5
  57. package/dist-types/handlers/init/policy.d.ts +26 -0
  58. package/dist-types/handlers/init/resolve-init.d.ts +44 -0
  59. package/dist-types/handlers/init/translations.d.ts +48 -0
  60. package/dist-types/handlers/policy/snapshot.d.ts +99 -0
  61. package/{src/handlers/status/index.ts → dist-types/handlers/status/index.d.ts} +0 -1
  62. package/{dist → dist-types}/handlers/status/status.handler.d.ts +0 -1
  63. package/{dist → dist-types}/handlers/subject/get.handler.d.ts +0 -1
  64. package/{src/handlers/subject/index.ts → dist-types/handlers/subject/index.d.ts} +0 -1
  65. package/{dist → dist-types}/handlers/subject/list.handler.d.ts +0 -1
  66. package/{dist → dist-types}/handlers/subject/patch.handler.d.ts +0 -1
  67. package/{dist → dist-types}/handlers/subject/post.handler.d.ts +12 -1
  68. package/{dist → dist-types}/handlers/utils/consent-enrichment.d.ts +0 -1
  69. package/{dist → dist-types}/init.d.ts +4 -7
  70. package/{dist → dist-types}/middleware/auth/index.d.ts +0 -1
  71. package/{dist → dist-types}/middleware/auth/validate-api-key.d.ts +0 -1
  72. package/{dist → dist-types}/middleware/cors/cors.d.ts +0 -1
  73. package/{src/middleware/cors/index.ts → dist-types/middleware/cors/index.d.ts} +0 -1
  74. package/{dist → dist-types}/middleware/cors/is-origin-trusted.d.ts +1 -2
  75. package/{dist → dist-types}/middleware/cors/process-cors.d.ts +0 -1
  76. package/{dist → dist-types}/middleware/openapi/config.d.ts +0 -1
  77. package/{dist → dist-types}/middleware/openapi/handlers.d.ts +0 -1
  78. package/{src/middleware/openapi/index.ts → dist-types/middleware/openapi/index.d.ts} +0 -1
  79. package/{dist → dist-types}/middleware/process-ip/index.d.ts +0 -1
  80. package/dist-types/policies/builder.d.ts +127 -0
  81. package/dist-types/policies/defaults.d.ts +2 -0
  82. package/dist-types/policies/matchers.d.ts +3 -0
  83. package/{dist → dist-types}/router.d.ts +0 -1
  84. package/{dist → dist-types}/routes/consent.d.ts +0 -1
  85. package/{src/routes/index.ts → dist-types/routes/index.d.ts} +0 -1
  86. package/{dist → dist-types}/routes/init.d.ts +0 -1
  87. package/{dist → dist-types}/routes/status.d.ts +0 -1
  88. package/{dist → dist-types}/routes/subject.d.ts +0 -1
  89. package/{dist → dist-types}/types/api.d.ts +0 -1
  90. package/dist-types/types/index.d.ts +443 -0
  91. package/dist-types/utils/background.d.ts +6 -0
  92. package/{dist → dist-types}/utils/create-telemetry-options.d.ts +1 -2
  93. package/{dist → dist-types}/utils/env.d.ts +0 -1
  94. package/{dist → dist-types}/utils/extract-error-message.d.ts +0 -1
  95. package/{dist → dist-types}/utils/instrumentation.d.ts +0 -1
  96. package/{dist → dist-types}/utils/logger.d.ts +1 -2
  97. package/{dist → dist-types}/utils/metrics.d.ts +0 -1
  98. package/dist-types/version.d.ts +1 -0
  99. package/docs/README.md +49 -0
  100. package/docs/api/configuration.md +197 -0
  101. package/docs/api/endpoints.md +211 -0
  102. package/docs/guides/caching.md +85 -0
  103. package/docs/guides/database-setup.md +128 -0
  104. package/docs/guides/edge-deployment.md +248 -0
  105. package/docs/guides/framework-integration.md +142 -0
  106. package/docs/guides/iab-tcf.md +89 -0
  107. package/docs/guides/observability.md +96 -0
  108. package/docs/guides/policy-packs.md +396 -0
  109. package/docs/quickstart.md +129 -0
  110. package/package.json +37 -23
  111. package/.turbo/turbo-build.log +0 -49
  112. package/CHANGELOG.md +0 -115
  113. package/dist/cache/adapters/cloudflare-kv.d.ts.map +0 -1
  114. package/dist/cache/adapters/index.d.ts.map +0 -1
  115. package/dist/cache/adapters/memory.d.ts.map +0 -1
  116. package/dist/cache/adapters/upstash-redis.d.ts.map +0 -1
  117. package/dist/cache/gvl-resolver.d.ts.map +0 -1
  118. package/dist/cache/index.d.ts.map +0 -1
  119. package/dist/cache/keys.d.ts.map +0 -1
  120. package/dist/cache/types.d.ts.map +0 -1
  121. package/dist/core.d.ts.map +0 -1
  122. package/dist/db/adapters/drizzle.d.ts +0 -2
  123. package/dist/db/adapters/drizzle.d.ts.map +0 -1
  124. package/dist/db/adapters/index.d.ts +0 -2
  125. package/dist/db/adapters/index.d.ts.map +0 -1
  126. package/dist/db/adapters/kysely.d.ts +0 -2
  127. package/dist/db/adapters/kysely.d.ts.map +0 -1
  128. package/dist/db/adapters/mongo.d.ts +0 -2
  129. package/dist/db/adapters/mongo.d.ts.map +0 -1
  130. package/dist/db/adapters/prisma.d.ts +0 -2
  131. package/dist/db/adapters/prisma.d.ts.map +0 -1
  132. package/dist/db/adapters/typeorm.d.ts +0 -2
  133. package/dist/db/adapters/typeorm.d.ts.map +0 -1
  134. package/dist/db/migrator/index.d.ts.map +0 -1
  135. package/dist/db/registry/consent-policy.d.ts.map +0 -1
  136. package/dist/db/registry/consent-purpose.d.ts.map +0 -1
  137. package/dist/db/registry/domain.d.ts.map +0 -1
  138. package/dist/db/registry/index.d.ts.map +0 -1
  139. package/dist/db/registry/subject.d.ts.map +0 -1
  140. package/dist/db/registry/types.d.ts.map +0 -1
  141. package/dist/db/registry/utils/generate-id.d.ts.map +0 -1
  142. package/dist/db/registry/utils.d.ts.map +0 -1
  143. package/dist/db/schema/1.0.0/audit-log.d.ts.map +0 -1
  144. package/dist/db/schema/1.0.0/consent-policy.d.ts.map +0 -1
  145. package/dist/db/schema/1.0.0/consent-purpose.d.ts.map +0 -1
  146. package/dist/db/schema/1.0.0/consent-record.d.ts.map +0 -1
  147. package/dist/db/schema/1.0.0/consent.d.ts.map +0 -1
  148. package/dist/db/schema/1.0.0/domain.d.ts.map +0 -1
  149. package/dist/db/schema/1.0.0/index.d.ts.map +0 -1
  150. package/dist/db/schema/1.0.0/subject.d.ts.map +0 -1
  151. package/dist/db/schema/2.0.0/audit-log.d.ts.map +0 -1
  152. package/dist/db/schema/2.0.0/consent-policy.d.ts.map +0 -1
  153. package/dist/db/schema/2.0.0/consent-purpose.d.ts.map +0 -1
  154. package/dist/db/schema/2.0.0/consent.d.ts.map +0 -1
  155. package/dist/db/schema/2.0.0/domain.d.ts.map +0 -1
  156. package/dist/db/schema/2.0.0/index.d.ts.map +0 -1
  157. package/dist/db/schema/2.0.0/subject.d.ts.map +0 -1
  158. package/dist/db/schema/index.d.ts.map +0 -1
  159. package/dist/db/tenant-scope.d.ts.map +0 -1
  160. package/dist/define-config.d.ts +0 -5
  161. package/dist/define-config.d.ts.map +0 -1
  162. package/dist/handlers/consent/check.handler.d.ts.map +0 -1
  163. package/dist/handlers/consent/index.d.ts +0 -12
  164. package/dist/handlers/consent/index.d.ts.map +0 -1
  165. package/dist/handlers/init/geo.d.ts.map +0 -1
  166. package/dist/handlers/init/index.d.ts.map +0 -1
  167. package/dist/handlers/init/translations.d.ts +0 -28
  168. package/dist/handlers/init/translations.d.ts.map +0 -1
  169. package/dist/handlers/status/index.d.ts +0 -7
  170. package/dist/handlers/status/index.d.ts.map +0 -1
  171. package/dist/handlers/status/status.handler.d.ts.map +0 -1
  172. package/dist/handlers/subject/get.handler.d.ts.map +0 -1
  173. package/dist/handlers/subject/index.d.ts +0 -10
  174. package/dist/handlers/subject/index.d.ts.map +0 -1
  175. package/dist/handlers/subject/list.handler.d.ts.map +0 -1
  176. package/dist/handlers/subject/patch.handler.d.ts.map +0 -1
  177. package/dist/handlers/subject/post.handler.d.ts.map +0 -1
  178. package/dist/handlers/utils/consent-enrichment.d.ts.map +0 -1
  179. package/dist/init.d.ts.map +0 -1
  180. package/dist/middleware/auth/index.d.ts.map +0 -1
  181. package/dist/middleware/auth/validate-api-key.d.ts.map +0 -1
  182. package/dist/middleware/cors/cors.d.ts.map +0 -1
  183. package/dist/middleware/cors/index.d.ts +0 -30
  184. package/dist/middleware/cors/index.d.ts.map +0 -1
  185. package/dist/middleware/cors/is-origin-trusted.d.ts.map +0 -1
  186. package/dist/middleware/cors/process-cors.d.ts.map +0 -1
  187. package/dist/middleware/openapi/config.d.ts.map +0 -1
  188. package/dist/middleware/openapi/handlers.d.ts.map +0 -1
  189. package/dist/middleware/openapi/index.d.ts +0 -12
  190. package/dist/middleware/openapi/index.d.ts.map +0 -1
  191. package/dist/middleware/process-ip/index.d.ts.map +0 -1
  192. package/dist/router.d.ts.map +0 -1
  193. package/dist/routes/consent.d.ts.map +0 -1
  194. package/dist/routes/index.d.ts +0 -10
  195. package/dist/routes/index.d.ts.map +0 -1
  196. package/dist/routes/init.d.ts.map +0 -1
  197. package/dist/routes/status.d.ts.map +0 -1
  198. package/dist/routes/subject.d.ts.map +0 -1
  199. package/dist/types/api.d.ts.map +0 -1
  200. package/dist/types/index.d.ts +0 -263
  201. package/dist/types/index.d.ts.map +0 -1
  202. package/dist/utils/create-telemetry-options.d.ts.map +0 -1
  203. package/dist/utils/env.d.ts.map +0 -1
  204. package/dist/utils/extract-error-message.d.ts.map +0 -1
  205. package/dist/utils/index.d.ts +0 -4
  206. package/dist/utils/index.d.ts.map +0 -1
  207. package/dist/utils/instrumentation.d.ts.map +0 -1
  208. package/dist/utils/logger.d.ts.map +0 -1
  209. package/dist/utils/metrics.d.ts.map +0 -1
  210. package/dist/version.d.ts +0 -2
  211. package/dist/version.d.ts.map +0 -1
  212. package/knip.json +0 -31
  213. package/rslib.config.ts +0 -93
  214. package/src/cache/adapters/cloudflare-kv.ts +0 -71
  215. package/src/cache/adapters/index.ts +0 -22
  216. package/src/cache/adapters/memory.ts +0 -111
  217. package/src/cache/adapters/upstash-redis.ts +0 -113
  218. package/src/cache/gvl-resolver.ts +0 -289
  219. package/src/cache/index.ts +0 -34
  220. package/src/cache/keys.ts +0 -68
  221. package/src/cache/types.ts +0 -66
  222. package/src/core.ts +0 -369
  223. package/src/db/migrator/index.ts +0 -80
  224. package/src/db/registry/consent-policy.test.ts +0 -451
  225. package/src/db/registry/consent-policy.ts +0 -82
  226. package/src/db/registry/consent-purpose.test.ts +0 -428
  227. package/src/db/registry/consent-purpose.ts +0 -61
  228. package/src/db/registry/domain.test.ts +0 -445
  229. package/src/db/registry/domain.ts +0 -91
  230. package/src/db/registry/index.ts +0 -14
  231. package/src/db/registry/subject.test.ts +0 -371
  232. package/src/db/registry/subject.ts +0 -126
  233. package/src/db/registry/types.ts +0 -10
  234. package/src/db/registry/utils/generate-id.test.ts +0 -216
  235. package/src/db/registry/utils/generate-id.ts +0 -133
  236. package/src/db/registry/utils.ts +0 -133
  237. package/src/db/schema/1.0.0/audit-log.ts +0 -15
  238. package/src/db/schema/1.0.0/consent-policy.ts +0 -14
  239. package/src/db/schema/1.0.0/consent-purpose.ts +0 -14
  240. package/src/db/schema/1.0.0/consent-record.ts +0 -10
  241. package/src/db/schema/1.0.0/consent.ts +0 -20
  242. package/src/db/schema/1.0.0/domain.ts +0 -12
  243. package/src/db/schema/1.0.0/index.ts +0 -48
  244. package/src/db/schema/1.0.0/subject.ts +0 -11
  245. package/src/db/schema/2.0.0/audit-log.ts +0 -18
  246. package/src/db/schema/2.0.0/consent-policy.ts +0 -28
  247. package/src/db/schema/2.0.0/consent-purpose.ts +0 -12
  248. package/src/db/schema/2.0.0/consent.ts +0 -28
  249. package/src/db/schema/2.0.0/domain.ts +0 -12
  250. package/src/db/schema/2.0.0/index.ts +0 -47
  251. package/src/db/schema/2.0.0/subject.ts +0 -13
  252. package/src/db/schema/index.ts +0 -15
  253. package/src/db/tenant-scope.test.ts +0 -747
  254. package/src/db/tenant-scope.ts +0 -103
  255. package/src/define-config.ts +0 -5
  256. package/src/handlers/consent/check.handler.ts +0 -126
  257. package/src/handlers/init/geo.test.ts +0 -317
  258. package/src/handlers/init/geo.ts +0 -195
  259. package/src/handlers/init/index.test.ts +0 -205
  260. package/src/handlers/init/index.ts +0 -114
  261. package/src/handlers/init/translations.test.ts +0 -121
  262. package/src/handlers/init/translations.ts +0 -72
  263. package/src/handlers/status/status.handler.test.ts +0 -155
  264. package/src/handlers/status/status.handler.ts +0 -51
  265. package/src/handlers/subject/get.handler.ts +0 -92
  266. package/src/handlers/subject/list.handler.ts +0 -92
  267. package/src/handlers/subject/patch.handler.ts +0 -119
  268. package/src/handlers/subject/post.handler.test.ts +0 -294
  269. package/src/handlers/subject/post.handler.ts +0 -268
  270. package/src/handlers/utils/consent-enrichment.test.ts +0 -380
  271. package/src/handlers/utils/consent-enrichment.ts +0 -218
  272. package/src/init.test.ts +0 -126
  273. package/src/init.ts +0 -87
  274. package/src/middleware/auth/index.ts +0 -11
  275. package/src/middleware/auth/validate-api-key.test.ts +0 -86
  276. package/src/middleware/auth/validate-api-key.ts +0 -107
  277. package/src/middleware/cors/cors.test.ts +0 -135
  278. package/src/middleware/cors/cors.ts +0 -186
  279. package/src/middleware/cors/is-origin-trusted.test.ts +0 -164
  280. package/src/middleware/cors/is-origin-trusted.ts +0 -130
  281. package/src/middleware/cors/process-cors.ts +0 -91
  282. package/src/middleware/openapi/config.ts +0 -29
  283. package/src/middleware/openapi/handlers.ts +0 -34
  284. package/src/middleware/process-ip/index.test.ts +0 -195
  285. package/src/middleware/process-ip/index.ts +0 -199
  286. package/src/router.ts +0 -15
  287. package/src/routes/consent.ts +0 -52
  288. package/src/routes/init.ts +0 -105
  289. package/src/routes/status.ts +0 -46
  290. package/src/routes/subject.ts +0 -152
  291. package/src/types/api.ts +0 -48
  292. package/src/types/index.ts +0 -297
  293. package/src/utils/create-telemetry-options.test.ts +0 -302
  294. package/src/utils/create-telemetry-options.ts +0 -229
  295. package/src/utils/env.ts +0 -84
  296. package/src/utils/extract-error-message.ts +0 -21
  297. package/src/utils/instrumentation.test.ts +0 -185
  298. package/src/utils/instrumentation.ts +0 -196
  299. package/src/utils/logger.ts +0 -41
  300. package/src/utils/metrics.test.ts +0 -323
  301. package/src/utils/metrics.ts +0 -402
  302. package/src/utils/telemetry-pii.test.ts +0 -325
  303. package/src/version.ts +0 -2
  304. package/tsconfig.json +0 -11
  305. package/vitest.config.ts +0 -28
  306. /package/dist/{types.cjs → types/index.cjs} +0 -0
  307. /package/dist/{types.js → types/index.js} +0 -0
  308. /package/{src/db/adapters/drizzle.ts → dist-types/db/adapters/drizzle.d.ts} +0 -0
  309. /package/{src/db/adapters/index.ts → dist-types/db/adapters/index.d.ts} +0 -0
  310. /package/{src/db/adapters/kysely.ts → dist-types/db/adapters/kysely.d.ts} +0 -0
  311. /package/{src/db/adapters/mongo.ts → dist-types/db/adapters/mongo.d.ts} +0 -0
  312. /package/{src/db/adapters/prisma.ts → dist-types/db/adapters/prisma.d.ts} +0 -0
  313. /package/{src/db/adapters/typeorm.ts → dist-types/db/adapters/typeorm.d.ts} +0 -0
  314. /package/{src/utils/index.ts → dist-types/utils/index.d.ts} +0 -0
package/dist/router.js CHANGED
@@ -3,7 +3,10 @@ import { Hono } from "hono";
3
3
  import { describeRoute, resolver, validator } from "hono-openapi";
4
4
  import { HTTPException } from "hono/http-exception";
5
5
  import { SpanKind as api_SpanKind, SpanStatusCode as api_SpanStatusCode, context, metrics as api_metrics, trace as api_trace } from "@opentelemetry/api";
6
- import { baseTranslations, deepMergeTranslations, selectLanguage } from "@c15t/translations";
6
+ import { SignJWT, errors, jwtVerify } from "jose";
7
+ import { resolvePolicyDecision } from "@c15t/schema/types";
8
+ import { deepMergeTranslations, selectLanguage } from "@c15t/translations";
9
+ import { baseTranslations } from "@c15t/translations/all";
7
10
  import { object, optional, string } from "valibot";
8
11
  import base_x from "base-x";
9
12
  function extractErrorMessage(error) {
@@ -14,22 +17,22 @@ function extractErrorMessage(error) {
14
17
  if (error instanceof Error) return error.message || error.name;
15
18
  return String(error);
16
19
  }
17
- const version_version = '2.0.0-rc.3';
20
+ const version_version = '2.0.0-rc.5';
18
21
  let cachedConfig = null;
19
22
  let cachedDefaultAttributes = {};
20
23
  function create_telemetry_options_isTelemetryEnabled(options) {
21
- if (options) return options.advanced?.telemetry?.enabled === true;
24
+ if (options) return options.telemetry?.enabled === true;
22
25
  return cachedConfig?.enabled === true;
23
26
  }
24
27
  const create_telemetry_options_getTracer = (options)=>{
25
28
  if (!create_telemetry_options_isTelemetryEnabled(options)) return api_trace.getTracer('c15t-noop');
26
- const tracer = options?.advanced?.telemetry?.tracer ?? cachedConfig?.tracer;
29
+ const tracer = options?.telemetry?.tracer ?? cachedConfig?.tracer;
27
30
  if (tracer) return tracer;
28
31
  return api_trace.getTracer(options?.appName ?? 'c15t');
29
32
  };
30
33
  const getMeter = (options)=>{
31
34
  if (!create_telemetry_options_isTelemetryEnabled(options)) return api_metrics.getMeter('c15t-noop');
32
- const meter = options?.advanced?.telemetry?.meter ?? cachedConfig?.meter;
35
+ const meter = options?.telemetry?.meter ?? cachedConfig?.meter;
33
36
  if (meter) return meter;
34
37
  return api_metrics.getMeter(options?.appName ?? 'c15t');
35
38
  };
@@ -434,7 +437,7 @@ async function executeWithSpan(span, operation) {
434
437
  }
435
438
  }
436
439
  function resolveDefaultAttributes(options) {
437
- return options?.advanced?.telemetry?.defaultAttributes || getDefaultAttributes();
440
+ return options?.telemetry?.defaultAttributes || getDefaultAttributes();
438
441
  }
439
442
  async function withExternalSpan(attributes, operation, options) {
440
443
  if (!create_telemetry_options_isTelemetryEnabled(options)) return operation();
@@ -610,6 +613,123 @@ function createGVLResolver(options) {
610
613
  }
611
614
  };
612
615
  }
616
+ const POLICY_SNAPSHOT_JWT_HEADER = {
617
+ alg: 'HS256',
618
+ typ: 'JWT'
619
+ };
620
+ const DEFAULT_POLICY_SNAPSHOT_ISSUER = 'c15t';
621
+ const DEFAULT_POLICY_SNAPSHOT_AUDIENCE = 'c15t-policy-snapshot';
622
+ function resolveSnapshotIssuer(options) {
623
+ return options?.issuer?.trim() || DEFAULT_POLICY_SNAPSHOT_ISSUER;
624
+ }
625
+ function resolveSnapshotAudience(params) {
626
+ const configuredAudience = params.options?.audience?.trim();
627
+ if (configuredAudience) return configuredAudience;
628
+ return params.tenantId ? `${DEFAULT_POLICY_SNAPSHOT_AUDIENCE}:${params.tenantId}` : DEFAULT_POLICY_SNAPSHOT_AUDIENCE;
629
+ }
630
+ function getSigningKey(secret) {
631
+ return new TextEncoder().encode(secret);
632
+ }
633
+ function isPolicySnapshotPayload(payload) {
634
+ 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;
635
+ }
636
+ async function createPolicySnapshotToken(params) {
637
+ const { options } = params;
638
+ if (!options?.signingKey) return;
639
+ const iat = Math.floor(Date.now() / 1000);
640
+ const ttlSeconds = options.ttlSeconds ?? 1800;
641
+ const exp = iat + ttlSeconds;
642
+ const iss = resolveSnapshotIssuer(options);
643
+ const aud = resolveSnapshotAudience({
644
+ options,
645
+ tenantId: params.tenantId
646
+ });
647
+ const payload = {
648
+ iss,
649
+ aud,
650
+ sub: params.policyId,
651
+ tenantId: params.tenantId,
652
+ policyId: params.policyId,
653
+ fingerprint: params.fingerprint,
654
+ matchedBy: params.matchedBy,
655
+ country: params.country,
656
+ region: params.region,
657
+ jurisdiction: params.jurisdiction,
658
+ language: params.language,
659
+ model: params.model,
660
+ policyI18n: params.policyI18n,
661
+ expiryDays: params.expiryDays,
662
+ scopeMode: params.scopeMode,
663
+ uiMode: params.uiMode,
664
+ bannerUi: params.bannerUi,
665
+ dialogUi: params.dialogUi,
666
+ categories: params.categories,
667
+ preselectedCategories: params.preselectedCategories,
668
+ gpc: params.gpc,
669
+ proofConfig: params.proofConfig,
670
+ iat,
671
+ exp
672
+ };
673
+ const token = await new SignJWT(payload).setProtectedHeader(POLICY_SNAPSHOT_JWT_HEADER).setIssuedAt(iat).setExpirationTime(exp).sign(getSigningKey(options.signingKey));
674
+ return {
675
+ token,
676
+ payload
677
+ };
678
+ }
679
+ async function verifyPolicySnapshotToken(params) {
680
+ const { token, options, tenantId } = params;
681
+ if (!options?.signingKey) return {
682
+ valid: false,
683
+ reason: 'missing'
684
+ };
685
+ if (!token) return {
686
+ valid: false,
687
+ reason: 'missing'
688
+ };
689
+ if (3 !== token.split('.').length) return {
690
+ valid: false,
691
+ reason: 'malformed'
692
+ };
693
+ try {
694
+ const { payload, protectedHeader } = await jwtVerify(token, getSigningKey(options.signingKey), {
695
+ issuer: resolveSnapshotIssuer(options),
696
+ audience: resolveSnapshotAudience({
697
+ options,
698
+ tenantId
699
+ })
700
+ });
701
+ const header = protectedHeader;
702
+ if ('HS256' !== header.alg || 'JWT' !== header.typ) return {
703
+ valid: false,
704
+ reason: 'invalid'
705
+ };
706
+ if (!isPolicySnapshotPayload(payload)) return {
707
+ valid: false,
708
+ reason: 'invalid'
709
+ };
710
+ if (payload.sub !== payload.policyId) return {
711
+ valid: false,
712
+ reason: 'invalid'
713
+ };
714
+ if ((tenantId ?? void 0) !== (payload.tenantId ?? void 0)) return {
715
+ valid: false,
716
+ reason: 'invalid'
717
+ };
718
+ return {
719
+ valid: true,
720
+ payload
721
+ };
722
+ } catch (error) {
723
+ if (error instanceof errors.JWTExpired) return {
724
+ valid: false,
725
+ reason: 'expired'
726
+ };
727
+ return {
728
+ valid: false,
729
+ reason: 'invalid'
730
+ };
731
+ }
732
+ }
613
733
  function geo_normalizeHeader(value) {
614
734
  if (!value) return null;
615
735
  return Array.isArray(value) ? value[0] ?? null : value;
@@ -751,7 +871,7 @@ function checkJurisdiction(countryCode, regionCode) {
751
871
  return jurisdiction;
752
872
  }
753
873
  async function getLocation(request, options) {
754
- if (options.advanced?.disableGeoLocation) return {
874
+ if (options.disableGeoLocation) return {
755
875
  countryCode: null,
756
876
  regionCode: null
757
877
  };
@@ -762,29 +882,250 @@ async function getLocation(request, options) {
762
882
  };
763
883
  }
764
884
  function getJurisdiction(location, options) {
765
- if (options.advanced?.disableGeoLocation) return 'GDPR';
885
+ if (options.disableGeoLocation) return 'GDPR';
766
886
  return checkJurisdiction(location.countryCode, location.regionCode);
767
887
  }
888
+ async function policy_resolvePolicyDecision(params) {
889
+ return resolvePolicyDecision({
890
+ policies: params.policies,
891
+ countryCode: params.countryCode,
892
+ regionCode: params.regionCode,
893
+ jurisdiction: params.jurisdiction,
894
+ iabEnabled: params.iabEnabled
895
+ });
896
+ }
897
+ const DEFAULT_PROFILE = 'default';
898
+ const warnedKeys = new Set();
768
899
  function isSupportedBaseLanguage(lang) {
769
900
  return lang in baseTranslations;
770
901
  }
771
- function translations_getTranslationsData(acceptLanguage, customTranslations) {
772
- const supportedDefaultLanguages = Object.keys(baseTranslations);
773
- const supportedCustomLanguages = Object.keys(customTranslations || {});
774
- const supportedLanguages = [
775
- ...supportedDefaultLanguages,
776
- ...supportedCustomLanguages
902
+ function warnOnce(logger, key, message, metadata) {
903
+ if (!logger || warnedKeys.has(key)) return;
904
+ warnedKeys.add(key);
905
+ logger.warn(message, metadata);
906
+ }
907
+ function normalizeLanguage(value) {
908
+ if (!value) return;
909
+ const normalized = value.split(',')[0]?.split(';')[0]?.trim().toLowerCase();
910
+ if (!normalized) return;
911
+ return normalized.split('-')[0] ?? void 0;
912
+ }
913
+ function normalizeProfiles(params) {
914
+ const profiles = params.i18n?.messages;
915
+ const legacy = params.customTranslations;
916
+ if (profiles && Object.keys(profiles).length > 0) {
917
+ if (legacy && Object.keys(legacy).length > 0) warnOnce(params.logger, 'i18n.customTranslations.ignored', '`customTranslations` is deprecated and ignored when `i18n.messages` is configured.');
918
+ return profiles;
919
+ }
920
+ if (legacy && Object.keys(legacy).length > 0) {
921
+ warnOnce(params.logger, 'i18n.customTranslations.deprecated', '`customTranslations` is deprecated. Use `i18n.messages` instead.');
922
+ return {
923
+ [DEFAULT_PROFILE]: {
924
+ translations: legacy
925
+ }
926
+ };
927
+ }
928
+ return {};
929
+ }
930
+ function buildCandidates(input) {
931
+ const raw = [
932
+ {
933
+ language: input.language,
934
+ reason: 'profile_language'
935
+ },
936
+ {
937
+ language: input.fallbackLanguage,
938
+ reason: 'profile_fallback'
939
+ }
777
940
  ];
778
- const preferredLanguage = selectLanguage(supportedLanguages, {
941
+ const dedupe = new Set();
942
+ return raw.filter((candidate)=>{
943
+ const key = candidate.language;
944
+ if (dedupe.has(key)) return false;
945
+ dedupe.add(key);
946
+ return true;
947
+ });
948
+ }
949
+ function getProfileLanguages(profiles, profile) {
950
+ return Object.keys(profiles[profile]?.translations ?? {}).sort();
951
+ }
952
+ function getSelectableLanguages(input) {
953
+ return getProfileLanguages(input.profiles, input.profile);
954
+ }
955
+ function resolveFallbackLanguage(input) {
956
+ const configuredFallbackLanguage = normalizeLanguage(input.profile?.fallbackLanguage) ?? 'en';
957
+ const profileLanguages = Object.keys(input.profile?.translations ?? {}).sort();
958
+ if (profileLanguages.includes(configuredFallbackLanguage)) return configuredFallbackLanguage;
959
+ if (profileLanguages.includes('en')) return 'en';
960
+ return profileLanguages[0] ?? configuredFallbackLanguage;
961
+ }
962
+ function resolveActiveProfile(input) {
963
+ const requestedProfile = input.policyProfile ?? input.defaultProfile;
964
+ if (input.profiles[requestedProfile]) return requestedProfile;
965
+ if (input.policyProfile) warnOnce(input.logger, `i18n.profile.missing:${requestedProfile}`, `Policy i18n profile '${requestedProfile}' does not exist. Falling back to default profile '${input.defaultProfile}'.`);
966
+ return input.defaultProfile;
967
+ }
968
+ function translations_getTranslationsData(acceptLanguage, customTranslations, options) {
969
+ const profiles = normalizeProfiles({
970
+ customTranslations,
971
+ i18n: options?.i18n,
972
+ logger: options?.logger
973
+ });
974
+ const defaultProfile = options?.i18n?.defaultProfile ?? DEFAULT_PROFILE;
975
+ const profile = resolveActiveProfile({
976
+ profiles,
977
+ defaultProfile,
978
+ policyProfile: options?.policyI18n?.messageProfile,
979
+ logger: options?.logger
980
+ });
981
+ const configuredLanguages = Object.keys(profiles).length > 0 ? getSelectableLanguages({
982
+ profiles,
983
+ profile
984
+ }) : Object.keys(baseTranslations);
985
+ const fallbackLanguage = Object.keys(profiles).length > 0 ? resolveFallbackLanguage({
986
+ profile: profiles[profile]
987
+ }) : 'en';
988
+ const policyLanguage = normalizeLanguage(options?.policyI18n?.language);
989
+ const requestedLanguage = policyLanguage ?? selectLanguage(configuredLanguages, {
779
990
  header: acceptLanguage,
780
- fallback: 'en'
991
+ fallback: fallbackLanguage
992
+ });
993
+ const candidates = buildCandidates({
994
+ language: requestedLanguage,
995
+ fallbackLanguage
996
+ });
997
+ const selectedCandidate = candidates.find((candidate)=>!!profiles[profile]?.translations[candidate.language]);
998
+ if (selectedCandidate && 'profile_language' !== selectedCandidate.reason) warnOnce(options?.logger, `i18n.fallback:${profile}:${requestedLanguage}:${selectedCandidate.language}`, `Policy translation fallback used (${selectedCandidate.reason}).`, {
999
+ requestedProfile: profile,
1000
+ requestedLanguage,
1001
+ resolvedProfile: profile,
1002
+ resolvedLanguage: selectedCandidate.language
781
1003
  });
782
- const base = isSupportedBaseLanguage(preferredLanguage) ? baseTranslations[preferredLanguage] : baseTranslations.en;
783
- const custom = supportedCustomLanguages.includes(preferredLanguage) ? customTranslations?.[preferredLanguage] : {};
1004
+ let language = selectedCandidate?.language ?? requestedLanguage;
1005
+ if (!selectedCandidate && !isSupportedBaseLanguage(language)) {
1006
+ warnOnce(options?.logger, `i18n.base-fallback:${language}`, `No translation found for '${language}'. Falling back to base English translations.`);
1007
+ language = 'en';
1008
+ }
1009
+ const base = isSupportedBaseLanguage(language) ? baseTranslations[language] : baseTranslations.en;
1010
+ const custom = selectedCandidate ? profiles[profile]?.translations[selectedCandidate.language] : void 0;
784
1011
  const translations = custom ? deepMergeTranslations(base, custom) : base;
785
1012
  return {
786
1013
  translations: translations,
787
- language: preferredLanguage
1014
+ language
1015
+ };
1016
+ }
1017
+ function stripIabTranslations(translations) {
1018
+ const { iab: _iab, ...rest } = translations;
1019
+ return rest;
1020
+ }
1021
+ function resolveNoPolicyFallback() {
1022
+ return {
1023
+ id: 'no_banner',
1024
+ model: 'none',
1025
+ ui: {
1026
+ mode: 'none'
1027
+ }
1028
+ };
1029
+ }
1030
+ async function resolveInitPayload(request, options, logger) {
1031
+ const acceptLanguage = request.headers.get('accept-language') || 'en';
1032
+ const location = await getLocation(request, options);
1033
+ const jurisdiction = getJurisdiction(location, options);
1034
+ const hasExplicitPolicyPack = void 0 !== options.policyPacks;
1035
+ const isExplicitEmptyPolicyPack = hasExplicitPolicyPack && (options.policyPacks?.length ?? 0) === 0;
1036
+ const policyDecision = isExplicitEmptyPolicyPack ? void 0 : await policy_resolvePolicyDecision({
1037
+ policies: options.policyPacks,
1038
+ countryCode: location.countryCode,
1039
+ regionCode: location.regionCode,
1040
+ jurisdiction,
1041
+ iabEnabled: options.iab?.enabled === true
1042
+ });
1043
+ if (hasExplicitPolicyPack && !isExplicitEmptyPolicyPack && !policyDecision) logger?.warn('Policy packs configured but no policy matched', {
1044
+ country: location.countryCode,
1045
+ region: location.regionCode
1046
+ });
1047
+ const resolvedPolicy = hasExplicitPolicyPack ? policyDecision?.policy ?? resolveNoPolicyFallback() : void 0;
1048
+ const iabOptions = options.iab;
1049
+ const shouldIncludeIabPayload = iabOptions?.enabled === true && (!hasExplicitPolicyPack || resolvedPolicy?.model === 'iab');
1050
+ const translationsResult = translations_getTranslationsData(acceptLanguage, options.customTranslations, {
1051
+ i18n: options.i18n,
1052
+ policyI18n: resolvedPolicy?.i18n,
1053
+ logger
1054
+ });
1055
+ const responseTranslations = shouldIncludeIabPayload ? translationsResult : {
1056
+ ...translationsResult,
1057
+ translations: stripIabTranslations(translationsResult.translations)
1058
+ };
1059
+ let gvl = null;
1060
+ if (shouldIncludeIabPayload && iabOptions) {
1061
+ const language = translationsResult.language.split('-')[0] || 'en';
1062
+ const gvlResolver = createGVLResolver({
1063
+ appName: options.appName || 'c15t',
1064
+ bundled: iabOptions.bundled,
1065
+ cacheAdapter: options.cache?.adapter,
1066
+ vendorIds: iabOptions.vendorIds,
1067
+ endpoint: iabOptions.endpoint
1068
+ });
1069
+ gvl = await gvlResolver.get(language);
1070
+ }
1071
+ const customVendors = shouldIncludeIabPayload ? iabOptions?.customVendors : void 0;
1072
+ const snapshot = policyDecision ? await createPolicySnapshotToken({
1073
+ options: options.policySnapshot,
1074
+ tenantId: options.tenantId,
1075
+ policyId: policyDecision.policy.id,
1076
+ fingerprint: policyDecision.fingerprint,
1077
+ matchedBy: policyDecision.matchedBy,
1078
+ country: location?.countryCode ?? null,
1079
+ region: location?.regionCode ?? null,
1080
+ jurisdiction,
1081
+ language: translationsResult.language,
1082
+ model: policyDecision.policy.model,
1083
+ policyI18n: policyDecision.policy.i18n,
1084
+ expiryDays: policyDecision.policy.consent?.expiryDays,
1085
+ scopeMode: policyDecision.policy.consent?.scopeMode,
1086
+ uiMode: policyDecision.policy.ui?.mode,
1087
+ bannerUi: policyDecision.policy.ui?.banner,
1088
+ dialogUi: policyDecision.policy.ui?.dialog,
1089
+ categories: policyDecision.policy.consent?.categories,
1090
+ preselectedCategories: policyDecision.policy.consent?.preselectedCategories,
1091
+ gpc: policyDecision.policy.consent?.gpc,
1092
+ proofConfig: policyDecision.policy.proof
1093
+ }) : void 0;
1094
+ const gpc = '1' === request.headers.get('sec-gpc');
1095
+ getMetrics()?.recordInit({
1096
+ jurisdiction,
1097
+ country: location?.countryCode ?? void 0,
1098
+ region: location?.regionCode ?? void 0,
1099
+ gpc
1100
+ });
1101
+ return {
1102
+ jurisdiction,
1103
+ location,
1104
+ translations: responseTranslations,
1105
+ branding: options.branding || 'c15t',
1106
+ ...shouldIncludeIabPayload && {
1107
+ gvl,
1108
+ customVendors
1109
+ },
1110
+ ...resolvedPolicy && {
1111
+ policy: resolvedPolicy
1112
+ },
1113
+ ...policyDecision && {
1114
+ policyDecision: {
1115
+ policyId: policyDecision.policy.id,
1116
+ fingerprint: policyDecision.fingerprint,
1117
+ matchedBy: policyDecision.matchedBy,
1118
+ country: location.countryCode,
1119
+ region: location.regionCode,
1120
+ jurisdiction
1121
+ }
1122
+ },
1123
+ ...snapshot?.token && {
1124
+ policySnapshotToken: snapshot.token
1125
+ },
1126
+ ...shouldIncludeIabPayload && iabOptions?.cmpId != null && {
1127
+ cmpId: iabOptions.cmpId
1128
+ }
788
1129
  };
789
1130
  }
790
1131
  const createInitRoute = (options)=>{
@@ -797,7 +1138,7 @@ const createInitRoute = (options)=>{
797
1138
  - **Location** – User's location (null if geo-location is disabled)
798
1139
  - **Translations** – Consent manager copy (from \`Accept-Language\` header)
799
1140
  - **Branding** – Configured branding key
800
- - **GVL** – Global Vendor List when enabled
1141
+ - **GVL** – Global Vendor List when IAB is active for the request
801
1142
 
802
1143
  Use for geo-targeted consent banners and regional compliance.`,
803
1144
  tags: [
@@ -814,42 +1155,9 @@ Use for geo-targeted consent banners and regional compliance.`,
814
1155
  }
815
1156
  }
816
1157
  }), async (c)=>{
817
- const request = c.req.raw;
818
- const acceptLanguage = request.headers.get('accept-language') || 'en';
819
- const location = await getLocation(request, options);
820
- const jurisdiction = getJurisdiction(location, options);
821
- const translationsResult = translations_getTranslationsData(acceptLanguage, options.advanced?.customTranslations);
822
- let gvl = null;
823
- if (options.advanced?.iab?.enabled) {
824
- const language = translationsResult.language.split('-')[0] || 'en';
825
- const gvlResolver = createGVLResolver({
826
- appName: options.appName || 'c15t',
827
- bundled: options.advanced.iab.bundled,
828
- cacheAdapter: options.advanced.cache?.adapter,
829
- vendorIds: options.advanced.iab.vendorIds,
830
- endpoint: options.advanced.iab.endpoint
831
- });
832
- gvl = await gvlResolver.get(language);
833
- }
834
- const customVendors = options.advanced?.iab?.customVendors;
835
- const gpc = '1' === request.headers.get('sec-gpc');
836
- getMetrics()?.recordInit({
837
- jurisdiction,
838
- country: location?.countryCode ?? void 0,
839
- region: location?.regionCode ?? void 0,
840
- gpc
841
- });
842
- return c.json({
843
- jurisdiction,
844
- location,
845
- translations: translationsResult,
846
- branding: options.advanced?.branding || 'c15t',
847
- gvl,
848
- customVendors,
849
- ...options.advanced?.iab?.cmpId != null && {
850
- cmpId: options.advanced.iab.cmpId
851
- }
852
- });
1158
+ const ctx = c.get('c15tContext');
1159
+ const payload = await resolveInitPayload(c.req.raw, options, ctx?.logger);
1160
+ return c.json(payload);
853
1161
  });
854
1162
  return app;
855
1163
  };
@@ -1205,6 +1513,119 @@ const patchSubjectHandler = async (c)=>{
1205
1513
  });
1206
1514
  }
1207
1515
  };
1516
+ function buildRuntimeDecisionDedupeKey(input) {
1517
+ return [
1518
+ input.tenantId ?? 'default',
1519
+ input.fingerprint,
1520
+ input.matchedBy,
1521
+ input.countryCode ?? 'none',
1522
+ input.regionCode ?? 'none',
1523
+ input.jurisdiction,
1524
+ input.language ?? 'none'
1525
+ ].join('|');
1526
+ }
1527
+ function buildDecisionPayload(params) {
1528
+ const { tenantId, snapshot, decision, location, jurisdiction, language, proofConfig } = params;
1529
+ if (snapshot?.valid && snapshot.payload) {
1530
+ const sp = snapshot.payload;
1531
+ return {
1532
+ tenantId,
1533
+ policyId: sp.policyId,
1534
+ fingerprint: sp.fingerprint,
1535
+ matchedBy: sp.matchedBy,
1536
+ countryCode: sp.country,
1537
+ regionCode: sp.region,
1538
+ jurisdiction: sp.jurisdiction,
1539
+ language: sp.language,
1540
+ model: sp.model,
1541
+ policyI18n: sp.policyI18n,
1542
+ uiMode: sp.uiMode,
1543
+ bannerUi: sp.bannerUi,
1544
+ dialogUi: sp.dialogUi,
1545
+ categories: sp.categories,
1546
+ preselectedCategories: sp.preselectedCategories,
1547
+ proofConfig: sp.proofConfig,
1548
+ dedupeKey: buildRuntimeDecisionDedupeKey({
1549
+ tenantId,
1550
+ fingerprint: sp.fingerprint,
1551
+ matchedBy: sp.matchedBy,
1552
+ countryCode: sp.country,
1553
+ regionCode: sp.region,
1554
+ jurisdiction: sp.jurisdiction,
1555
+ language: sp.language
1556
+ }),
1557
+ source: 'snapshot_token'
1558
+ };
1559
+ }
1560
+ if (decision) return {
1561
+ tenantId,
1562
+ policyId: decision.policy.id,
1563
+ fingerprint: decision.fingerprint,
1564
+ matchedBy: decision.matchedBy,
1565
+ countryCode: location.countryCode,
1566
+ regionCode: location.regionCode,
1567
+ jurisdiction,
1568
+ language,
1569
+ model: decision.policy.model,
1570
+ policyI18n: decision.policy.i18n,
1571
+ uiMode: decision.policy.ui?.mode,
1572
+ bannerUi: decision.policy.ui?.banner,
1573
+ dialogUi: decision.policy.ui?.dialog,
1574
+ categories: decision.policy.consent?.categories,
1575
+ preselectedCategories: decision.policy.consent?.preselectedCategories,
1576
+ proofConfig,
1577
+ dedupeKey: buildRuntimeDecisionDedupeKey({
1578
+ tenantId,
1579
+ fingerprint: decision.fingerprint,
1580
+ matchedBy: decision.matchedBy,
1581
+ countryCode: location.countryCode,
1582
+ regionCode: location.regionCode,
1583
+ jurisdiction,
1584
+ language
1585
+ }),
1586
+ source: 'write_time_fallback'
1587
+ };
1588
+ }
1589
+ function parseLanguageFromHeader(header) {
1590
+ if (!header) return;
1591
+ const firstLanguage = header.split(',')[0]?.split(';')[0]?.trim();
1592
+ if (!firstLanguage) return;
1593
+ return firstLanguage.split('-')[0]?.toLowerCase();
1594
+ }
1595
+ function resolveSnapshotFailureMode(ctx) {
1596
+ return ctx.policySnapshot?.onValidationFailure ?? 'reject';
1597
+ }
1598
+ function buildSnapshotHttpException(reason) {
1599
+ switch(reason){
1600
+ case 'missing':
1601
+ return new HTTPException(409, {
1602
+ message: 'Policy snapshot token is required',
1603
+ cause: {
1604
+ code: 'POLICY_SNAPSHOT_REQUIRED'
1605
+ }
1606
+ });
1607
+ case 'expired':
1608
+ return new HTTPException(409, {
1609
+ message: 'Policy snapshot token has expired',
1610
+ cause: {
1611
+ code: 'POLICY_SNAPSHOT_EXPIRED'
1612
+ }
1613
+ });
1614
+ case 'malformed':
1615
+ case 'invalid':
1616
+ return new HTTPException(409, {
1617
+ message: 'Policy snapshot token is invalid',
1618
+ cause: {
1619
+ code: 'POLICY_SNAPSHOT_INVALID'
1620
+ }
1621
+ });
1622
+ default:
1623
+ {
1624
+ const _exhaustive = reason;
1625
+ throw new Error(`Unhandled policy snapshot verification failure reason: ${_exhaustive}`);
1626
+ }
1627
+ }
1628
+ }
1208
1629
  const postSubjectHandler = async (c)=>{
1209
1630
  const ctx = c.get('c15tContext');
1210
1631
  const logger = ctx.logger;
@@ -1216,9 +1637,6 @@ const postSubjectHandler = async (c)=>{
1216
1637
  const givenAt = new Date(givenAtEpoch);
1217
1638
  const rawConsentAction = 'consentAction' in input ? input.consentAction : void 0;
1218
1639
  let derivedConsentAction;
1219
- if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
1220
- else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === input.jurisdictionModel ? 'opt_out' : 'reject_all';
1221
- else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
1222
1640
  logger.debug('Request parameters', {
1223
1641
  type,
1224
1642
  subjectId,
@@ -1227,6 +1645,50 @@ const postSubjectHandler = async (c)=>{
1227
1645
  domain
1228
1646
  });
1229
1647
  try {
1648
+ 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.');
1649
+ const request = c.req.raw ?? new Request('https://c15t.local/subjects');
1650
+ const acceptLanguage = request.headers.get('accept-language');
1651
+ const requestLanguage = parseLanguageFromHeader(acceptLanguage);
1652
+ const location = await getLocation(request, ctx);
1653
+ const resolvedJurisdiction = getJurisdiction(location, ctx);
1654
+ const snapshotVerification = await verifyPolicySnapshotToken({
1655
+ token: input.policySnapshotToken,
1656
+ options: ctx.policySnapshot,
1657
+ tenantId: ctx.tenantId
1658
+ });
1659
+ const hasValidSnapshot = snapshotVerification.valid;
1660
+ const snapshotPayload = snapshotVerification.valid ? snapshotVerification.payload : null;
1661
+ const shouldRequireSnapshot = !!ctx.policySnapshot?.signingKey && 'reject' === resolveSnapshotFailureMode(ctx);
1662
+ if (!hasValidSnapshot && shouldRequireSnapshot) throw buildSnapshotHttpException(snapshotVerification.reason);
1663
+ const resolvedPolicyDecision = hasValidSnapshot ? void 0 : await policy_resolvePolicyDecision({
1664
+ policies: ctx.policyPacks,
1665
+ countryCode: location.countryCode,
1666
+ regionCode: location.regionCode,
1667
+ jurisdiction: resolvedJurisdiction,
1668
+ iabEnabled: ctx.iab?.enabled === true
1669
+ });
1670
+ const effectivePolicy = hasValidSnapshot && snapshotPayload ? {
1671
+ id: snapshotPayload.policyId,
1672
+ model: snapshotPayload.model,
1673
+ i18n: snapshotPayload.policyI18n,
1674
+ consent: {
1675
+ expiryDays: snapshotPayload.expiryDays,
1676
+ scopeMode: snapshotPayload.scopeMode,
1677
+ categories: snapshotPayload.categories,
1678
+ preselectedCategories: snapshotPayload.preselectedCategories,
1679
+ gpc: snapshotPayload.gpc
1680
+ },
1681
+ ui: {
1682
+ mode: snapshotPayload.uiMode,
1683
+ banner: snapshotPayload.bannerUi,
1684
+ dialog: snapshotPayload.dialogUi
1685
+ },
1686
+ proof: snapshotPayload.proofConfig
1687
+ } : resolvedPolicyDecision?.policy;
1688
+ const effectiveModel = effectivePolicy?.model ?? ('opt-in' === input.jurisdictionModel || 'opt-out' === input.jurisdictionModel || 'iab' === input.jurisdictionModel ? input.jurisdictionModel : void 0);
1689
+ if ('all' === rawConsentAction) derivedConsentAction = 'accept_all';
1690
+ else if ('necessary' === rawConsentAction) derivedConsentAction = 'opt-out' === effectiveModel ? 'opt_out' : 'reject_all';
1691
+ else if ('custom' === rawConsentAction) derivedConsentAction = 'custom';
1230
1692
  const subject = await registry.findOrCreateSubject({
1231
1693
  subjectId,
1232
1694
  externalSubjectId,
@@ -1253,6 +1715,7 @@ const postSubjectHandler = async (c)=>{
1253
1715
  });
1254
1716
  let policyId;
1255
1717
  let purposeIds = [];
1718
+ let appliedPreferences;
1256
1719
  const inputPolicyId = 'policyId' in input ? input.policyId : void 0;
1257
1720
  if (inputPolicyId) {
1258
1721
  policyId = inputPolicyId;
@@ -1285,20 +1748,66 @@ const postSubjectHandler = async (c)=>{
1285
1748
  policyId = policy.id;
1286
1749
  }
1287
1750
  if (preferences) {
1288
- const consentedPurposes = Object.entries(preferences).filter(([_, isConsented])=>isConsented).map(([purposeCode])=>purposeCode);
1751
+ const allowedCategories = effectivePolicy?.consent?.categories;
1752
+ const effectiveScopeMode = effectivePolicy?.consent?.scopeMode ?? 'permissive';
1753
+ const hasWildcardCategoryScope = allowedCategories?.includes('*') === true;
1754
+ const appliedPreferenceEntries = Object.entries(preferences);
1755
+ let filteredAppliedPreferenceEntries = appliedPreferenceEntries;
1756
+ if (allowedCategories && allowedCategories.length > 0 && !hasWildcardCategoryScope) {
1757
+ const disallowed = appliedPreferenceEntries.map(([purpose])=>purpose).filter((purpose)=>!allowedCategories.includes(purpose));
1758
+ filteredAppliedPreferenceEntries = appliedPreferenceEntries.filter(([purpose])=>allowedCategories.includes(purpose));
1759
+ if (disallowed.length > 0 && 'strict' === effectiveScopeMode) throw new HTTPException(400, {
1760
+ message: 'Preferences include categories not allowed by policy',
1761
+ cause: {
1762
+ code: 'PURPOSE_NOT_ALLOWED',
1763
+ disallowed
1764
+ }
1765
+ });
1766
+ }
1767
+ appliedPreferences = Object.fromEntries(filteredAppliedPreferenceEntries);
1768
+ const filteredConsentedPurposeCodes = filteredAppliedPreferenceEntries.filter(([_, isConsented])=>isConsented).map(([purposeCode])=>purposeCode);
1289
1769
  logger.debug('Consented purposes', {
1290
- consentedPurposes
1770
+ consentedPurposes: filteredConsentedPurposeCodes
1291
1771
  });
1292
- const purposesRaw = await Promise.all(consentedPurposes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
1772
+ const purposesRaw = await Promise.all(filteredConsentedPurposeCodes.map((purposeCode)=>registry.findOrCreateConsentPurposeByCode(purposeCode)));
1293
1773
  const purposes = purposesRaw.map((purpose)=>purpose?.id ?? null).filter((id)=>Boolean(id));
1294
1774
  logger.debug('Filtered purposes', {
1295
1775
  purposes
1296
1776
  });
1297
1777
  if (0 === purposes.length) logger.warn('No valid purpose IDs found after filtering. Using empty list.', {
1298
- consentedPurposes
1778
+ consentedPurposes: filteredConsentedPurposeCodes
1299
1779
  });
1300
1780
  purposeIds = purposes;
1301
1781
  }
1782
+ const expiryDays = effectivePolicy?.consent?.expiryDays;
1783
+ const validUntil = 'number' == typeof expiryDays && Number.isFinite(expiryDays) ? new Date(givenAt.getTime() + 86400000 * Math.max(0, expiryDays)) : void 0;
1784
+ const proofConfig = effectivePolicy?.proof;
1785
+ const shouldStoreIp = proofConfig?.storeIp ?? true;
1786
+ const shouldStoreUserAgent = proofConfig?.storeUserAgent ?? true;
1787
+ const shouldStoreLanguage = proofConfig?.storeLanguage ?? false;
1788
+ const effectiveLanguage = (snapshotPayload?.language && hasValidSnapshot ? snapshotPayload.language : requestLanguage) ?? void 0;
1789
+ const metadataWithPolicy = {
1790
+ ...metadata ?? {},
1791
+ ...shouldStoreLanguage && effectiveLanguage ? {
1792
+ policyLanguage: effectiveLanguage
1793
+ } : {}
1794
+ };
1795
+ const effectiveJurisdiction = hasValidSnapshot && snapshotPayload ? snapshotPayload.jurisdiction : resolvedJurisdiction;
1796
+ const decisionPayload = buildDecisionPayload({
1797
+ tenantId: ctx.tenantId,
1798
+ snapshot: hasValidSnapshot && snapshotPayload ? {
1799
+ valid: true,
1800
+ payload: snapshotPayload
1801
+ } : null,
1802
+ decision: resolvedPolicyDecision,
1803
+ location: {
1804
+ countryCode: location.countryCode,
1805
+ regionCode: location.regionCode
1806
+ },
1807
+ jurisdiction: resolvedJurisdiction,
1808
+ language: effectiveLanguage,
1809
+ proofConfig
1810
+ });
1302
1811
  const existingConsent = await db.findFirst('consent', {
1303
1812
  where: (b)=>b.and(b('subjectId', '=', subject.id), b('domainId', '=', domainRecord.id), b('policyId', '=', policyId), b('givenAt', '=', givenAt))
1304
1813
  });
@@ -1313,6 +1822,7 @@ const postSubjectHandler = async (c)=>{
1313
1822
  domain: domainRecord.name,
1314
1823
  type,
1315
1824
  metadata,
1825
+ appliedPreferences,
1316
1826
  uiSource: input.uiSource,
1317
1827
  givenAt: existingConsent.givenAt
1318
1828
  });
@@ -1324,6 +1834,42 @@ const postSubjectHandler = async (c)=>{
1324
1834
  policyId,
1325
1835
  purposeIds
1326
1836
  });
1837
+ const runtimePolicyDecision = decisionPayload ? await tx.findFirst('runtimePolicyDecision', {
1838
+ where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
1839
+ }) ?? await tx.create('runtimePolicyDecision', {
1840
+ id: `rpd_${crypto.randomUUID().replaceAll('-', '')}`,
1841
+ tenantId: decisionPayload.tenantId,
1842
+ policyId: decisionPayload.policyId,
1843
+ fingerprint: decisionPayload.fingerprint,
1844
+ matchedBy: decisionPayload.matchedBy,
1845
+ countryCode: decisionPayload.countryCode,
1846
+ regionCode: decisionPayload.regionCode,
1847
+ jurisdiction: decisionPayload.jurisdiction,
1848
+ language: decisionPayload.language,
1849
+ model: decisionPayload.model,
1850
+ policyI18n: decisionPayload.policyI18n ? {
1851
+ json: decisionPayload.policyI18n
1852
+ } : void 0,
1853
+ uiMode: decisionPayload.uiMode,
1854
+ bannerUi: decisionPayload.bannerUi ? {
1855
+ json: decisionPayload.bannerUi
1856
+ } : void 0,
1857
+ dialogUi: decisionPayload.dialogUi ? {
1858
+ json: decisionPayload.dialogUi
1859
+ } : void 0,
1860
+ categories: decisionPayload.categories ? {
1861
+ json: decisionPayload.categories
1862
+ } : void 0,
1863
+ preselectedCategories: decisionPayload.preselectedCategories ? {
1864
+ json: decisionPayload.preselectedCategories
1865
+ } : void 0,
1866
+ proofConfig: decisionPayload.proofConfig ? {
1867
+ json: decisionPayload.proofConfig
1868
+ } : void 0,
1869
+ dedupeKey: decisionPayload.dedupeKey
1870
+ }).catch(async ()=>tx.findFirst('runtimePolicyDecision', {
1871
+ where: (b)=>b('dedupeKey', '=', decisionPayload.dedupeKey)
1872
+ })) : void 0;
1327
1873
  const consentRecord = await tx.create('consent', {
1328
1874
  id: await generateUniqueId(tx, 'consent', ctx),
1329
1875
  subjectId: subject.id,
@@ -1332,17 +1878,20 @@ const postSubjectHandler = async (c)=>{
1332
1878
  purposeIds: {
1333
1879
  json: purposeIds
1334
1880
  },
1335
- metadata: metadata ? {
1336
- json: metadata
1881
+ metadata: Object.keys(metadataWithPolicy).length > 0 ? {
1882
+ json: metadataWithPolicy
1337
1883
  } : void 0,
1338
- ipAddress: ctx.ipAddress,
1339
- userAgent: ctx.userAgent,
1340
- jurisdiction: input.jurisdiction,
1341
- jurisdictionModel: input.jurisdictionModel,
1884
+ ipAddress: shouldStoreIp ? ctx.ipAddress : null,
1885
+ userAgent: shouldStoreUserAgent ? ctx.userAgent : null,
1886
+ jurisdiction: effectiveJurisdiction,
1887
+ jurisdictionModel: effectiveModel,
1342
1888
  tcString: input.tcString,
1343
1889
  uiSource: input.uiSource,
1344
1890
  consentAction: derivedConsentAction,
1345
- givenAt
1891
+ givenAt,
1892
+ validUntil,
1893
+ runtimePolicyDecisionId: runtimePolicyDecision?.id,
1894
+ runtimePolicySource: decisionPayload?.source
1346
1895
  });
1347
1896
  logger.debug('Created consent', {
1348
1897
  consentRecord: consentRecord.id
@@ -1361,7 +1910,7 @@ const postSubjectHandler = async (c)=>{
1361
1910
  });
1362
1911
  const metrics = getMetrics();
1363
1912
  if (metrics) {
1364
- const jurisdiction = input.jurisdiction;
1913
+ const jurisdiction = effectiveJurisdiction;
1365
1914
  metrics.recordConsentCreated({
1366
1915
  type,
1367
1916
  jurisdiction
@@ -1383,6 +1932,7 @@ const postSubjectHandler = async (c)=>{
1383
1932
  domain: domainRecord.name,
1384
1933
  type,
1385
1934
  metadata,
1935
+ appliedPreferences,
1386
1936
  uiSource: input.uiSource,
1387
1937
  givenAt: result.consent.givenAt
1388
1938
  });