{
  "version": 3,
  "sources": ["../../../../node_modules/@sniptt/guards/lib/guards/primitives.ts", "../../../../node_modules/@sniptt/guards/lib/guards/structural.ts", "../../../../node_modules/@sniptt/guards/lib/guards/convenience.ts", "../../../../node_modules/@sniptt/guards/lib/index.ts", "../../../../src/logic-functions/licence-revalidate.logic-function.ts", "twenty-sdk-define-stub:__twenty-sdk-define-stub__", "../../../../src/constants/licence-identifiers.ts", "../../../../node_modules/twenty-shared/dist/is-record-object-schema-CwzshFdt.mjs", "../../../../node_modules/twenty-shared/dist/logic-function.mjs", "../../../../node_modules/twenty-sdk/src/sdk/logic-function/connections/errors/app-connection-auth-failed.error.ts", "../../../../node_modules/twenty-shared/dist/FieldMetadataType-PppCGM82.mjs", "../../../../node_modules/twenty-shared/dist/get-system-view-universal-identifier.util-CJoglbKX.mjs", "../../../../node_modules/twenty-shared/dist/application.mjs", "../../../../node_modules/twenty-sdk/src/sdk/logic-function/utils/post-graphql-request.util.ts", "../../../../node_modules/twenty-sdk/src/sdk/logic-function/connections/get-connection.ts", "../../../../node_modules/twenty-sdk/src/sdk/logic-function/connections/list-connections.ts", "../../../../node_modules/twenty-sdk/src/sdk/logic-function/connections/find-connection-for-request.ts", "../../../../node_modules/twenty-sdk/src/sdk/logic-function/agents/run-agent.ts", "../../../../node_modules/twenty-sdk/src/sdk/logic-function/jobs/enqueue-job.ts", "../../../../node_modules/twenty-sdk/src/sdk/logic-function/key-value/kv.ts", "../../../../node_modules/twenty-sdk/src/sdk/logic-function/response.ts", "../../../../src/logic-functions/greenlight-api.ts", "../../../../src/logic-functions/licence-cache-store.ts", "../../../../src/licensing/parse.ts", "../../../../src/licensing/mask.ts", "../../../../src/logic-functions/licence-http-client.ts", "../../../../src/licensing/audit.ts", "../../../../src/licensing/cache.ts", "../../../../src/licensing/entitlement.ts", "../../../../src/licensing/state.ts", "../../../../src/calibration/canonical.ts", "../../../../src/calibration/keys.ts", "../../../../src/calibration/types.ts", "../../../../src/calibration/parse.ts", "../../../../src/calibration/verify.ts", "../../../../src/calibration/seal.ts", "../../../../src/calibration/state.ts", "../../../../src/calibration/refresh.ts", "../../../../src/enrichment/field-specs.ts", "../../../../src/logic-functions/licence-cache-seal.ts", "../../../../src/logic-functions/licence-run.ts", "../../../../src/logic-functions/licence-workspace-identity.ts"],
  "sourcesContent": [null, null, null, null, "import { CoreApiClient } from 'twenty-client-sdk/core';\nimport { defineLogicFunction } from 'twenty-sdk/define';\n\nimport {\n  LICENCE_REVALIDATE_CRON_PATTERN,\n  LICENCE_REVALIDATE_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,\n} from 'src/constants/licence-identifiers';\nimport {\n  kvCalibrationStore,\n  kvLicenceStore,\n} from 'src/logic-functions/licence-cache-store';\nimport { createHttpLicensingClient } from 'src/logic-functions/licence-http-client';\nimport {\n  readLicenceEnvironment,\n  runLicenceRevalidation,\n} from 'src/logic-functions/licence-run';\nimport { metadataWorkspaceIdentity } from 'src/logic-functions/licence-workspace-identity';\n\n/**\n * Nightly licence re-validation.\n *\n * ## Why nightly, and why this is the only scheduled call\n *\n * The cached entitlement is trusted for 24 hours, so validating once a day is\n * the lowest frequency that never lets the cache go stale between runs and the\n * highest that buys anything. Validating per lead event, or per app launch,\n * would put a customer's scoring path behind a network call to us \u2014 which is\n * precisely the coupling the fail-open rule exists to prevent.\n *\n * Revocation therefore takes up to 24 hours to bite. That is a deliberate\n * trade: the alternative is Numaya pushing `license.revoked` webhooks into\n * customer instances, which means every workspace exposing an inbound endpoint\n * to us. Not worth it for a day of latency on an event that happens rarely and\n * costs us a single workspace's enrichment.\n *\n * ## What this run can and cannot do\n *\n * It **cannot** stop leads being scored \u2014 there is no code path from here to the\n * scoring engine, and `LicenceMode` has no \"off\". The worst outcome of every\n * branch is that `enrichmentEnabled` becomes false and an audit row explains\n * why. See `src/licensing/state.ts` for the full decision table.\n *\n * It also does not call `activate`: the slot is claimed once at install, and\n * re-claiming it nightly would be a write per workspace per day to a service\n * that learns nothing from it.\n *\n * ## Calibration\n *\n * This run is also where licence-delivered scoring calibration arrives. It costs\n * no extra request: the `validate` response already carries the licence's\n * `customerMetadata`, and that is the only channel through which the licensing\n * service will hand org-authored data to a bare licence-key holder \u2014 its two\n * config-download endpoints refuse licence keys by design. The payload's Ed25519\n * signature is verified against a public key embedded in this build before\n * anything is cached. See `src/calibration/`.\n *\n * Calibration is strictly downstream of the entitlement here, and nothing about\n * it can affect the licence decision, the audit row or the admin notice. If it\n * fails \u2014 absent, malformed, unsigned, wrongly signed, or a runtime with no\n * Ed25519 \u2014 the workspace scores on the shipped defaults, which is exactly what\n * an unlicensed install has always done.\n *\n * ## Timeout\n *\n * 30s. The HTTP client's own budget is 8s, leaving room for the metadata query,\n * five key-value writes, one signature verification and the audit mutation.\n * Failing fast is right here: a hung licence check must not pin a worker, and if\n * it fails there is a cached entitlement and another run tomorrow.\n */\nconst handler = async () => {\n  // Read once: the environment is the only impure input this file contributes,\n  // and reading it twice invites the two values drifting apart in a future edit.\n  const environment = readLicenceEnvironment();\n\n  return runLicenceRevalidation({\n    licensing: createHttpLicensingClient({\n      baseUrl: environment.baseUrl,\n      environment: environment.environment,\n    }),\n    store: kvLicenceStore,\n    // The same run that refreshes the entitlement also refreshes the calibration\n    // it delivers \u2014 see the \"Calibration\" section below.\n    calibration: kvCalibrationStore,\n    identity: metadataWorkspaceIdentity,\n    client: new CoreApiClient(),\n    licenceKey: environment.licenceKey,\n    environment: environment.environment,\n    now: new Date(),\n  });\n};\n\nexport default defineLogicFunction({\n  universalIdentifier: LICENCE_REVALIDATE_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,\n  name: 'greenlight-licence-revalidate',\n  description:\n    'Re-validates the Numaya AI licence nightly, refreshes the cached entitlement and records the resulting mode in the Greenlight audit log. Never blocks scoring.',\n  timeoutSeconds: 30,\n  handler,\n  cronTriggerSettings: {\n    pattern: LICENCE_REVALIDATE_CRON_PATTERN,\n  },\n});\n", "\n// Auto-generated stub for twenty-sdk/define injected by the SDK CLI build.\n// Real implementations would pull in zod, twenty-shared and ~1MB of code; at\n// runtime only `default.config.handler` is consumed, so tiny no-ops suffice.\nconst __defineFactoryStub = (config) => ({\n  success: true,\n  config,\n  errors: [],\n});\n\nconst __anyHandler = {\n  get(_target, prop) {\n    if (prop === '__esModule') return true;\n    if (prop === Symbol.toPrimitive) return () => '';\n    if (typeof prop === 'symbol') return undefined;\n    return new Proxy(() => undefined, __anyHandler);\n  },\n  apply() {\n    return new Proxy(() => undefined, __anyHandler);\n  },\n};\nconst __anyStub = new Proxy(() => undefined, __anyHandler);\n\nexport const createValidationResult = __defineFactoryStub;\nexport const defineAgent = __defineFactoryStub;\nexport const defineApplication = __defineFactoryStub;\nexport const defineApplicationRole = __defineFactoryStub;\nexport const defineCommandMenuItem = __defineFactoryStub;\nexport const defineConnectionProvider = __defineFactoryStub;\nexport const defineField = __defineFactoryStub;\nexport const defineFrontComponent = __defineFactoryStub;\nexport const defineIndex = __defineFactoryStub;\nexport const defineLogicFunction = __defineFactoryStub;\nexport const defineNavigationMenuItem = __defineFactoryStub;\nexport const defineObject = __defineFactoryStub;\nexport const definePageLayout = __defineFactoryStub;\nexport const definePageLayoutTab = __defineFactoryStub;\nexport const definePermissionFlag = __defineFactoryStub;\nexport const definePostInstallLogicFunction = __defineFactoryStub;\nexport const definePreInstallLogicFunction = __defineFactoryStub;\nexport const defineRole = __defineFactoryStub;\nexport const defineSettingsFrontComponent = __defineFactoryStub;\nexport const defineSkill = __defineFactoryStub;\nexport const defineUninstallLogicFunction = __defineFactoryStub;\nexport const defineView = __defineFactoryStub;\nexport const defineViewField = __defineFactoryStub;\nexport const AggregateOperations = __anyStub;\nexport const DateDisplayFormat = __anyStub;\nexport const FieldMetadataSettingsOnClickAction = __anyStub;\nexport const FieldType = __anyStub;\nexport const HTTPMethod = __anyStub;\nexport const NavigationMenuItemType = __anyStub;\nexport const NumberDataType = __anyStub;\nexport const ObjectRecordGroupByDateGranularity = __anyStub;\nexport const OnDeleteAction = __anyStub;\nexport const PageLayoutTabLayoutMode = __anyStub;\nexport const PageLayoutType = __anyStub;\nexport const RelationType = __anyStub;\nexport const RowLevelPermissionPredicateGroupLogicalOperator = __anyStub;\nexport const RowLevelPermissionPredicateOperand = __anyStub;\nexport const STANDARD_OBJECT = __anyStub;\nexport const STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS = __anyStub;\nexport const STANDARD_PAGE_LAYOUT = __anyStub;\nexport const STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS = __anyStub;\nexport const SystemPermissionFlag = __anyStub;\nexport const ViewCalendarLayout = __anyStub;\nexport const ViewFilterGroupLogicalOperator = __anyStub;\nexport const ViewFilterOperand = __anyStub;\nexport const ViewKey = __anyStub;\nexport const ViewOpenRecordIn = __anyStub;\nexport const ViewSortDirection = __anyStub;\nexport const ViewType = __anyStub;\nexport const ViewVisibility = __anyStub;\nexport const canAccessFullAdminPanel = __anyStub;\nexport const canImpersonate = __anyStub;\nexport const every = __anyStub;\nexport const everyDefined = __anyStub;\nexport const everyEquals = __anyStub;\nexport const favoriteRecordIds = __anyStub;\nexport const featureFlags = __anyStub;\nexport const getFieldUniversalIdentifier = __anyStub;\nexport const getSystemRelationFieldUniversalIdentifier = __anyStub;\nexport const getSystemViewFieldUniversalIdentifier = __anyStub;\nexport const getSystemViewUniversalIdentifier = __anyStub;\nexport const hasAnySoftDeleteFilterOnView = __anyStub;\nexport const includes = __anyStub;\nexport const includesEvery = __anyStub;\nexport const isDashboardPageLayoutInEditMode = __anyStub;\nexport const isDefined = __anyStub;\nexport const isInSidePanel = __anyStub;\nexport const isLayoutCustomizationModeEnabled = __anyStub;\nexport const isNonEmptyString = __anyStub;\nexport const isSelectAll = __anyStub;\nexport const none = __anyStub;\nexport const noneDefined = __anyStub;\nexport const noneEquals = __anyStub;\nexport const numberOfSelectedRecords = __anyStub;\nexport const objectMetadataItem = __anyStub;\nexport const objectMetadataLabel = __anyStub;\nexport const objectPermissions = __anyStub;\nexport const pageType = __anyStub;\nexport const selectedRecords = __anyStub;\nexport const some = __anyStub;\nexport const someDefined = __anyStub;\nexport const someEquals = __anyStub;\nexport const someNonEmptyString = __anyStub;\nexport const targetObjectReadPermissions = __anyStub;\nexport const targetObjectWritePermissions = __anyStub;\nexport const validateFields = __anyStub;\n", "/**\n * Identifiers, endpoint shape and storage keys for the Numaya licensing client.\n *\n * Same permanence rule as `src/constants/universal-identifiers.ts`: every UUID\n * here is written into the customer's workspace at install time, so changing one\n * orphans the old entity rather than renaming it. Adding is safe.\n *\n * These live in their own file rather than in `universal-identifiers.ts` for the\n * same reason the gate-queue constants do \u2014 so the licensing surface can be\n * built without touching the data-model constants the scoring engine depends on.\n *\n * ---------------------------------------------------------------------------\n * ## What is verified here, and how\n *\n * Everything below is now confirmed against the **REST API of the running\n * service**, not inferred. The service publishes an OpenAPI document at\n * `https://licensing.rizvigoc.com/openapi.json` (title: *Numaya Licensing -\n * Customer API*), and each request shape below was additionally exercised over\n * real HTTP on 2026-08-04.\n *\n * The spec is authoritative over `LICENSING_INTEGRATION.md`, which was written\n * from the service's **MCP** interface and predates the published spec. Where\n * the two disagree, the spec \u2014 and the live response \u2014 wins.\n *\n * The base URL stays an **application variable** (`LICENCE_API_BASE_URL`) even\n * though it is now confirmed, because a customer running a private Numaya\n * deployment still needs to point it somewhere else, and because a wrong\n * default must remain a settings change rather than a release.\n *\n * Nothing here is load-bearing for safety: every request this client makes\n * either succeeds, or fails in a way that degrades to rules-only scoring. A\n * wrong base URL costs the customer enrichment, never a lead. See\n * `src/licensing/state.ts`.\n * ---------------------------------------------------------------------------\n */\n\n/* -------------------------------------------------------------------------- */\n/* Universal identifiers                                                       */\n/* -------------------------------------------------------------------------- */\n\nexport const LICENCE_API_BASE_URL_APP_VARIABLE_UNIVERSAL_IDENTIFIER =\n  '0dbad279-c07c-4ae0-91e5-68cb0e87c944';\n\n/**\n * `LICENCE_ENVIRONMENT`. See `LICENCE_ENVIRONMENTS` below for why an endpoint\n * that a customer will never change is still a Layer 1 variable.\n */\nexport const LICENCE_ENVIRONMENT_APP_VARIABLE_UNIVERSAL_IDENTIFIER =\n  'e39571dd-0053-4c17-a11f-c0988985d312';\n\nexport const LICENCE_REVALIDATE_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER =\n  'fc2534ad-a3a1-4b05-bdf0-f2e7a1cef729';\n\nexport const LICENCE_STATUS_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER =\n  '42eaa3bc-75b6-49be-863c-a3b3c539e3a2';\n\nexport const LICENCE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER =\n  '42089a05-139e-4061-aaaa-1d53e851ac90';\n\nexport const LICENCE_COMMAND_MENU_ITEM_UNIVERSAL_IDENTIFIER =\n  'b0316c8e-e4ad-492c-9197-f26c553c0151';\n\n/* -------------------------------------------------------------------------- */\n/* The licence panel                                                           */\n/* -------------------------------------------------------------------------- */\n\nexport const LICENCE_ROUTE_PATH = '/greenlight/licence';\n\nexport const LICENCE_CLIENT_PATH = `/s${LICENCE_ROUTE_PATH}`;\n\n/**\n * Where \"Get a licence\" sends an admin.\n *\n * A constant rather than a literal in the component because it is the one part\n * of this feature guaranteed to move: today numaya.ai has no Greenlight\n * checkout, so the honest destination is the contact route, which reaches a\n * human who can issue a key. When a real purchase page exists this is the only\n * line that changes.\n *\n * The `source` parameter is not decoration. Without it there is no way to tell\n * an enquiry that came from inside a customer's own CRM \u2014 someone who has\n * already installed the app and hit the rules-only ceiling \u2014 from a cold\n * website visitor, and those two deserve different replies.\n */\nexport const LICENCE_PURCHASE_URL =\n  'https://numaya.ai/contact?source=greenlight-app';\n\n/* -------------------------------------------------------------------------- */\n/* Endpoint shape \u2014 confirmed against the published spec and live HTTP         */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Default value of the `LICENCE_API_BASE_URL` application variable.\n *\n * `https://licensing.rizvigoc.com` is the licensing service's real public\n * hostname, confirmed on 2026-08-04 by fetching its OpenAPI document and by\n * driving `validate` and `activate` against it over HTTPS.\n *\n * The earlier default, `license.numaya.ai`, never existed: it was a placeholder\n * that entered this project's own documentation and was then cited by it. It\n * did not resolve in public DNS, so every install shipped with it fell straight\n * through the cache \u2192 grace \u2192 rules-only ladder. That ladder worked, which is\n * precisely why the wrong hostname survived as long as it did.\n */\nexport const DEFAULT_LICENCE_API_BASE_URL = 'https://licensing.rizvigoc.com';\n\n/**\n * Confirmed. Both paths appear verbatim in the service's published OpenAPI\n * document and both were exercised live. The `/v1` prefix and the US spelling\n * `licenses` are the service's, not ours.\n */\nexport const LICENCE_VALIDATE_PATH = '/v1/licenses/validate';\nexport const LICENCE_ACTIVATE_PATH = '/v1/licenses/activate';\n\n/* -------------------------------------------------------------------------- */\n/* Environment routing                                                         */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The header that selects which side of the licensing service answers.\n *\n * One host serves both environments. Sandbox licences are **only** visible to a\n * request carrying `X-Numaya-Environment: sandbox`; without it the same key\n * comes back `200 valid:false reason:\"license_not_found\"`. The service's own\n * spec says so in one sentence \u2014 \"Use `X-Numaya-Environment: sandbox` header or\n * `nml_test_` API keys to access the sandbox\" \u2014 and the live behaviour matches.\n *\n * The header value is matched case-insensitively by the service (`sandbox`,\n * `Sandbox` and `SANDBOX` all worked); we send the lower-case form.\n */\nexport const LICENCE_ENVIRONMENT_HEADER = 'X-Numaya-Environment';\n\n/**\n * The two environments, and why this is an application variable at all.\n *\n * It is **not** derivable from the base URL \u2014 the same host serves both \u2014 so it\n * cannot be folded into `LICENCE_API_BASE_URL`. It cannot be a code constant\n * either: our own CI workspace and every throwaway dev container is licensed\n * out of sandbox, and making that a constant would mean a code change and a\n * republish to test a licensing change. And it cannot live in Layer 2, the\n * CRM-editable `GreenlightConfig` record, because the post-install hook\n * validates the licence before any Layer 2 record is guaranteed to exist.\n *\n * That leaves Layer 1. A customer never touches it \u2014 the default is\n * `production` and the description says as much \u2014 but it has to be *settable*\n * without a rebuild, which is the identical argument that already put\n * `LICENCE_API_BASE_URL` in Layer 1.\n */\nexport const LICENCE_ENVIRONMENTS = ['production', 'sandbox'] as const;\n\nexport const DEFAULT_LICENCE_ENVIRONMENT = 'production';\n\n/**\n * The service's `reason` when a key authenticated but resolved to no licence in\n * the environment the request was routed to. US spelling, the service's own.\n *\n * This is the misrouted-environment signal. See `src/licensing/entitlement.ts`\n * for why it cannot mean \"the key has a typo\".\n */\nexport const LICENCE_NOT_FOUND_REASON = 'license_not_found';\n\n/**\n * Request body field names.\n *\n * These are *not* guesses: they are the parameter names the licensing service's\n * own MCP tool schema declares for `numaya_validate_license` and\n * `numaya_activate_license`, which are generated from the service's parameter\n * model. The REST layer using different names is possible but unlikely.\n */\nexport const LICENCE_REQUEST_FIELDS = {\n  key: 'key',\n  deviceFingerprint: 'deviceFingerprint',\n  feature: 'feature',\n  deviceName: 'deviceName',\n} as const;\n\n/**\n * The feature name the paid capability is licensed under. `rules` \u2014 the\n * deterministic gate \u2014 is on every policy and is never checked, because rules\n * scoring is the free floor and must run with no licence at all.\n */\nexport const ENRICHMENT_FEATURE_NAME = 'enrichment';\n\n/* -------------------------------------------------------------------------- */\n/* App key-value storage                                                       */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Where the cached entitlement and the published admin-facing state live.\n *\n * `scope: 'WORKSPACE'` throughout \u2014 the licence is per workspace, and the\n * activation slot is claimed with the workspace id as its fingerprint. A read\n * under a different scope silently finds nothing, which is the failure mode\n * `release-marker-store.ts` documents.\n */\nexport const LICENCE_CACHE_KV_KEY = 'greenlight.licence.cache';\n\n/**\n * Sticky record of what happened the last time we tried to claim an activation\n * slot. Separate from the validation cache on purpose: a `409` is durable\n * customer state (the licence is bound to a different workspace) and must\n * survive every nightly re-validation, whereas the validation cache is replaced\n * nightly. See `src/licensing/state.ts` for why this matters.\n */\nexport const LICENCE_ACTIVATION_KV_KEY = 'greenlight.licence.activation';\n\n/**\n * The resolved licence state, published for the admin UI to read. Written on\n * every validation; never contains the licence key.\n */\nexport const LICENCE_STATE_KV_KEY = 'greenlight.licence.state';\n\n/**\n * The verified calibration payload, cached beside the entitlement.\n *\n * Separate from `LICENCE_CACHE_KV_KEY` on purpose. The two are written by the\n * same nightly run but answer different questions and age on different clocks \u2014\n * the entitlement tightens at 24h because a stale one risks serving a paid\n * feature to a revoked licence, while calibration survives to 72h because it is\n * a word list and dropping it early would move a customer's scores for a reason\n * that has nothing to do with their data. See `src/calibration/state.ts`.\n *\n * It holds no key, no entitlement and nothing secret: the payload is the same\n * vocabulary every licensed workspace receives.\n */\nexport const CALIBRATION_CACHE_KV_KEY = 'greenlight.calibration.cache';\n\n/**\n * The resolved calibration state, published for the admin panel: shipped\n * defaults or calibration vNN, from where, and when it last refreshed.\n */\nexport const CALIBRATION_STATE_KV_KEY = 'greenlight.calibration.state';\n\n/* -------------------------------------------------------------------------- */\n/* Schedule                                                                    */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Nightly re-validation, 03:17 UTC.\n *\n * Standard five-field CRON. The odd minute is deliberate: every Greenlight\n * install would otherwise phone home on the same second, and the licensing\n * service is a single small container. Daily is the right frequency because the\n * cache TTL is 24h \u2014 validating more often buys nothing, validating less often\n * would let the cache go stale before the next run.\n */\nexport const LICENCE_REVALIDATE_CRON_PATTERN = '17 3 * * *';\n", "import { isNonEmptyString as e } from \"@sniptt/guards\";\n//#region src/logic-function/is-record-object-schema.ts\nvar t = (t) => (t?.type === \"record\" || t?.type === \"object\") && e(t.objectUniversalIdentifier);\n//#endregion\nexport { t };\n", "import { t as e } from \"./isDefined-Dtu5EYqP.mjs\";\nimport { t } from \"./is-record-object-schema-CwzshFdt.mjs\";\nimport { isNonEmptyString as n, isObject as r } from \"@sniptt/guards\";\n//#region src/logic-function/build-tool-input-json-schema.ts\nvar i = (e, t) => {\n\tlet r = n(e) ? t?.(e) : void 0;\n\treturn `Id of the ${n(r) ? r : \"linked\"} record`;\n}, a = (n, o) => {\n\tif (t(n)) return {\n\t\ttype: \"string\",\n\t\tdescription: i(n.objectUniversalIdentifier, o)\n\t};\n\tif (n.type === \"records\") return {\n\t\ttype: \"array\",\n\t\titems: {\n\t\t\ttype: \"string\",\n\t\t\tdescription: i(n.objectUniversalIdentifier, o)\n\t\t}\n\t};\n\tlet { objectUniversalIdentifier: s, multiline: c, label: l, items: u, properties: d, additionalProperties: f, ...p } = n, m = { ...p };\n\treturn e(u) && (m.items = a(u, o)), e(d) && (m.properties = Object.fromEntries(Object.entries(d).map(([e, t]) => [e, a(t, o)]))), e(f) && (m.additionalProperties = r(f) ? a(f, o) : f), m;\n}, o = {\n\ttype: \"object\",\n\tproperties: {}\n}, s = (e) => {\n\tlet t = { type: \"unknown\" };\n\tswitch (e.type) {\n\t\tcase \"string\":\n\t\t\tt.type = \"string\";\n\t\t\tbreak;\n\t\tcase \"number\":\n\t\tcase \"integer\":\n\t\t\tt.type = \"number\";\n\t\t\tbreak;\n\t\tcase \"boolean\":\n\t\t\tt.type = \"boolean\";\n\t\t\tbreak;\n\t\tcase \"array\":\n\t\t\tt.type = \"array\", e.items && (t.items = s(e.items));\n\t\t\tbreak;\n\t\tcase \"object\":\n\t\t\tt.type = \"object\", e.properties && (t.properties = Object.fromEntries(Object.entries(e.properties).map(([e, t]) => [e, s(t)])));\n\t\t\tbreak;\n\t\tcase \"record\":\n\t\t\tt.type = \"record\";\n\t\t\tbreak;\n\t\tcase \"records\":\n\t\t\tt.type = \"records\";\n\t\t\tbreak;\n\t\tdefault: t.type = \"unknown\";\n\t}\n\treturn Array.isArray(e.enum) && (t.enum = e.enum.filter((e) => typeof e == \"string\")), e.multiline === !0 && (t.multiline = !0), n(e.label) && (t.label = e.label), n(e.objectUniversalIdentifier) && (t.objectUniversalIdentifier = e.objectUniversalIdentifier), t;\n}, c = (e) => [s(e)], l = { inputSchema: c({\n\ttype: \"object\",\n\tproperties: {\n\t\ta: { type: \"string\" },\n\t\tb: { type: \"number\" }\n\t}\n}) }, u = async (t) => {\n\tlet { getFunctionInputSchema: n } = await import(\"./get-function-input-schema-GNk3NRLJ.mjs\"), r = n(t)[0];\n\treturn r?.type === \"object\" && e(r.properties) ? {\n\t\ttype: \"object\",\n\t\tproperties: r.properties\n\t} : o;\n}, d = (t) => !e(t) || t === null ? \"unknown\" : typeof t == \"string\" ? \"string\" : typeof t == \"number\" ? \"number\" : typeof t == \"boolean\" ? \"boolean\" : Array.isArray(t) ? \"array\" : \"unknown\", f = (e) => e ? Object.entries(e).reduce((e, [t, n]) => (r(n) && !Array.isArray(n) ? e[t] = {\n\tisLeaf: !1,\n\ttype: \"object\",\n\tlabel: t,\n\tvalue: f(n)\n} : e[t] = {\n\tisLeaf: !0,\n\tvalue: n,\n\ttype: d(n),\n\tlabel: t\n}, e), {}) : {}, p = (e, t) => e ? `${e}.${t}` : t, m = (t, n, r = \"\") => {\n\tlet i = [];\n\tfor (let [a, o] of Object.entries(n)) {\n\t\tlet n = p(r, a), s = t[a];\n\t\tif (!e(s)) {\n\t\t\ti.push(`Missing key \"${n}\" in declared output schema.`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (o.isLeaf !== s.isLeaf) {\n\t\t\ti.push(`Type mismatch at \"${n}\": expected ${o.isLeaf ? o.type : \"object\"} but declared ${s.isLeaf ? s.type : \"object\"}.`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!o.isLeaf && !s.isLeaf) {\n\t\t\ti.push(...m(s.value, o.value, n));\n\t\t\tcontinue;\n\t\t}\n\t\to.isLeaf && s.isLeaf && o.type !== \"unknown\" && s.type !== \"unknown\" && o.type !== s.type && i.push(`Type mismatch at \"${n}\": expected ${o.type} but declared ${s.type}.`);\n\t}\n\treturn i;\n}, h = [\n\t\"string\",\n\t\"number\",\n\t\"boolean\",\n\t\"array\",\n\t\"unknown\"\n], g = (e) => h.includes(e), _ = (e, t) => {\n\tlet n = t.label ?? e;\n\treturn t.type === \"record\" ? {\n\t\tisLeaf: !0,\n\t\ttype: \"string\",\n\t\tlabel: n,\n\t\tvalue: null\n\t} : t.type === \"records\" ? {\n\t\tisLeaf: !0,\n\t\ttype: \"array\",\n\t\tlabel: n,\n\t\tvalue: null\n\t} : t.type === \"object\" ? {\n\t\tisLeaf: !1,\n\t\ttype: \"object\",\n\t\tlabel: n,\n\t\tvalue: r(t.properties) ? v(t.properties) : {}\n\t} : {\n\t\tisLeaf: !0,\n\t\ttype: g(t.type) ? t.type : \"unknown\",\n\t\tlabel: n,\n\t\tvalue: null\n\t};\n}, v = (e) => Object.entries(e).reduce((e, [t, n]) => (e[t] = _(t, n), e), {}), y = (e) => {\n\tlet t = e[0];\n\treturn t?.type !== \"object\" || !r(t.properties) ? {} : v(t.properties);\n}, b = (e) => e?.type === \"records\" && n(e.objectUniversalIdentifier) || e?.type === \"array\" && t(e?.items);\n//#endregion\nexport { o as DEFAULT_TOOL_INPUT_SCHEMA, l as SEED_WORKFLOW_ACTION_TRIGGER_SETTINGS, a as buildToolInputJsonSchema, u as getInputSchemaFromSourceCode, f as getOutputSchemaFromValue, m as getOutputSchemaMismatchIssues, y as inputSchemaToOutputSchema, b as isRecordArraySchema, t as isRecordObjectSchema, c as jsonSchemaToInputSchema };\n", "// Thrown when the platform asks the SDK to operate on a connection whose\n// OAuth refresh failed permanently (`authFailedAt` is set). The end user\n// must reconnect from the app's settings tab — the app cannot recover on\n// its own.\n//\n// `listConnections` filters these out by default (the user can't act on\n// them anyway). `getConnection` throws this when the looked-up connection\n// is in this state, so a stored connection id can be safely retried until\n// it works again.\nexport class AppConnectionAuthFailedError extends Error {\n  readonly connectionId: string;\n\n  constructor(connectionId: string) {\n    super(\n      `App connection ${connectionId} requires the user to reconnect ` +\n        `(authFailedAt is set). Surface a \"Reconnect\" prompt in your UI.`,\n    );\n    this.name = 'AppConnectionAuthFailedError';\n    this.connectionId = connectionId;\n  }\n}\n", "//#region src/types/FieldMetadataType.ts\nvar e = /* @__PURE__ */ function(e) {\n\treturn e.ACTOR = \"ACTOR\", e.ADDRESS = \"ADDRESS\", e.ARRAY = \"ARRAY\", e.BOOLEAN = \"BOOLEAN\", e.CURRENCY = \"CURRENCY\", e.DATE = \"DATE\", e.DATE_TIME = \"DATE_TIME\", e.EMAILS = \"EMAILS\", e.FILES = \"FILES\", e.FULL_NAME = \"FULL_NAME\", e.LINKS = \"LINKS\", e.MORPH_RELATION = \"MORPH_RELATION\", e.MULTI_SELECT = \"MULTI_SELECT\", e.NUMBER = \"NUMBER\", e.NUMERIC = \"NUMERIC\", e.PHONES = \"PHONES\", e.POSITION = \"POSITION\", e.RATING = \"RATING\", e.RAW_JSON = \"RAW_JSON\", e.RELATION = \"RELATION\", e.RICH_TEXT = \"RICH_TEXT\", e.SELECT = \"SELECT\", e.TEXT = \"TEXT\", e.TS_VECTOR = \"TS_VECTOR\", e.UUID = \"UUID\", e;\n}({});\n//#endregion\nexport { e as t };\n", "import { v5 as e } from \"uuid\";\n//#region src/application/constants/TwentyStandardApplicationUniversalIdentifier.ts\nvar t = \"20202020-64aa-4b6f-b003-9c74b97cee20\", n = ({ entityNamespace: t, value: n, applicationUniversalIdentifier: r }) => e(`${t}:${n}`, r), r = ({ applicationUniversalIdentifier: e, objectUniversalIdentifier: t, name: r }) => n({\n\tentityNamespace: \"fieldMetadata\",\n\tvalue: `${t}:${r}`,\n\tapplicationUniversalIdentifier: e\n}), i = ({ applicationUniversalIdentifier: e, objectUniversalIdentifier: t, relationTargetObjectUniversalIdentifier: r }) => n({\n\tentityNamespace: \"fieldMetadata\",\n\tvalue: `${t}:systemRelation:${r}`,\n\tapplicationUniversalIdentifier: e\n}), a = ({ fieldMetadataApplicationUniversalIdentifier: e, viewUniversalIdentifier: t, fieldMetadataUniversalIdentifier: r }) => n({\n\tentityNamespace: \"viewField\",\n\tvalue: `${t}:${r}`,\n\tapplicationUniversalIdentifier: e\n}), o = ({ objectMetadataApplicationUniversalIdentifier: e, objectUniversalIdentifier: t, viewKey: r }) => n({\n\tentityNamespace: \"view\",\n\tvalue: `${t}:${r}`,\n\tapplicationUniversalIdentifier: e\n});\n//#endregion\nexport { n as a, r as i, a as n, t as o, i as r, o as t };\n", "import { t as e } from \"./FieldMetadataType-PppCGM82.mjs\";\nimport { a as t, i as n, n as r, o as i, r as a, t as o } from \"./get-system-view-universal-identifier.util-CJoglbKX.mjs\";\n//#region src/application/applicationCategoryType.ts\nvar s = [\n\t\"Communication\",\n\t\"Productivity\",\n\t\"Product management\",\n\t\"Sales\",\n\t\"Marketing\",\n\t\"Enrichment\",\n\t\"Data\",\n\t\"Search\",\n\t\"Other\"\n], c = (e) => s.includes(e), l = [\n\te.TEXT,\n\te.ARRAY,\n\te.BOOLEAN,\n\te.DATE,\n\te.DATE_TIME,\n\te.NUMBER,\n\te.NUMERIC,\n\te.RAW_JSON,\n\te.RICH_TEXT,\n\te.SELECT,\n\te.MULTI_SELECT\n], u = \"public\", d = \"TWENTY_API_KEY\", f = \"TWENTY_API_URL\", p = \"TWENTY_APP_ACCESS_TOKEN\", m = \"TWENTY_FUNCTIONS_URL\", h = \"generated\", g = { js: \"import { createRequire as __createRequire } from 'module';\\nconst require = __createRequire(import.meta.url);\" }, _ = \".twenty/output\", v = \"Standard\", y = ({ applicationUniversalIdentifier: e, name: n }) => t({\n\tentityNamespace: \"agent\",\n\tvalue: n,\n\tapplicationUniversalIdentifier: e\n}), b = ({ applicationUniversalIdentifier: e, key: n }) => t({\n\tentityNamespace: \"applicationVariable\",\n\tvalue: n,\n\tapplicationUniversalIdentifier: e\n}), x = \"navigation\", S = ({ applicationUniversalIdentifier: e, engineComponentKey: n }) => t({\n\tentityNamespace: \"commandMenuItem\",\n\tvalue: `GLOBAL:${n}`,\n\tapplicationUniversalIdentifier: e\n}), C = ({ applicationUniversalIdentifier: e, engineComponentKey: n }) => t({\n\tentityNamespace: \"commandMenuItem\",\n\tvalue: `GLOBAL_OBJECT_CONTEXT:${n}`,\n\tapplicationUniversalIdentifier: e\n}), w = ({ applicationUniversalIdentifier: e, engineComponentKey: n, objectUniversalIdentifier: r }) => t({\n\tentityNamespace: \"commandMenuItem\",\n\tvalue: `RECORD_SELECTION:${n}:${r ?? \"\"}`,\n\tapplicationUniversalIdentifier: e\n}), T = ({ applicationUniversalIdentifier: e, objectUniversalIdentifier: n }) => t({\n\tentityNamespace: \"commandMenuItem\",\n\tvalue: `${n}:${x}`,\n\tapplicationUniversalIdentifier: e\n}), E = ({ applicationUniversalIdentifier: e, name: n }) => t({\n\tentityNamespace: \"connectionProvider\",\n\tvalue: n,\n\tapplicationUniversalIdentifier: e\n}), D = ({ applicationUniversalIdentifier: e, roleUniversalIdentifier: n, fieldUniversalIdentifier: r }) => t({\n\tentityNamespace: \"fieldPermission\",\n\tvalue: `${n}:${r}`,\n\tapplicationUniversalIdentifier: e\n}), O = ({ applicationUniversalIdentifier: e, pageLayoutWidgetUniversalIdentifier: n }) => t({\n\tentityNamespace: \"view\",\n\tvalue: n,\n\tapplicationUniversalIdentifier: e\n}), k = ({ applicationUniversalIdentifier: e, componentName: n }) => t({\n\tentityNamespace: \"frontComponent\",\n\tvalue: n,\n\tapplicationUniversalIdentifier: e\n}), A = ({ applicationUniversalIdentifier: e, objectUniversalIdentifier: n, name: r }) => t({\n\tentityNamespace: \"index\",\n\tvalue: `${n}:${r}`,\n\tapplicationUniversalIdentifier: e\n}), j = ({ applicationUniversalIdentifier: e, name: n }) => t({\n\tentityNamespace: \"logicFunction\",\n\tvalue: n,\n\tapplicationUniversalIdentifier: e\n}), ee = ({ applicationUniversalIdentifier: e, name: n }) => t({\n\tentityNamespace: \"navigationMenuItem\",\n\tvalue: `FOLDER:${n}`,\n\tapplicationUniversalIdentifier: e\n}), M = ({ applicationUniversalIdentifier: e, objectUniversalIdentifier: n }) => t({\n\tentityNamespace: \"navigationMenuItem\",\n\tvalue: `OBJECT:${n}`,\n\tapplicationUniversalIdentifier: e\n}), N = ({ applicationUniversalIdentifier: e, viewUniversalIdentifier: n }) => t({\n\tentityNamespace: \"navigationMenuItem\",\n\tvalue: `VIEW:${n}`,\n\tapplicationUniversalIdentifier: e\n}), P = ({ applicationUniversalIdentifier: e, link: n }) => t({\n\tentityNamespace: \"navigationMenuItem\",\n\tvalue: `LINK:${n}`,\n\tapplicationUniversalIdentifier: e\n}), F = ({ applicationUniversalIdentifier: e, roleUniversalIdentifier: n, objectUniversalIdentifier: r }) => t({\n\tentityNamespace: \"objectPermission\",\n\tvalue: `${n}:${r}`,\n\tapplicationUniversalIdentifier: e\n}), I = ({ applicationUniversalIdentifier: e, nameSingular: n }) => t({\n\tentityNamespace: \"objectMetadata\",\n\tvalue: n,\n\tapplicationUniversalIdentifier: e\n}), L = ({ applicationUniversalIdentifier: e, pageLayoutUniversalIdentifier: n, title: r }) => t({\n\tentityNamespace: \"pageLayoutTab\",\n\tvalue: `${n}:${r}`,\n\tapplicationUniversalIdentifier: e\n}), R = \"RECORD_PAGE\", z = ({ applicationUniversalIdentifier: e, objectUniversalIdentifier: n, name: r }) => t({\n\tentityNamespace: \"pageLayout\",\n\tvalue: n ? `${n}:${r}` : r,\n\tapplicationUniversalIdentifier: e\n}), B = ({ applicationUniversalIdentifier: e, objectUniversalIdentifier: n }) => t({\n\tentityNamespace: \"pageLayout\",\n\tvalue: `${n}:${R}`,\n\tapplicationUniversalIdentifier: e\n}), V = ({ applicationUniversalIdentifier: e, pageLayoutTabUniversalIdentifier: n, title: r }) => t({\n\tentityNamespace: \"pageLayoutWidget\",\n\tvalue: `${n}:${r}`,\n\tapplicationUniversalIdentifier: e\n}), H = ({ applicationUniversalIdentifier: e, key: n }) => t({\n\tentityNamespace: \"permissionFlag\",\n\tvalue: n,\n\tapplicationUniversalIdentifier: e\n}), U = ({ applicationUniversalIdentifier: e, roleUniversalIdentifier: n, permissionFlagUniversalIdentifier: r }) => t({\n\tentityNamespace: \"rolePermissionFlag\",\n\tvalue: `${n}:${r}`,\n\tapplicationUniversalIdentifier: e\n}), W = ({ applicationUniversalIdentifier: e, agentUniversalIdentifier: n }) => t({\n\tentityNamespace: \"roleTarget\",\n\tvalue: n,\n\tapplicationUniversalIdentifier: e\n}), G = ({ applicationUniversalIdentifier: e, label: n }) => t({\n\tentityNamespace: \"role\",\n\tvalue: n,\n\tapplicationUniversalIdentifier: e\n}), K = ({ applicationUniversalIdentifier: e, fieldMetadataUniversalIdentifier: n }) => t({\n\tentityNamespace: \"searchFieldMetadata\",\n\tvalue: n,\n\tapplicationUniversalIdentifier: e\n}), q = ({ applicationUniversalIdentifier: e, fieldUniversalIdentifier: n, value: r }) => t({\n\tentityNamespace: \"selectOption\",\n\tvalue: `${n}:${r}`,\n\tapplicationUniversalIdentifier: e\n}), J = ({ applicationUniversalIdentifier: e, name: n }) => t({\n\tentityNamespace: \"skill\",\n\tvalue: n,\n\tapplicationUniversalIdentifier: e\n}), Y = ({ applicationUniversalIdentifier: e, viewUniversalIdentifier: n, name: r }) => t({\n\tentityNamespace: \"viewFieldGroup\",\n\tvalue: `${n}:${r}`,\n\tapplicationUniversalIdentifier: e\n}), X = ({ applicationUniversalIdentifier: e, viewUniversalIdentifier: n, fieldMetadataUniversalIdentifier: r }) => t({\n\tentityNamespace: \"viewField\",\n\tvalue: `${n}:${r}`,\n\tapplicationUniversalIdentifier: e\n}), Z = ({ applicationUniversalIdentifier: e, viewUniversalIdentifier: n, fieldMetadataUniversalIdentifier: r, operand: i, subFieldName: a }) => t({\n\tentityNamespace: \"viewFilter\",\n\tvalue: `${n}:${r}:${i}:${a ?? \"\"}`,\n\tapplicationUniversalIdentifier: e\n}), Q = ({ applicationUniversalIdentifier: e, viewUniversalIdentifier: n, fieldValue: r }) => t({\n\tentityNamespace: \"viewGroup\",\n\tvalue: `${n}:${r}`,\n\tapplicationUniversalIdentifier: e\n}), $ = ({ applicationUniversalIdentifier: e, viewUniversalIdentifier: n, fieldMetadataUniversalIdentifier: r }) => t({\n\tentityNamespace: \"viewSort\",\n\tvalue: `${n}:${r}`,\n\tapplicationUniversalIdentifier: e\n}), te = ({ applicationUniversalIdentifier: e, objectUniversalIdentifier: n, name: r }) => t({\n\tentityNamespace: \"view\",\n\tvalue: `${n}:${r}`,\n\tapplicationUniversalIdentifier: e\n}), ne = /* @__PURE__ */ function(e) {\n\treturn e.Object = \"object\", e.Field = \"field\", e.LogicFunction = \"logicFunction\", e.FrontComponent = \"frontComponent\", e.Role = \"role\", e.Skill = \"skill\", e.Agent = \"agent\", e.ConnectionProvider = \"connectionProvider\", e.View = \"view\", e.ViewField = \"viewField\", e.NavigationMenuItem = \"navigationMenuItem\", e.PageLayout = \"pageLayout\", e.PageLayoutTab = \"pageLayoutTab\", e.CommandMenuItem = \"commandMenuItem\", e;\n}({}), re = (t, n = e.TEXT) => {\n\tif (t == null) return \"\";\n\tswitch (n) {\n\t\tcase e.BOOLEAN: return String(t) === \"true\" ? \"true\" : \"false\";\n\t\tcase e.NUMBER:\n\t\tcase e.NUMERIC: return String(t);\n\t\tcase e.ARRAY:\n\t\tcase e.MULTI_SELECT:\n\t\t\tif (Array.isArray(t)) return JSON.stringify(t);\n\t\t\tif (typeof t == \"string\") {\n\t\t\t\ttry {\n\t\t\t\t\tlet e = JSON.parse(t);\n\t\t\t\t\tif (Array.isArray(e)) return t;\n\t\t\t\t} catch {}\n\t\t\t\treturn JSON.stringify([t]);\n\t\t\t}\n\t\t\treturn JSON.stringify(t);\n\t\tcase e.RAW_JSON:\n\t\tcase e.RICH_TEXT: return typeof t == \"string\" ? t : JSON.stringify(t);\n\t\tdefault: return typeof t == \"string\" ? t : String(t);\n\t}\n}, ie = (t, n = e.TEXT) => {\n\tif (t === \"\") return n === e.ARRAY || n === e.MULTI_SELECT ? [] : \"\";\n\tswitch (n) {\n\t\tcase e.BOOLEAN: return t === \"true\";\n\t\tcase e.NUMBER:\n\t\tcase e.NUMERIC: {\n\t\t\tlet e = Number(t);\n\t\t\treturn Number.isNaN(e) ? t : e;\n\t\t}\n\t\tcase e.ARRAY:\n\t\tcase e.MULTI_SELECT: try {\n\t\t\tlet e = JSON.parse(t);\n\t\t\treturn Array.isArray(e) ? e : [];\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t\tcase e.RAW_JSON:\n\t\tcase e.RICH_TEXT: try {\n\t\t\treturn JSON.parse(t);\n\t\t} catch {\n\t\t\treturn t;\n\t\t}\n\t\tdefault: return t;\n\t}\n};\n//#endregion\nexport { s as APPLICATION_CATEGORIES, l as APPLICATION_VARIABLE_FIELD_METADATA_TYPES, u as ASSETS_DIR, d as DEFAULT_API_KEY_NAME, f as DEFAULT_API_URL_NAME, p as DEFAULT_APP_ACCESS_TOKEN_NAME, m as DEFAULT_FUNCTIONS_URL_NAME, h as GENERATED_DIR, g as NODE_ESM_CJS_BANNER, _ as OUTPUT_DIR, ne as SyncableEntity, v as TWENTY_STANDARD_APPLICATION_NAME, i as TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, t as computeDeterministicUuid, ie as deserializeApplicationVariableValue, y as getAgentUniversalIdentifier, b as getApplicationVariableUniversalIdentifier, E as getConnectionProviderUniversalIdentifier, D as getFieldPermissionUniversalIdentifier, n as getFieldUniversalIdentifier, O as getFieldsWidgetViewUniversalIdentifier, ee as getFolderNavigationMenuItemUniversalIdentifier, k as getFrontComponentUniversalIdentifier, S as getGlobalCommandMenuItemUniversalIdentifier, C as getGlobalObjectContextCommandMenuItemUniversalIdentifier, A as getIndexUniversalIdentifier, P as getLinkNavigationMenuItemUniversalIdentifier, j as getLogicFunctionUniversalIdentifier, T as getNavigationCommandUniversalIdentifier, M as getObjectNavigationMenuItemUniversalIdentifier, F as getObjectPermissionUniversalIdentifier, I as getObjectUniversalIdentifier, L as getPageLayoutTabUniversalIdentifier, z as getPageLayoutUniversalIdentifier, V as getPageLayoutWidgetUniversalIdentifier, H as getPermissionFlagUniversalIdentifier, B as getRecordPageLayoutUniversalIdentifier, w as getRecordSelectionCommandMenuItemUniversalIdentifier, U as getRolePermissionFlagUniversalIdentifier, W as getRoleTargetUniversalIdentifier, G as getRoleUniversalIdentifier, K as getSearchFieldUniversalIdentifier, q as getSelectOptionUniversalIdentifier, J as getSkillUniversalIdentifier, a as getSystemRelationFieldUniversalIdentifier, r as getSystemViewFieldUniversalIdentifier, o as getSystemViewUniversalIdentifier, Y as getViewFieldGroupUniversalIdentifier, X as getViewFieldUniversalIdentifier, Z as getViewFilterUniversalIdentifier, Q as getViewGroupUniversalIdentifier, N as getViewNavigationMenuItemUniversalIdentifier, $ as getViewSortUniversalIdentifier, te as getViewUniversalIdentifier, c as isKnownApplicationCategory, re as serializeApplicationVariableValue };\n", "import {\n  DEFAULT_API_URL_NAME,\n  DEFAULT_APP_ACCESS_TOKEN_NAME,\n} from 'twenty-shared/application';\n\nexport const postGraphqlRequest = async <TVariables, TData>({\n  query,\n  variables,\n  caller,\n}: {\n  query: string;\n  variables: TVariables;\n  caller: string;\n}): Promise<TData> => {\n  const apiUrl = process.env[DEFAULT_API_URL_NAME];\n  const accessToken = process.env[DEFAULT_APP_ACCESS_TOKEN_NAME];\n\n  if (!apiUrl || !accessToken) {\n    throw new Error(\n      `${caller}() requires the app runtime env vars ` +\n        `${DEFAULT_API_URL_NAME} and ${DEFAULT_APP_ACCESS_TOKEN_NAME}.`,\n    );\n  }\n\n  const response = await fetch(`${apiUrl}/metadata`, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json',\n      Authorization: `Bearer ${accessToken}`,\n    },\n    body: JSON.stringify({ query, variables }),\n  });\n\n  if (!response.ok) {\n    throw new Error(\n      `${caller}() failed: HTTP ${response.status} ${response.statusText}`,\n    );\n  }\n\n  const body = (await response.json()) as {\n    data?: TData;\n    errors?: { message: string }[];\n  };\n\n  if (body.errors && body.errors.length > 0) {\n    throw new Error(\n      `${caller}() failed: ${body.errors.map((error) => error.message).join(', ')}`,\n    );\n  }\n\n  if (!body.data) {\n    throw new Error(`${caller}() failed: response contained no data.`);\n  }\n\n  return body.data;\n};\n", "import { AppConnectionAuthFailedError } from '@/sdk/logic-function/connections/errors/app-connection-auth-failed.error';\nimport { type AppConnection } from '@/sdk/logic-function/connections/types/app-connection.type';\nimport { postGraphqlRequest } from '@/sdk/logic-function/utils/post-graphql-request.util';\n\nconst GET_APP_CONNECTION_QUERY = `\n  query GetAppConnection($id: ID!) {\n    appConnection(id: $id) {\n      id\n      providerName\n      name\n      handle\n      visibility\n      userWorkspaceId\n      accessToken\n      scopes\n      authFailedAt\n    }\n  }\n`;\n\nexport const getConnection = async (id: string): Promise<AppConnection> => {\n  const { appConnection } = await postGraphqlRequest<\n    { id: string },\n    { appConnection: AppConnection }\n  >({\n    query: GET_APP_CONNECTION_QUERY,\n    variables: { id },\n    caller: 'getConnection',\n  });\n\n  if (appConnection.authFailedAt !== null) {\n    throw new AppConnectionAuthFailedError(appConnection.id);\n  }\n\n  return appConnection;\n};\n", "import { type AppConnection } from '@/sdk/logic-function/connections/types/app-connection.type';\nimport { postGraphqlRequest } from '@/sdk/logic-function/utils/post-graphql-request.util';\n\nconst LIST_APP_CONNECTIONS_QUERY = `\n  query ListAppConnections($filter: ListAppConnectionsInput) {\n    appConnections(filter: $filter) {\n      id\n      providerName\n      name\n      handle\n      visibility\n      userWorkspaceId\n      accessToken\n      scopes\n      authFailedAt\n    }\n  }\n`;\n\nexport type ListConnectionsFilter = {\n  providerName?: string;\n  userWorkspaceId?: string;\n  visibility?: 'user' | 'workspace';\n};\n\nexport const listConnections = async (\n  filter: ListConnectionsFilter = {},\n): Promise<AppConnection[]> => {\n  const { appConnections } = await postGraphqlRequest<\n    { filter: ListConnectionsFilter },\n    { appConnections: AppConnection[] }\n  >({\n    query: LIST_APP_CONNECTIONS_QUERY,\n    variables: { filter },\n    caller: 'listConnections',\n  });\n\n  return appConnections;\n};\n", "import { type AppConnection } from '@/sdk/logic-function/connections/types/app-connection.type';\n\nexport const findConnectionForRequest = (\n  connections: AppConnection[],\n  event: { userWorkspaceId: string | null },\n): AppConnection | null => {\n  if (event.userWorkspaceId !== null) {\n    const personal = connections.find(\n      (connection) =>\n        connection.visibility === 'user' &&\n        connection.userWorkspaceId === event.userWorkspaceId,\n    );\n\n    if (personal) {\n      return personal;\n    }\n  }\n\n  const workspaceShared = connections.find(\n    (connection) => connection.visibility === 'workspace',\n  );\n\n  return workspaceShared ?? null;\n};\n", "import {\n  type RunAgentInput,\n  type RunAgentResult,\n} from 'twenty-shared/application';\n\nimport { postGraphqlRequest } from '@/sdk/logic-function/utils/post-graphql-request.util';\n\nconst RUN_AGENT_MUTATION = `\n  mutation RunAgent($input: RunAgentInput!) {\n    runAgent(input: $input) {\n      result\n      error\n      success\n    }\n  }\n`;\n\nexport const runAgent = async (\n  input: RunAgentInput,\n): Promise<RunAgentResult> => {\n  const { runAgent: result } = await postGraphqlRequest<\n    { input: RunAgentInput },\n    { runAgent: RunAgentResult }\n  >({\n    query: RUN_AGENT_MUTATION,\n    variables: { input },\n    caller: 'runAgent',\n  });\n\n  return result;\n};\n", "import { MetadataApiClient } from 'twenty-client-sdk/metadata';\nimport {\n  type EnqueueJobInput,\n  type EnqueueJobResult,\n} from 'twenty-shared/application';\n\nexport const enqueueJob = async (\n  input: EnqueueJobInput,\n): Promise<EnqueueJobResult> => {\n  const client = new MetadataApiClient();\n\n  const { enqueueJob: result } = await client.mutation({\n    enqueueJob: {\n      __args: { input },\n      enqueued: true,\n      logicFunctionUniversalIdentifier: true,\n    },\n  });\n\n  return result;\n};\n", "import {\n  type AppKeyValue,\n  type AppKeyValueScope,\n} from 'twenty-shared/application';\n\nimport { postGraphqlRequest } from '@/sdk/logic-function/utils/post-graphql-request.util';\n\nconst GET_APP_KEY_VALUE_QUERY = `\n  query GetAppKeyValue($key: String!, $scope: AppKeyValueScope) {\n    appKeyValue(key: $key, scope: $scope) {\n      key\n      value\n      scope\n    }\n  }\n`;\n\nconst SET_APP_KEY_VALUE_MUTATION = `\n  mutation SetAppKeyValue($input: SetAppKeyValueInput!) {\n    setAppKeyValue(input: $input) {\n      key\n      value\n      scope\n    }\n  }\n`;\n\nconst DELETE_APP_KEY_VALUE_MUTATION = `\n  mutation DeleteAppKeyValue($key: String!, $scope: AppKeyValueScope) {\n    deleteAppKeyValue(key: $key, scope: $scope)\n  }\n`;\n\nconst DEFAULT_APP_KEY_VALUE_SCOPE: AppKeyValueScope = 'WORKSPACE';\n\ntype KvOptions = {\n  scope?: AppKeyValueScope;\n};\n\nexport const kv = {\n  async get<TValue = unknown>(\n    key: string,\n    options?: KvOptions,\n  ): Promise<TValue | null> {\n    const { appKeyValue } = await postGraphqlRequest<\n      { key: string; scope: AppKeyValueScope },\n      { appKeyValue: AppKeyValue | null }\n    >({\n      query: GET_APP_KEY_VALUE_QUERY,\n      variables: { key, scope: options?.scope ?? DEFAULT_APP_KEY_VALUE_SCOPE },\n      caller: 'kv.get',\n    });\n\n    return (appKeyValue?.value ?? null) as TValue | null;\n  },\n\n  async set<TValue>(\n    key: string,\n    value: TValue,\n    options?: KvOptions,\n  ): Promise<void> {\n    await postGraphqlRequest<\n      {\n        input: { key: string; value: TValue; scope: AppKeyValueScope };\n      },\n      { setAppKeyValue: AppKeyValue }\n    >({\n      query: SET_APP_KEY_VALUE_MUTATION,\n      variables: {\n        input: {\n          key,\n          value,\n          scope: options?.scope ?? DEFAULT_APP_KEY_VALUE_SCOPE,\n        },\n      },\n      caller: 'kv.set',\n    });\n  },\n\n  async delete(key: string, options?: KvOptions): Promise<boolean> {\n    const { deleteAppKeyValue } = await postGraphqlRequest<\n      { key: string; scope: AppKeyValueScope },\n      { deleteAppKeyValue: boolean }\n    >({\n      query: DELETE_APP_KEY_VALUE_MUTATION,\n      variables: { key, scope: options?.scope ?? DEFAULT_APP_KEY_VALUE_SCOPE },\n      caller: 'kv.delete',\n    });\n\n    return deleteAppKeyValue;\n  },\n};\n", "import { type LogicFunctionHttpResponse } from 'twenty-shared/types';\n\nexport type ResponseInit = {\n  status?: number;\n  headers?: Record<string, string>;\n};\n\nexport class Response implements LogicFunctionHttpResponse {\n  readonly __twentyHttpResponse = true as const;\n  readonly body: unknown;\n  readonly status?: number;\n  readonly headers?: Record<string, string>;\n\n  constructor(body: unknown, init?: ResponseInit) {\n    this.body = body;\n    this.status = init?.status;\n    this.headers = init?.headers;\n  }\n}\n", "/**\n * The narrowest possible view of Twenty's Core API client.\n *\n * `CoreApiClient` from `twenty-client-sdk/core` is typed `any` until\n * `dev:generate-client` regenerates it against the installed workspace schema,\n * so depending on it directly buys no type safety and costs testability \u2014 the\n * class reads `API_URL` / `APP_ACCESS_TOKEN` from the process environment in its\n * constructor, which no unit test has.\n *\n * Everything in this folder therefore talks to `GreenlightApiClient`. The\n * `define*` files are the only place `new CoreApiClient()` appears, and they do\n * nothing but hand it to a handler that can be driven by a fake in tests.\n */\n\nexport interface GreenlightApiClient {\n  query(request: Record<string, unknown>): Promise<unknown>;\n  mutation(request: Record<string, unknown>): Promise<unknown>;\n}\n\nexport const isPlainRecord = (\n  value: unknown,\n): value is Record<string, unknown> =>\n  typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/**\n * Twenty returns connections as `{ edges: [{ node }] }`. Pull the nodes out\n * without assuming the shape is correct \u2014 a schema drift should degrade to\n * \"no records found\", never throw.\n */\nexport const readConnectionNodes = (\n  payload: unknown,\n  connectionName: string,\n): Record<string, unknown>[] => {\n  if (!isPlainRecord(payload)) {\n    return [];\n  }\n\n  const connection = payload[connectionName];\n\n  if (!isPlainRecord(connection)) {\n    return [];\n  }\n\n  const edges = connection['edges'];\n\n  if (!Array.isArray(edges)) {\n    return [];\n  }\n\n  return edges.flatMap((edge): Record<string, unknown>[] => {\n    if (!isPlainRecord(edge)) {\n      return [];\n    }\n\n    const node = edge['node'];\n\n    return isPlainRecord(node) ? [node] : [];\n  });\n};\n\n/** ISO-8601 sorts lexicographically, so this needs no Date parsing. */\nexport const oldestByCreatedAt = (\n  records: readonly Record<string, unknown>[],\n): Record<string, unknown> | null => {\n  let oldest: Record<string, unknown> | null = null;\n  let oldestKey: string | null = null;\n\n  for (const record of records) {\n    const createdAt = record['createdAt'];\n    const key = typeof createdAt === 'string' ? createdAt : '';\n\n    if (oldest === null || oldestKey === null || key < oldestKey) {\n      oldest = record;\n      oldestKey = key;\n    }\n  }\n\n  return oldest;\n};\n\n/**\n * Structured log line. Logic functions have no logger injected \u2014 stdout is what\n * `yarn twenty dev:function:logs` shows \u2014 so everything Greenlight emits is a\n * single JSON object with a stable `event` key that support can grep for.\n */\nexport const logGreenlight = (\n  event: string,\n  detail: Record<string, unknown>,\n): void => {\n  // eslint-disable-next-line no-console\n  console.log(JSON.stringify({ app: 'numaya-greenlight', event, ...detail }));\n};\n\nexport const describeError = (error: unknown): string => {\n  if (error instanceof Error) {\n    return `${error.name}: ${error.message}`;\n  }\n\n  if (typeof error === 'string') {\n    return error;\n  }\n\n  try {\n    return JSON.stringify(error) ?? 'unknown error';\n  } catch {\n    return 'unknown error';\n  }\n};\n", "/**\n * The real licence store: Twenty's app key-value storage.\n *\n * Same shape and the same reasoning as `release-marker-store.ts` \u2014 this is the\n * only licensing module that imports the SDK, which is what lets `licence-run.ts`\n * and the whole of `src/licensing/` be driven by a fake in unit tests.\n *\n * `scope: 'WORKSPACE'` throughout, and it is not a detail. A licence belongs to\n * a workspace, its activation slot is claimed with the workspace id as the\n * fingerprint, and a read under a different scope silently finds nothing \u2014 which\n * would present as \"the cache never survives a restart\", i.e. as a permanent\n * grace-expired state that fails open forever and never says why.\n *\n * ## Reads are tolerant, writes are best-effort\n *\n * `kv.get` returns whatever was stored, which after an upgrade might be a shape\n * this version has never seen. Every read validates the couple of fields it\n * actually depends on and returns `null` / `unknown` otherwise, so a stale entry\n * degrades to \"no cache\" rather than to a `TypeError` inside a cron job.\n *\n * Writes swallow their errors for the same reason install-run's audit write\n * does: failing a licence check because the *cache* could not be written would\n * convert a storage blip into a lost validation, when the validation itself\n * already succeeded.\n */\n\nimport { kv } from 'twenty-sdk/logic-function';\n\nimport {\n  CALIBRATION_CACHE_KV_KEY,\n  CALIBRATION_STATE_KV_KEY,\n  LICENCE_ACTIVATION_KV_KEY,\n  LICENCE_CACHE_KV_KEY,\n  LICENCE_STATE_KV_KEY,\n} from 'src/constants/licence-identifiers';\nimport {\n  type CachedCalibration,\n  type CalibrationReaderPort,\n  type CalibrationStorePort,\n} from 'src/calibration/types';\nimport {\n  describeError,\n  logGreenlight,\n} from 'src/logic-functions/greenlight-api';\nimport {\n  type CachedLicenceValidation,\n  type LicenceActivationState,\n  type LicenceState,\n  type LicenceStorePort,\n} from 'src/licensing/types';\n\nconst SCOPE = { scope: 'WORKSPACE' } as const;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n  typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/**\n * A cache entry is only usable if it carries the three things the offline path\n * reads: when it was taken, and the two gate booleans. Everything else is\n * cosmetic and is allowed to be missing.\n */\nconst readCacheEntry = (value: unknown): CachedLicenceValidation | null => {\n  if (!isRecord(value)) {\n    return null;\n  }\n\n  const { cachedAt, valid, hasFeature } = value;\n\n  if (\n    typeof cachedAt !== 'string' ||\n    typeof valid !== 'boolean' ||\n    typeof hasFeature !== 'boolean'\n  ) {\n    return null;\n  }\n\n  return {\n    cachedAt,\n    maskedKey: typeof value['maskedKey'] === 'string' ? value['maskedKey'] : '',\n    valid,\n    hasFeature,\n    reason: typeof value['reason'] === 'string' ? value['reason'] : null,\n    features: Array.isArray(value['features'])\n      ? value['features'].filter((entry): entry is string => typeof entry === 'string')\n      : [],\n    tier: typeof value['tier'] === 'string' ? value['tier'] : null,\n    daysRemaining:\n      typeof value['daysRemaining'] === 'number' ? value['daysRemaining'] : null,\n    expiryWarning: value['expiryWarning'] === true,\n    expiryAt: typeof value['expiryAt'] === 'string' ? value['expiryAt'] : null,\n  };\n};\n\nconst readActivationState = (value: unknown): LicenceActivationState => {\n  if (!isRecord(value)) {\n    return { status: 'unknown' };\n  }\n\n  const status = value['status'];\n  const at = typeof value['at'] === 'string' ? value['at'] : '';\n\n  if (status === 'claimed' || status === 'limit_reached') {\n    return { status, at };\n  }\n\n  return { status: 'unknown' };\n};\n\nexport const kvLicenceStore: LicenceStorePort = {\n  readCache: async () => {\n    try {\n      return readCacheEntry(await kv.get<unknown>(LICENCE_CACHE_KV_KEY, SCOPE));\n    } catch (error) {\n      logGreenlight('licence_cache_read_failed', { error: describeError(error) });\n\n      return null;\n    }\n  },\n\n  writeCache: async (entry) => {\n    try {\n      await kv.set(LICENCE_CACHE_KV_KEY, entry, SCOPE);\n    } catch (error) {\n      logGreenlight('licence_cache_write_failed', { error: describeError(error) });\n    }\n  },\n\n  readActivation: async () => {\n    try {\n      return readActivationState(\n        await kv.get<unknown>(LICENCE_ACTIVATION_KV_KEY, SCOPE),\n      );\n    } catch (error) {\n      logGreenlight('licence_activation_read_failed', {\n        error: describeError(error),\n      });\n\n      return { status: 'unknown' };\n    }\n  },\n\n  writeActivation: async (state) => {\n    try {\n      await kv.set(LICENCE_ACTIVATION_KV_KEY, state, SCOPE);\n    } catch (error) {\n      logGreenlight('licence_activation_write_failed', {\n        error: describeError(error),\n      });\n    }\n  },\n\n  publishState: async (state) => {\n    try {\n      await kv.set(LICENCE_STATE_KV_KEY, state, SCOPE);\n    } catch (error) {\n      logGreenlight('licence_state_publish_failed', { error: describeError(error) });\n    }\n  },\n};\n\n/* -------------------------------------------------------------------------- */\n/* Calibration                                                                 */\n/* -------------------------------------------------------------------------- */\n\n/**\n * A cached calibration is only usable if it carries when it was taken and a\n * payload with a version. Everything else is allowed to be missing and is\n * defaulted, for the same reason the entitlement cache is read tolerantly: an\n * entry written by a previous release must degrade to \"no calibration\" \u2014 i.e.\n * to shipped defaults \u2014 rather than to a `TypeError` inside a cron job.\n *\n * The lists are *not* re-validated element by element here. They were validated\n * and normalised by `src/calibration/parse.ts` before the signature was checked,\n * and re-deriving that on every read would spend a scoring run's time to\n * re-establish a fact the write already established.\n */\nconst readCalibrationEntry = (value: unknown): CachedCalibration | null => {\n  if (!isRecord(value)) {\n    return null;\n  }\n\n  const { cachedAt, calibration, sealTag, verifiedAt, keyId } = value;\n\n  if (typeof cachedAt !== 'string' || !isRecord(calibration)) {\n    return null;\n  }\n\n  if (typeof calibration['calibrationVersion'] !== 'number') {\n    return null;\n  }\n\n  // Every field the seal covers is read strictly, with no defaulting. A\n  // defaulted value would change the canonical bytes the tag is checked against\n  // and turn a legitimate entry into a seal mismatch \u2014 the failure would look\n  // like tampering, which is the most misleading thing it could look like. An\n  // entry missing any of them is simply not a sealed entry.\n  if (\n    typeof sealTag !== 'string' ||\n    sealTag.length === 0 ||\n    typeof verifiedAt !== 'string' ||\n    typeof keyId !== 'string'\n  ) {\n    return null;\n  }\n\n  return {\n    cachedAt,\n    verifiedAt,\n    keyId,\n    sealTag,\n    calibration: calibration as unknown as CachedCalibration['calibration'],\n  };\n};\n\nexport const kvCalibrationStore: CalibrationStorePort = {\n  readCalibration: async () => {\n    try {\n      return readCalibrationEntry(\n        await kv.get<unknown>(CALIBRATION_CACHE_KV_KEY, SCOPE),\n      );\n    } catch (error) {\n      logGreenlight('calibration_cache_read_failed', {\n        error: describeError(error),\n      });\n\n      return null;\n    }\n  },\n\n  writeCalibration: async (entry) => {\n    try {\n      await kv.set(CALIBRATION_CACHE_KV_KEY, entry, SCOPE);\n    } catch (error) {\n      logGreenlight('calibration_cache_write_failed', {\n        error: describeError(error),\n      });\n    }\n  },\n\n  clearCalibration: async () => {\n    try {\n      // `kv.set(key, null)` rather than a delete: the SDK's key-value surface\n      // has no delete, and a stored `null` fails `readCalibrationEntry`'s record\n      // check, which is exactly \"no calibration\". Writing an empty object would\n      // not \u2014 it would parse as a record and then fail on a missing field, which\n      // is the same outcome by a longer route and one more shape to reason about.\n      await kv.set(CALIBRATION_CACHE_KV_KEY, null, SCOPE);\n    } catch (error) {\n      logGreenlight('calibration_cache_clear_failed', {\n        error: describeError(error),\n      });\n    }\n  },\n\n  publishCalibrationState: async (state) => {\n    try {\n      await kv.set(CALIBRATION_STATE_KV_KEY, state, SCOPE);\n    } catch (error) {\n      logGreenlight('calibration_state_publish_failed', {\n        error: describeError(error),\n      });\n    }\n  },\n};\n\n/**\n * The scoring path's view: read-only, and narrower than the store above.\n *\n * A scoring run must not be handed something it could write through \u2014 the same\n * argument that keeps `readPublishedLicenceState` off `LicenceStorePort`. A run\n * that could rewrite the calibration cache would be a run that could change what\n * every *subsequent* lead is scored against, from inside a per-lead code path.\n */\nexport const kvCalibrationReader: CalibrationReaderPort = {\n  read: () => kvCalibrationStore.readCalibration(),\n};\n\n/**\n * Read the published calibration state back, for the admin panel.\n *\n * Returns `null` rather than a synthetic \"shipped defaults\" state when nothing\n * has been published: \"this workspace has never run a licence check\" and \"this\n * workspace ran one and is on shipped defaults\" are different things to show a\n * human, and inventing the second would hide the first.\n */\nexport const readPublishedCalibrationState = async (): Promise<unknown> => {\n  try {\n    const value = await kv.get<unknown>(CALIBRATION_STATE_KV_KEY, SCOPE);\n\n    return isRecord(value) && typeof value['status'] === 'string' ? value : null;\n  } catch (error) {\n    logGreenlight('calibration_state_read_failed', {\n      error: describeError(error),\n    });\n\n    return null;\n  }\n};\n\n/**\n * Read the published state back \u2014 what a future enrichment path calls before\n * consulting `isEnrichmentEnabled`. Deliberately not on `LicenceStorePort`: the\n * port is the *writer's* interface, and enrichment has no business being handed\n * something it could write through.\n */\nexport const readPublishedLicenceState = async (): Promise<LicenceState | null> => {\n  try {\n    const value = await kv.get<unknown>(LICENCE_STATE_KV_KEY, SCOPE);\n\n    if (!isRecord(value) || typeof value['mode'] !== 'string') {\n      return null;\n    }\n\n    return value as unknown as LicenceState;\n  } catch (error) {\n    logGreenlight('licence_state_read_failed', { error: describeError(error) });\n\n    return null;\n  }\n};\n", "/**\n * Tolerant parsing of the licensing service's `validate` response.\n *\n * The response is JSON from a service we do not deploy in lockstep with this\n * app. It will change shape at some point \u2014 a field renamed, a number arriving\n * as a string, a proxy returning an HTML error page with a `200`. None of those\n * may throw inside a customer's workspace, because the caller is a nightly cron\n * job whose failure is silent and whose consequence is a licence that stops\n * being re-checked.\n *\n * So parsing is total and returns a discriminated result: either a fully\n * populated response with every field defaulted, or `malformed`. There is no\n * middle state and no `undefined` anywhere downstream.\n *\n * ## What counts as malformed\n *\n * Only one thing: `valid` is not a boolean. That field is the entire contract \u2014\n * `LICENSING_INTEGRATION.md` is explicit that a failed validation is a normal\n * `200` and that the caller must branch on the boolean. A body without a usable\n * `valid` is not a licence answer, whatever else it contains.\n *\n * Everything else degrades. A missing `hasFeature` is `false` (fail closed on\n * the *paid feature*, which is the safe direction \u2014 the customer keeps rules\n * scoring either way). A missing `daysRemaining` is `null`, not `0`, because\n * `0` would read as \"expires today\" in the admin notice.\n *\n * ## The service's own spec over-promises, and this is why that costs nothing\n *\n * `ValidateLicenseResponse` in the published OpenAPI document marks nineteen\n * fields `required`, including `reason`, `gracePeriodDaysRemaining` and\n * `maintenanceExpiryAt`. A healthy live response on 2026-08-04 carried none of\n * those three: `reason` is simply absent when the licence is valid, and the two\n * grace/maintenance fields were absent from every response observed, valid and\n * invalid alike.\n *\n * A parser written to the spec \u2014 destructuring required fields, or validating\n * their presence \u2014 would therefore have rejected every real response the\n * service sends. Tolerant parsing was the right call for a reason better than\n * the one originally given: not \"the schema will drift one day\", but \"the\n * schema is already wrong today\".\n */\n\nimport { type LicenceValidationResponse } from 'src/licensing/types';\n\nexport type ParsedValidation =\n  | { readonly kind: 'parsed'; readonly response: LicenceValidationResponse }\n  | { readonly kind: 'malformed'; readonly detail: string };\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n  typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst readBoolean = (value: unknown, fallback: boolean): boolean =>\n  typeof value === 'boolean' ? value : fallback;\n\nconst readString = (value: unknown): string | null =>\n  typeof value === 'string' && value.length > 0 ? value : null;\n\n/**\n * Accepts a numeric string too. Numbers crossing a JSON boundary from a .NET or\n * JVM service occasionally arrive quoted, and `daysRemaining: \"14\"` meaning\n * `null` would silently suppress every expiry warning.\n */\nconst readNumber = (value: unknown): number | null => {\n  if (typeof value === 'number' && Number.isFinite(value)) {\n    return value;\n  }\n\n  if (typeof value === 'string' && value.trim().length > 0) {\n    const parsed = Number(value);\n\n    return Number.isFinite(parsed) ? parsed : null;\n  }\n\n  return null;\n};\n\nconst readStringArray = (value: unknown): readonly string[] =>\n  Array.isArray(value)\n    ? value.filter((entry): entry is string => typeof entry === 'string')\n    : [];\n\nexport const parseValidationResponse = (payload: unknown): ParsedValidation => {\n  if (!isRecord(payload)) {\n    return {\n      kind: 'malformed',\n      detail: `expected a JSON object, received ${payload === null ? 'null' : typeof payload}`,\n    };\n  }\n\n  const valid = payload['valid'];\n\n  if (typeof valid !== 'boolean') {\n    return {\n      kind: 'malformed',\n      detail: `response has no boolean \"valid\" field (received ${typeof valid})`,\n    };\n  }\n\n  return {\n    kind: 'parsed',\n    response: {\n      valid,\n      reason: readString(payload['reason']),\n      // Fail closed on the paid capability, open on the product: absent\n      // hasFeature means \"no enrichment\", never \"enrichment\".\n      hasFeature: readBoolean(payload['hasFeature'], false),\n      features: readStringArray(payload['features']),\n      tier: readString(payload['tier']),\n      // The service calls this `type`; `type` is a reserved-ish name in this\n      // codebase's field vocabulary, so it is widened to `licenceType` here and\n      // nowhere else.\n      licenceType: readString(payload['type']),\n      status: readString(payload['status']),\n      daysRemaining: readNumber(payload['daysRemaining']),\n      expiryWarning: readBoolean(payload['expiryWarning'], false),\n      expiryAt: readString(payload['expiryAt']),\n      activatedCount: readNumber(payload['activatedCount']),\n      maxActivations: readNumber(payload['maxActivations']),\n      inGracePeriod: readBoolean(payload['inGracePeriod'], false),\n      // Declared required by the spec, absent from every live response so far.\n      // Read anyway: the day the service starts sending them, a maintenance\n      // window that has already lapsed is something an admin should be able to\n      // see in the audit row rather than something we silently dropped. Nothing\n      // branches on either \u2014 `isEnrichmentEntitled` is still `valid && hasFeature`\n      // and nothing else.\n      gracePeriodDaysRemaining: readNumber(payload['gracePeriodDaysRemaining']),\n      maintenanceExpired: readBoolean(payload['maintenanceExpired'], false),\n      maintenanceExpiryAt: readString(payload['maintenanceExpiryAt']),\n      // Undocumented and present on every live response \u2014 the mirror image of\n      // the three fields above, which are documented and present on none.\n      licenceId: readString(payload['licenseId']),\n      // Passed through untouched, including `undefined`. Validating it here\n      // would mean the licensing parser knowing what calibration is, and the\n      // whole point of `unknown` is that it does not.\n      customerMetadata: payload['customerMetadata'],\n    },\n  };\n};\n", "/**\n * Licence-key masking.\n *\n * `LICENCE_KEY` is a secret application variable. Twenty keeps it out of its own\n * UI and logs, but nothing stops *us* writing it into a structured log line, an\n * audit row's `ruleTrace`, or an error message \u2014 and an audit row is queryable\n * by every workspace admin and survives forever. So the key is masked exactly\n * once, at the edge of the run, and only the mask travels inwards.\n *\n * ## Why the mask keeps no key characters at all\n *\n * The obvious mask \u2014 \"first four, last four\" \u2014 is the wrong trade here. A key\n * is shaped `NUMAYA-XXXXX-XXXXX-XXXXX-X`: the last group is a single character and\n * the whole secret is about nineteen base-32 characters, so showing eight of\n * them is not a redaction, it is a discount. What support actually needs from a\n * mask is only ever *\"is this the same key as the one in that other log line\"*,\n * and a fingerprint answers that without revealing anything.\n *\n * So the mask is:\n *\n *   `NUMAYA-*****-*****-*****-* #6f1c2a9b`\n *\n * \u2014 group *structure* preserved (which makes a malformed key obvious at a\n * glance, e.g. someone pasting an invoice number), zero secret characters, and a\n * stable 32-bit fingerprint that distinguishes two keys from one another.\n *\n * ## Why FNV-1a and not a real hash\n *\n * This module is part of the pure core and may not import `node:crypto` \u2014 the\n * same rule that keeps `src/scoring/` runnable anywhere. FNV-1a is eleven lines\n * and deterministic. It is emphatically *not* a security primitive, and it does\n * not need to be: it never guards anything, it only labels. The key's entropy is\n * not protected by the hash being strong, it is protected by the hash being\n * lossy \u2014 32 bits out of ~95 bits of key means the fingerprint identifies a\n * gigantic equivalence class, not a key.\n */\n\nconst FNV_OFFSET_BASIS = 0x811c9dc5;\nconst FNV_PRIME = 0x01000193;\n\nconst fingerprint = (value: string): string => {\n  let hash = FNV_OFFSET_BASIS;\n\n  for (let index = 0; index < value.length; index += 1) {\n    hash ^= value.charCodeAt(index);\n    // Multiply in 32-bit space without overflowing into float precision.\n    hash = Math.imul(hash, FNV_PRIME) >>> 0;\n  }\n\n  return hash.toString(16).padStart(8, '0');\n};\n\n/**\n * The one product prefix that carries no information \u2014 every Greenlight key\n * starts with it, so echoing it back reveals nothing and makes the mask\n * readable. Any other leading group is masked like the rest.\n */\nconst NON_SECRET_PREFIX = 'NUMAYA';\n\nexport const MISSING_LICENCE_MASK = '(no licence key)';\n\n/**\n * Mask a licence key for logging, audit rows and error messages.\n *\n * Total: an empty, whitespace-only, `undefined` or non-string input returns\n * `MISSING_LICENCE_MASK` rather than throwing, because the commonest reason to\n * call this is precisely that the admin has not set the variable yet.\n */\nexport const maskLicenceKey = (key: string | null | undefined): string => {\n  if (typeof key !== 'string') {\n    return MISSING_LICENCE_MASK;\n  }\n\n  const trimmed = key.trim();\n\n  if (trimmed.length === 0) {\n    return MISSING_LICENCE_MASK;\n  }\n\n  const shape = trimmed\n    .split('-')\n    .map((group, index) =>\n      index === 0 && group.toUpperCase() === NON_SECRET_PREFIX\n        ? NON_SECRET_PREFIX\n        : '*'.repeat(group.length),\n    )\n    .join('-');\n\n  return `${shape} #${fingerprint(trimmed)}`;\n};\n\n/**\n * Last line of defence: strip any occurrence of the key from text that is about\n * to be logged or stored.\n *\n * `maskLicenceKey` is the primary mechanism and this is the belt to its braces \u2014\n * it exists because error messages are written by code we do not own. A `fetch`\n * rejection, a JSON parse error or a future SDK could all echo a request body,\n * and the URL-encoded form of a key is a plausible variant, so both are\n * replaced.\n *\n * Deliberately a no-op when there is no key: passing an empty string must not\n * turn every character of the message into a mask.\n */\nexport const redactLicenceKey = (\n  text: string,\n  key: string | null | undefined,\n): string => {\n  if (typeof key !== 'string') {\n    return text;\n  }\n\n  const trimmed = key.trim();\n\n  if (trimmed.length < 4) {\n    return text;\n  }\n\n  const mask = maskLicenceKey(trimmed);\n\n  return text\n    .split(trimmed)\n    .join(mask)\n    .split(encodeURIComponent(trimmed))\n    .join(mask);\n};\n", "/**\n * The HTTP adapter for the Numaya licensing service \u2014 the only module in\n * Greenlight that opens a socket to Numaya.\n *\n * It implements `LicensingPort`, which is what keeps `src/licensing/` testable\n * without a network: every test in this repo drives the core through a fake\n * port, and this file is exercised on its own against a fake `fetch`.\n *\n * ===========================================================================\n * ## It never throws\n *\n * Both methods return a discriminated outcome instead. A thrown error crossing\n * this boundary would have to be caught identically at every call site, and the\n * call sites are a cron handler and an install hook \u2014 places where one missing\n * `catch` is a workspace that silently stops re-validating and nobody notices\n * for a month. Making failure a return value makes the compiler enumerate it.\n *\n * ## What the status codes mean here\n *\n * | Wire                              | Outcome                      | Why |\n * |-----------------------------------|------------------------------|-----|\n * | `200` + `{ valid: false, \u2026 }`     | `validated`                  | **Not an error.** A failed validation is a normal 200; the caller branches on the boolean. |\n * | `200` + unparseable body          | `malformed_response`         | A proxy error page, or a schema change. |\n * | `401` / `403`                     | `key_rejected`               | The key **is** the credential here. Measured: an unknown or mistyped key gets a bare `401`. |\n * | `404`                             | `licence_not_found`          | On `activate`, this is the misrouted-environment failure. See below. |\n * | `409` on `activate`               | `activation_limit_reached`   | Matched on the **status code**, never on a title \u2014 see below. |\n * | `429`                             | `rate_limited`               | Honours `Retry-After` if present. |\n * | `500` / `502` / `503` / `504`     | `service_unavailable`        | Numaya-side, retriable on the next run. |\n * | anything else non-2xx             | `unexpected_status`          | Deliberately not silently retried. |\n * | thrown (DNS, TLS, timeout, abort) | `transport_failure`          | Includes a base URL that does not resolve. |\n *\n * ### Gotcha 3 \u2014 match the activation limit on the status code\n *\n * The REST layer answers a consumed licence with RFC 7807 problem details:\n *\n * ```json\n * { \"type\": \"https://httpstatuses.io/409\", \"title\": \"max_activations_reached\",\n *   \"status\": 409, \"detail\": \"Max activations reached for this license.\",\n *   \"instance\": \"/v1/licenses/activate\", \"retryable\": false }\n * ```\n *\n * The `title` is present over REST, and absent from the body previously observed\n * through the MCP layer \u2014 which is exactly why the match is\n * `response.status === 409` and only that. The status code is the half of the\n * contract both surfaces agree on. The body is never inspected.\n *\n * ### Gotcha 4 \u2014 `activate` reports a wrong environment as a `404`\n *\n * `validate` hides a misrouted environment inside a `200` (see\n * `src/licensing/entitlement.ts`). `activate` does not \u2014 it returns a bare\n * `404 license_not_found`. Classifying that as `unexpected_status` would bury\n * the clearest signal the service gives us, so it gets its own kind and lands on\n * the same admin notice as the `200` form.\n *\n * ## The licence key does not appear in anything this module returns\n *\n * Failure details are built from the error's name and message, then passed\n * through `redactLicenceKey`. That second step is belt-and-braces: the messages\n * come from `fetch`, from `JSON.parse` and from future runtimes, none of which\n * we control, and at least one plausible implementation echoes the request. Both\n * the raw and URL-encoded forms are replaced.\n * ===========================================================================\n */\n\nimport {\n  DEFAULT_LICENCE_ENVIRONMENT,\n  LICENCE_ACTIVATE_PATH,\n  LICENCE_ENVIRONMENT_HEADER,\n  LICENCE_REQUEST_FIELDS,\n  LICENCE_VALIDATE_PATH,\n} from 'src/constants/licence-identifiers';\nimport { parseValidationResponse } from 'src/licensing/parse';\nimport { redactLicenceKey } from 'src/licensing/mask';\nimport {\n  type LicenceActivateOutcome,\n  type LicenceCallFailure,\n  type LicenceEnvironmentName,\n  type LicensingPort,\n  type LicenceValidateOutcome,\n} from 'src/licensing/types';\n\n/** Injected so tests never touch the network and never need a timer. */\nexport type FetchLike = (\n  url: string,\n  init: {\n    method: string;\n    headers: Record<string, string>;\n    body: string;\n    signal?: AbortSignal;\n  },\n) => Promise<{\n  readonly ok: boolean;\n  readonly status: number;\n  readonly headers: { get(name: string): string | null };\n  text(): Promise<string>;\n}>;\n\nexport interface HttpLicensingClientOptions {\n  readonly baseUrl: string;\n  /**\n   * Which side of the service to ask. Optional, and `production` when omitted \u2014\n   * the same default the application variable ships with, so a caller that has\n   * not been updated behaves exactly as this client did before the header\n   * existed rather than silently switching a customer to sandbox.\n   */\n  readonly environment?: LicenceEnvironmentName;\n  readonly fetchImpl?: FetchLike | null;\n  /**\n   * 8 seconds. The nightly cron is given 30s and the install hook 60s, so this\n   * leaves room for the surrounding API writes. A licence check that takes\n   * longer than eight seconds is a service that is down, not a service that is\n   * slow, and the cache ladder is a better answer than waiting.\n   */\n  readonly timeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 8_000;\n\nconst describe = (error: unknown): string => {\n  if (error instanceof Error) {\n    return `${error.name}: ${error.message}`;\n  }\n\n  return typeof error === 'string' ? error : 'unknown transport error';\n};\n\n/**\n * `Retry-After` is defined as either seconds or an HTTP date. Only the numeric\n * form is read: the date form would need a clock, and this value is advisory \u2014\n * it is recorded for the audit row and never used to schedule anything, because\n * the next attempt is tomorrow's cron regardless.\n */\nconst readRetryAfter = (headers: { get(name: string): string | null }): number | null => {\n  const raw = headers.get('retry-after');\n\n  if (raw === null) {\n    return null;\n  }\n\n  const seconds = Number(raw.trim());\n\n  return Number.isFinite(seconds) && seconds >= 0 ? seconds : null;\n};\n\nconst classifyStatus = (\n  status: number,\n  headers: { get(name: string): string | null },\n): LicenceCallFailure => {\n  if (status === 429) {\n    return { kind: 'rate_limited', retryAfterSeconds: readRetryAfter(headers) };\n  }\n\n  if (status >= 500) {\n    return { kind: 'service_unavailable', statusCode: status };\n  }\n\n  // The licence key is the credential on these endpoints \u2014 it travels in the\n  // request body in place of an `Authorization` header \u2014 so a 401 means the\n  // service does not recognise the key at all, in any environment. Measured\n  // live: an unknown key, and a real key with one character changed, both\n  // return `401` with an empty body.\n  if (status === 401 || status === 403) {\n    return { kind: 'key_rejected', statusCode: status };\n  }\n\n  // Note what is *not* here: `404`. On `validate` a 404 means the path was\n  // wrong \u2014 a misconfigured base URL \u2014 not that the licence is missing, because\n  // `validate` reports a missing licence in-band with a `200`. Only `activate`\n  // uses 404 to mean \"no such licence\", and that is handled at its call site.\n  return { kind: 'unexpected_status', statusCode: status };\n};\n\nconst timeoutSignal = (timeoutMs: number): AbortSignal | undefined => {\n  // `AbortSignal.timeout` is available on Node 20+ and in the logic-function\n  // runtime; guarded anyway so a fake `fetch` in a stripped environment does not\n  // take the whole module down over a nicety.\n  if (typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function') {\n    return AbortSignal.timeout(timeoutMs);\n  }\n\n  return undefined;\n};\n\nconst normaliseBaseUrl = (baseUrl: string): string =>\n  baseUrl.trim().replace(/\\/+$/, '');\n\n/**\n * Build the licensing client.\n *\n * A blank base URL is not an error to throw \u2014 it is a configuration the admin\n * has not finished, and it produces a `transport_failure` so it walks the same\n * cache \u2192 grace \u2192 rules-only ladder as a real outage. One ladder, one set of\n * tests, no special case.\n */\nexport const createHttpLicensingClient = (\n  options: HttpLicensingClientOptions,\n): LicensingPort => {\n  const baseUrl = normaliseBaseUrl(options.baseUrl ?? '');\n  const environment = options.environment ?? DEFAULT_LICENCE_ENVIRONMENT;\n  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n  /**\n   * The header is sent **only** for sandbox. Production is the service's own\n   * default, so sending `X-Numaya-Environment: production` would be a no-op that\n   * every request then carries \u2014 and a header we send unconditionally is one\n   * that has to be right unconditionally. Omitting it keeps the production path\n   * byte-for-byte what it was before this feature existed.\n   */\n  const environmentHeaders: Record<string, string> =\n    environment === 'sandbox' ? { [LICENCE_ENVIRONMENT_HEADER]: 'sandbox' } : {};\n  const doFetch =\n    options.fetchImpl ??\n    (typeof globalThis.fetch === 'function'\n      ? (globalThis.fetch as unknown as FetchLike)\n      : null);\n\n  const post = async (\n    path: string,\n    body: Record<string, unknown>,\n    key: string,\n  ): Promise<\n    | { kind: 'ok'; payload: unknown }\n    | { kind: 'status'; status: number; headers: { get(name: string): string | null } }\n    | LicenceCallFailure\n  > => {\n    if (baseUrl.length === 0) {\n      return {\n        kind: 'transport_failure',\n        detail: 'LICENCE_API_BASE_URL is not configured',\n      };\n    }\n\n    if (doFetch === null) {\n      return {\n        kind: 'transport_failure',\n        detail: 'no fetch implementation available in this runtime',\n      };\n    }\n\n    let response: Awaited<ReturnType<FetchLike>>;\n\n    try {\n      response = await doFetch(`${baseUrl}${path}`, {\n        method: 'POST',\n        headers: {\n          'content-type': 'application/json',\n          accept: 'application/json',\n          ...environmentHeaders,\n        },\n        body: JSON.stringify(body),\n        signal: timeoutSignal(timeoutMs),\n      });\n    } catch (error) {\n      return {\n        kind: 'transport_failure',\n        detail: redactLicenceKey(describe(error), key),\n      };\n    }\n\n    if (!response.ok) {\n      return { kind: 'status', status: response.status, headers: response.headers };\n    }\n\n    let raw: string;\n\n    try {\n      raw = await response.text();\n    } catch (error) {\n      return {\n        kind: 'malformed_response',\n        detail: redactLicenceKey(describe(error), key),\n      };\n    }\n\n    try {\n      return { kind: 'ok', payload: JSON.parse(raw) as unknown };\n    } catch (error) {\n      return {\n        kind: 'malformed_response',\n        detail: redactLicenceKey(describe(error), key),\n      };\n    }\n  };\n\n  return {\n    validate: async ({ key, deviceFingerprint, feature }): Promise<LicenceValidateOutcome> => {\n      const body: Record<string, unknown> = { [LICENCE_REQUEST_FIELDS.key]: key };\n\n      if (typeof deviceFingerprint === 'string' && deviceFingerprint.length > 0) {\n        body[LICENCE_REQUEST_FIELDS.deviceFingerprint] = deviceFingerprint;\n      }\n\n      if (typeof feature === 'string' && feature.length > 0) {\n        body[LICENCE_REQUEST_FIELDS.feature] = feature;\n      }\n\n      const result = await post(LICENCE_VALIDATE_PATH, body, key);\n\n      if (result.kind === 'status') {\n        // A 409 on `validate` is not the activation-limit error \u2014 that only\n        // arises from `activate`. Left as an unexpected status rather than\n        // pattern-matched into something more specific we have never observed.\n        return classifyStatus(result.status, result.headers);\n      }\n\n      if (result.kind !== 'ok') {\n        return result;\n      }\n\n      const parsed = parseValidationResponse(result.payload);\n\n      return parsed.kind === 'parsed'\n        ? { kind: 'validated', response: parsed.response }\n        : { kind: 'malformed_response', detail: parsed.detail };\n    },\n\n    activate: async ({\n      key,\n      deviceFingerprint,\n      deviceName,\n    }): Promise<LicenceActivateOutcome> => {\n      const body: Record<string, unknown> = {\n        [LICENCE_REQUEST_FIELDS.key]: key,\n        [LICENCE_REQUEST_FIELDS.deviceFingerprint]: deviceFingerprint,\n      };\n\n      if (typeof deviceName === 'string' && deviceName.length > 0) {\n        body[LICENCE_REQUEST_FIELDS.deviceName] = deviceName;\n      }\n\n      const result = await post(LICENCE_ACTIVATE_PATH, body, key);\n\n      if (result.kind === 'status') {\n        // Gotcha 3. Status code only \u2014 the two surfaces disagree about the body.\n        if (result.status === 409) {\n          return { kind: 'activation_limit_reached' };\n        }\n\n        // Gotcha 4. `activate` has no in-band way to say \"not found\", so it uses\n        // a 404 \u2014 measured live, that is exactly what a sandbox licence returns\n        // when the request is routed to production.\n        if (result.status === 404) {\n          return { kind: 'licence_not_found', statusCode: 404 };\n        }\n\n        return classifyStatus(result.status, result.headers);\n      }\n\n      if (result.kind !== 'ok') {\n        return result;\n      }\n\n      // Activation is idempotent on the fingerprint and returns the existing\n      // activation id on a repeat call, so the id is informational: nothing\n      // branches on it. A body we cannot read is therefore still a successful\n      // activation \u2014 refusing to record the claimed slot because the id was\n      // missing would be strictly worse.\n      const payload = result.payload;\n      const id =\n        typeof payload === 'object' && payload !== null\n          ? ((payload as Record<string, unknown>)['id'] ??\n            (payload as Record<string, unknown>)['activationId'])\n          : null;\n\n      return { kind: 'activated', activationId: typeof id === 'string' ? id : null };\n    },\n  };\n};\n", "/**\n * How licence state reaches a human \u2014 the audit row and the admin notice.\n *\n * ===========================================================================\n * ## Why this is a problem worth solving rather than a log line\n *\n * The whole point of failing open is that a licence problem is *quiet*: the\n * product keeps working, leads keep scoring, nobody's day breaks. That is\n * exactly what makes it dangerous. An expired licence that silently downgrades\n * the product produces a support ticket six weeks later reading \"enrichment\n * stopped working at some point, we don't know when\" \u2014 and by then the audit\n * trail is the only way to answer it.\n *\n * So every validation is recorded, including the boring ones. One row a night\n * is 365 rows a year against an object built for one row per scored lead; the\n * cost is nil and the alternative \u2014 only recording changes \u2014 means the row you\n * want is the one that was suppressed because \"nothing changed\".\n *\n * ## Where it surfaces\n *\n * Three places, in descending order of how likely an admin is to see them:\n *\n *  1. **`GreenlightAuditLog`** \u2014 the object already has a view and a sidebar\n *     item, is sorted newest-first, and is the place admins are already told to\n *     look when they ask \"why did this happen\". Licence rows land there next to\n *     scoring decisions, which is right: from the customer's point of view they\n *     are the same story.\n *  2. **App key-value storage** (`LICENCE_STATE_KV_KEY`) \u2014 the resolved state,\n *     republished on every run, for the Greenlight main page to render as a\n *     banner. Cheap to read, always current, no query.\n *  3. **Structured logs** \u2014 `logGreenlight('licence_resolved', \u2026)`, for support\n *     with server access.\n *\n * ## The event type is `LICENCE`, for every severity\n *\n * These rows used to file under `CONFIG_CHANGED` when healthy and `ERROR` when\n * not, because `GreenlightAuditLog.eventType` had no option of its own. It has\n * one now, and both halves of that compromise are gone rather than one:\n *\n *   - `CONFIG_CHANGED` was never true. Nobody changed any configuration; a\n *     scheduled check ran and reported what the licence server already thought.\n *   - `ERROR` is worse. `eventType` says *what happened*, and what happened is a\n *     licence check \u2014 in exactly the way `scoring-run.ts` files a held lead as\n *     `GATED` and leaves the `decision` column to say which kind of hold it was.\n *     Reserving `ERROR` for \"Greenlight itself failed\" is what keeps a red row\n *     meaning something: an expired licence is a customer's commercial state,\n *     not a fault in this app, and filing it as a fault is how an admin learns\n *     to ignore red.\n *\n * The severity survives the move and is not inferred from the event type: it is\n * carried explicitly in `ruleTrace.severity`, and the row's summary leads with\n * the admin-facing headline. `LICENCE` is yellow in the SELECT, which is the\n * colour for \"look at this\" without the claim that something broke.\n *\n * Every row's summary still starts with a `LICENCE` prefix, so the text search\n * that isolated these rows before the option existed keeps working.\n *\n * ## Nothing here may contain the licence key\n *\n * `LicenceState` has no field that could hold one \u2014 it carries `maskedKey`, and\n * `maskLicenceKey` emits no characters of the secret. This module never sees the\n * raw key, so there is no code path from the key to a row. That is asserted in\n * `__tests__/audit.test.ts` by scanning the serialised row for the sandbox keys.\n * ===========================================================================\n */\n\nimport {\n  type LicenceEnvironmentName,\n  type LicenceSeverity,\n  type LicenceState,\n} from 'src/licensing/types';\n\nexport const LICENSING_CLIENT_VERSION = '0.1.0';\n\n/** Prefix on every licence audit summary, so the rows are greppable today. */\nexport const LICENCE_AUDIT_PREFIX = 'LICENCE';\n\n/* -------------------------------------------------------------------------- */\n/* Admin-facing description                                                    */\n/* -------------------------------------------------------------------------- */\n\nexport interface LicenceNotice {\n  readonly severity: LicenceSeverity;\n  /** One line, suitable as a banner title or an audit row summary. */\n  readonly headline: string;\n  /** One or two sentences telling the admin what to actually do. */\n  readonly detail: string;\n}\n\nconst daysPhrase = (daysRemaining: number | null): string =>\n  daysRemaining === null\n    ? 'soon'\n    : daysRemaining <= 0\n      ? 'today'\n      : daysRemaining === 1\n        ? 'in 1 day'\n        : `in ${daysRemaining} days`;\n\nconst hoursSince = (cacheAgeMs: number | null): string =>\n  cacheAgeMs === null ? 'never' : `${Math.floor(cacheAgeMs / 3_600_000)}h ago`;\n\n/**\n * States published before `environment` existed read back as `undefined`. The\n * fallback keeps a stale row readable rather than rendering \"the undefined\n * environment\" at the admin; the next nightly run replaces it with the truth.\n */\nconst environmentLabel = (environment: LicenceEnvironmentName): string =>\n  environment === 'sandbox' ? 'sandbox' : 'production';\n\n/** The setting to try, named rather than implied. */\nconst otherEnvironmentLabel = (environment: LicenceEnvironmentName): string =>\n  environment === 'sandbox' ? 'production' : 'sandbox';\n\n/**\n * Turn a resolved state into something an admin can act on.\n *\n * Every `detail` names the next action. A notice that says \"your licence is\n * invalid\" and stops is a notice that generates a support ticket; one that says\n * which lever to pull does not.\n */\nexport const describeLicenceForAdmin = (state: LicenceState): LicenceNotice => {\n  const expiring =\n    state.expiryWarning ||\n    (state.daysRemaining !== null && state.daysRemaining <= 7);\n\n  switch (state.reason) {\n    case 'licensed':\n      return expiring\n        ? {\n            severity: state.severity,\n            headline: `Greenlight licence expires ${daysPhrase(state.daysRemaining)}`,\n            detail:\n              'Enrichment is running normally. Renew before the expiry date to avoid dropping to rules-only scoring. Deterministic scoring and the gate continue either way.',\n          }\n        : {\n            severity: state.severity,\n            headline: 'Greenlight licence active',\n            detail: `Enrichment is enabled${state.tier === null ? '' : ` on the ${state.tier} tier`}.`,\n          };\n\n    case 'no_licence_key':\n      return {\n        severity: state.severity,\n        headline: 'Greenlight is running in rules-only mode',\n        detail:\n          'No licence key is configured, so AI and search enrichment are off. Deterministic scoring, the gate and the queue all work without one. Add a key in the app settings to enable enrichment.',\n      };\n\n    case 'feature_not_licensed':\n      return {\n        severity: state.severity,\n        headline: 'Enrichment is not included in this licence',\n        detail:\n          'The licence is valid and in good standing, but does not include the enrichment capability. Rules-only scoring is unaffected. Upgrade the licence to turn enrichment on.',\n      };\n\n    // The one notice in this file that names a Greenlight setting as the likely\n    // culprit, because it is the one failure the service reports with a message\n    // that reads like a customer problem when it is almost always ours. It says\n    // which environment was asked, what to change, and \u2014 explicitly \u2014 that the\n    // key is not the thing to go and re-check. See gotcha 4 in `entitlement.ts`.\n    case 'licence_not_found_in_environment':\n      return {\n        severity: state.severity,\n        headline: `Licence key was accepted but no licence exists in the ${environmentLabel(state.environment)} environment`,\n        detail:\n          `This is almost certainly a misconfiguration, not a bad key: the licensing service accepted the key as valid credentials, then found no licence attached to it in the ${environmentLabel(state.environment)} environment. Set LICENCE_ENVIRONMENT to ${otherEnvironmentLabel(state.environment)} in the app settings if this workspace is licensed there, and re-run the licence check. Do not re-enter the key \u2014 a wrong or mistyped key is rejected outright and reports \"licence was rejected\" instead. If the environment is already correct, the licence has been deleted and Numaya AI support can restore it. Scoring, the gate and the queue are unaffected throughout.`,\n      };\n\n    case 'expired':\n      return {\n        severity: state.severity,\n        headline: 'Greenlight licence has expired',\n        detail:\n          'Enrichment is off. Scoring, the gate and the queue continue to work. Renew the licence to restore enrichment.',\n      };\n\n    case 'suspended':\n      return {\n        severity: state.severity,\n        headline: 'Greenlight licence is suspended',\n        detail:\n          'Enrichment is off. This is usually a billing issue and is reversible \u2014 contact Numaya AI support. Scoring is unaffected.',\n      };\n\n    case 'revoked':\n      return {\n        severity: state.severity,\n        headline: 'Greenlight licence has been revoked',\n        detail:\n          'Enrichment is off and this cannot be reversed on the existing key. Contact Numaya AI support for a replacement. Scoring is unaffected.',\n      };\n\n    case 'quota_exceeded':\n      return {\n        severity: state.severity,\n        headline: 'Greenlight enrichment quota reached',\n        detail:\n          'Enrichment is paused until the quota period resets. Scoring is unaffected.',\n      };\n\n    case 'device_not_activated':\n      return {\n        severity: state.severity,\n        headline: 'This workspace is not activated against the licence',\n        detail:\n          'Reinstall the app, or contact Numaya AI support, to claim an activation slot. Scoring is unaffected.',\n      };\n\n    case 'max_activations_reached':\n      return {\n        severity: state.severity,\n        headline: 'Licence is already in use by another workspace',\n        detail:\n          'Every activation slot on this licence is taken, so this workspace runs rules-only. Free a slot on the other workspace or add a seat, then reinstall Greenlight here.',\n      };\n\n    case 'invalid_licence':\n      return {\n        severity: state.severity,\n        headline: 'Greenlight licence was rejected',\n        detail:\n          'The licensing service refused this key outright \u2014 it does not recognise it in any environment. Check the key in the app settings for a typo or a truncated paste, then contact Numaya AI support. Scoring is unaffected.',\n      };\n\n    case 'licence_service_rate_limited':\n      return {\n        severity: state.severity,\n        headline: 'Licence check was rate-limited',\n        detail:\n          'Numaya returned HTTP 429, so this workspace could not re-check its licence. Enrichment is paused until the next nightly check succeeds; scoring, the gate and the queue are unaffected. Nothing needs doing unless this persists for several days.',\n      };\n\n    case 'licence_response_malformed':\n      return {\n        severity: state.severity,\n        headline: 'Licence service returned an unreadable response',\n        detail:\n          'The licensing service answered, but not in a shape this version understands \u2014 usually a proxy or an upgrade in progress. Enrichment is paused; scoring is unaffected. Report it to Numaya AI support if it persists.',\n      };\n\n    case 'licence_service_unreachable':\n      return {\n        severity: state.severity,\n        headline: 'Cannot reach the Numaya AI licensing service',\n        detail: `Enrichment is paused while the cached entitlement ages out (last successful check ${hoursSince(state.cacheAgeMs)}). Check outbound network access to the licensing service. Scoring is unaffected.`,\n      };\n\n    case 'licence_service_unreachable_grace_expired':\n      return {\n        severity: state.severity,\n        headline:\n          state.cacheAgeMs === null\n            ? 'Greenlight has never successfully checked its licence'\n            : 'Licence has not been verified for over 72 hours',\n        detail: `Enrichment is off until a licence check succeeds (last successful check ${hoursSince(state.cacheAgeMs)}). Confirm LICENCE_API_BASE_URL and outbound network access. Scoring, the gate and the queue are unaffected and will keep running indefinitely.`,\n      };\n  }\n};\n\n/* -------------------------------------------------------------------------- */\n/* Audit row                                                                   */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The structured payload written to `ruleTrace`. This is the row that answers\n * \"what did Numaya say, and what did we do about it, at 03:17 on the 14th\".\n *\n * Carries `valid` and `hasFeature` as separate fields on purpose \u2014 collapsing\n * them into the resulting mode would erase precisely the distinction that the\n * independence gotcha makes load-bearing.\n */\nexport interface LicenceAuditDetail {\n  readonly checkedAt: string;\n  readonly maskedKey: string;\n  /**\n   * Recorded on every row, healthy ones included. A row that says\n   * `reason: license_not_found` without saying which environment was asked is a\n   * support ticket; with it, it is a two-minute fix.\n   */\n  readonly environment: string;\n  readonly valid: boolean | null;\n  readonly hasFeature: boolean | null;\n  readonly reason: string;\n  readonly source: 'live' | 'cache' | 'grace' | 'none';\n  readonly cached: boolean;\n  readonly cacheAgeHours: number | null;\n  readonly mode: string;\n  readonly severity: LicenceSeverity;\n  readonly failureKind: string | null;\n  readonly tier: string | null;\n  readonly daysRemaining: number | null;\n  readonly expiryWarning: boolean;\n  readonly expiryAt: string | null;\n}\n\nexport const buildLicenceAuditDetail = (\n  state: LicenceState,\n): LicenceAuditDetail => ({\n  checkedAt: state.checkedAt,\n  maskedKey: state.maskedKey,\n  environment: environmentLabel(state.environment),\n  valid: state.observedValid,\n  hasFeature: state.observedHasFeature,\n  reason: state.reason,\n  source: state.source,\n  // Spelled out as a boolean as well as a source, because \"cached vs live\" is\n  // the question support asks and `source: 'grace'` does not obviously answer\n  // it.\n  cached: state.source !== 'live',\n  cacheAgeHours:\n    state.cacheAgeMs === null\n      ? null\n      : Math.round((state.cacheAgeMs / 3_600_000) * 10) / 10,\n  mode: state.mode,\n  severity: state.severity,\n  failureKind: state.failureKind,\n  tier: state.tier,\n  daysRemaining: state.daysRemaining,\n  expiryWarning: state.expiryWarning,\n  expiryAt: state.expiryAt,\n});\n\n/**\n * The `data` argument for `createGreenlightAuditLog`.\n *\n * Shaped to match the rows `install-run.ts` writes: the lead-pointer fields are\n * empty strings rather than omitted, because the object's `SELECT` fields have\n * literal defaults and a partially-populated row reads worse in the view than a\n * deliberately blank one. `band` and `decision` are `UNSCORED` for the same\n * reason \u2014 this row describes no lead, and `UNSCORED` is the vocabulary's word\n * for \"not applicable\".\n */\nexport const buildLicenceAuditRow = (\n  state: LicenceState,\n  /**\n   * The calibration outcome of the same run, if there was one.\n   *\n   * Passed in as a loose record rather than typed against\n   * `src/calibration/types.ts` for the same reason `LicenceValidationResponse`\n   * types `customerMetadata` as `unknown`: the licensing core must not acquire\n   * a dependency on what calibration is. It reads three fields for display and\n   * copies the rest verbatim into the trace.\n   *\n   * It is on the row rather than only in the panel because an audit row outlives\n   * the panel's \"now\". \"Which vocabulary was this workspace scoring on last\n   * March\" is a question asked months later, and the only place that can answer\n   * it is the append-only trail.\n   */\n  calibration?: {\n    readonly status?: unknown;\n    readonly reason?: unknown;\n    readonly calibrationVersion?: unknown;\n    readonly refreshedAt?: unknown;\n  } | null,\n): Record<string, unknown> => {\n  const notice = describeLicenceForAdmin(state);\n\n  // \"calibration v1\" or \"shipped defaults\" \u2014 the whole answer to \"am I running\n  // the calibrated lists?\", short enough to sit in a row name in a list view.\n  const calibrationLabel =\n    calibration === null || calibration === undefined\n      ? null\n      : calibration.status === 'calibrated' &&\n          typeof calibration.calibrationVersion === 'number'\n        ? `calibration v${calibration.calibrationVersion}`\n        : 'shipped defaults';\n\n  return {\n    name:\n      calibrationLabel === null\n        ? `${LICENCE_AUDIT_PREFIX} \u00B7 ${notice.headline} \u00B7 ${state.mode}`\n        : `${LICENCE_AUDIT_PREFIX} \u00B7 ${notice.headline} \u00B7 ${state.mode} \u00B7 ${calibrationLabel}`,\n    // One value for every severity. See the module comment: the severity lives\n    // in `ruleTrace.severity` and in the headline above, not in a borrowed\n    // event type.\n    eventType: 'LICENCE',\n    occurredAt: state.checkedAt,\n    leadObjectNameSingular: '',\n    leadDisplayName: '',\n    actorType: 'SYSTEM',\n    actorDisplayName: `Greenlight licensing ${LICENSING_CLIENT_VERSION}`,\n    ruleTrace: {\n      ...buildLicenceAuditDetail(state),\n      adminDetail: notice.detail,\n      ...(calibration === null || calibration === undefined\n        ? {}\n        : { calibration }),\n    },\n    score: null,\n    band: 'UNSCORED',\n    decision: 'UNSCORED',\n    overrideReason: '',\n    traceId: '',\n  };\n};\n", "/**\n * Cache freshness and offline-grace arithmetic.\n *\n * Pure: `now` is passed in, never read. The scoring engine holds the same line\n * for the same reason \u2014 a clock read inside a decision function makes the\n * decision untestable at its boundaries, and every interesting case here *is* a\n * boundary.\n *\n * ---------------------------------------------------------------------------\n * ## The windows, and why they are these windows\n *\n * From the error-state table in `LICENSING_INTEGRATION.md`:\n *\n * | Cache age        | Behaviour                                     |\n * |------------------|-----------------------------------------------|\n * | `< 24h`          | Cached entitlement used as-is                 |\n * | `24h \u2013 72h`      | Rules-only, \"grace period\"                    |\n * | `> 72h`          | Rules-only + visible warning                  |\n *\n * Note that grace **ends at 72h from `cachedAt`**, not at 24h + 72h. The prose\n * elsewhere reads \"24h cache, then a 72h offline grace window\", which would put\n * the cliff at 96h; the table is the specific claim and the table is what is\n * implemented. The difference is 24 hours of degraded-but-warned operation and\n * it changes nothing about safety, because **nothing is ever blocked at any\n * point on this ladder** \u2014 past 72h the product is still scoring leads, it is\n * just scoring them without enrichment and saying so loudly.\n *\n * ## Why grace disables enrichment rather than extending it\n *\n * Because the two failure modes are not symmetric. Trusting a stale entitlement\n * for three days means potentially serving a paid feature to a revoked licence,\n * and revocation is the one lever that has to work. Turning enrichment off means\n * the customer loses an optional enhancement during a Numaya outage. The second\n * is recoverable in seconds when the service returns; the first is not\n * recoverable at all.\n *\n * The 24h band is the exception, and it is bounded precisely so that \"Numaya\n * restarted a container\" never costs a customer anything.\n * ---------------------------------------------------------------------------\n */\n\nconst HOUR_MS = 60 * 60 * 1000;\n\n/** Entitlements younger than this are used exactly as if they were live. */\nexport const LICENCE_CACHE_TTL_MS = 24 * HOUR_MS;\n\n/**\n * Age at which the offline grace window closes, measured from `cachedAt`.\n * Past this the product warns visibly \u2014 it does not stop.\n */\nexport const LICENCE_OFFLINE_GRACE_LIMIT_MS = 72 * HOUR_MS;\n\nexport type CacheFreshness = 'fresh' | 'grace' | 'expired';\n\n/**\n * Age of a cached entitlement in milliseconds, or `null` when `cachedAt` is\n * missing or unparseable.\n *\n * A negative age \u2014 a cache written by a machine whose clock is ahead \u2014 is\n * clamped to `0` rather than rejected. The alternative is that a small clock\n * skew on the licensing service makes a workspace look like it has never\n * validated, which is a worse outcome than briefly treating a slightly-future\n * entitlement as fresh.\n */\nexport const cacheAgeMs = (\n  cachedAt: string | null | undefined,\n  now: Date,\n): number | null => {\n  if (typeof cachedAt !== 'string' || cachedAt.length === 0) {\n    return null;\n  }\n\n  const cachedMs = Date.parse(cachedAt);\n\n  if (Number.isNaN(cachedMs)) {\n    return null;\n  }\n\n  return Math.max(0, now.getTime() - cachedMs);\n};\n\n/**\n * Boundaries are inclusive at the *lower* edge of the worse band: an entitlement\n * exactly 24h old is already in grace, and one exactly 72h old is already\n * expired. Erring towards the stricter band on the boundary keeps the rule\n * \"fresh means strictly under a day old\" true as stated.\n */\nexport const classifyCacheAge = (ageMs: number): CacheFreshness => {\n  if (ageMs < LICENCE_CACHE_TTL_MS) {\n    return 'fresh';\n  }\n\n  if (ageMs < LICENCE_OFFLINE_GRACE_LIMIT_MS) {\n    return 'grace';\n  }\n\n  return 'expired';\n};\n\n/**\n * `null` when there is no usable cache at all \u2014 which is not the same as an\n * expired one, and the admin notice says so differently.\n */\nexport const classifyCache = (\n  cachedAt: string | null | undefined,\n  now: Date,\n): { readonly freshness: CacheFreshness | null; readonly ageMs: number | null } => {\n  const ageMs = cacheAgeMs(cachedAt, now);\n\n  return {\n    freshness: ageMs === null ? null : classifyCacheAge(ageMs),\n    ageMs,\n  };\n};\n", "/**\n * The entitlement gate: turning one `validate` response into \"may enrichment\n * run, and if not, why not\".\n *\n * This module exists because the obvious implementation is wrong in two\n * directions at once, and both were confirmed by live test rather than inferred\n * from the service's documented reason codes.\n *\n * ---------------------------------------------------------------------------\n * ## Gotcha 1 \u2014 `valid` and `hasFeature` are independent\n *\n * The service documents a reason code `feature_not_licensed`, which invites the\n * assumption that an unlicensed feature makes the whole validation fail. It does\n * not. Two observed responses:\n *\n * | Licence                       | `valid` | `reason`      | `hasFeature` |\n * |-------------------------------|---------|---------------|--------------|\n * | Rules-only, asked `enrichment` | `true`  | `null`        | **`false`**  |\n * | Suspended, asked `enrichment`  | `false` | `\"suspended\"` | **`true`**   |\n *\n * So each single-boolean check is wrong in a different and expensive way:\n *\n * - `if (response.valid)` \u2014 enables the **paid feature on a licence that does\n *   not include it**. Revenue leak, and it would never surface as a bug report\n *   because the customer is delighted.\n * - `if (response.hasFeature)` \u2014 keeps enrichment running on a **suspended**\n *   licence, because `hasFeature` still reports the policy's feature list.\n *   Suspension is the reversible lever Numaya pulls on non-payment; ignoring it\n *   makes the lever useless.\n *\n * The only correct gate is the conjunction, and it is written exactly once, in\n * `isEnrichmentEntitled` below. Nothing else in this codebase may read\n * `.hasFeature` on its own.\n *\n * ## The reason we have to invent\n *\n * When `valid: true, hasFeature: false`, the service sends `reason: null` \u2014\n * there is nothing wrong with the licence, it simply does not include the\n * feature. That is still the single most important thing to tell an admin who is\n * wondering why enrichment is off, so we *derive* `feature_not_licensed`\n * ourselves. It is in the `LicenceReason` union for that reason and no other:\n * it never arrives over the wire.\n *\n * ---------------------------------------------------------------------------\n * ## Gotcha 4 \u2014 `license_not_found` cannot mean \"the key has a typo\"\n *\n * This is the one that would have cost us a customer.\n *\n * A sandbox licence is invisible to a request that does not carry\n * `X-Numaya-Environment: sandbox`. What comes back is not an error:\n *\n * ```\n * POST /v1/licenses/validate  {key, feature}            \u2192 200 valid:false reason:license_not_found\n * POST /v1/licenses/validate  {key, feature} + sandbox  \u2192 200 valid:true  hasFeature:true\n * ```\n *\n * A plausible rejection with a plausible reason, on the happy status code. Read\n * naively it says \"bad key\", the admin retypes a key that was always correct,\n * and the workspace sits in rules-only forever while every layer of the\n * fail-open design politely hides it.\n *\n * ### Why treating it as an environment problem is honest\n *\n * Because the service itself separates the two causes, and we measured where it\n * draws the line (2026-08-04, live):\n *\n * | Key                                        | Response                            |\n * |--------------------------------------------|-------------------------------------|\n * | Not a key the service knows, any shape     | **`401`**, empty body               |\n * | One character altered in a real key        | **`401`**, empty body               |\n * | A real key, wrong environment              | `200 valid:false license_not_found` |\n * | A real key, right environment              | `200 valid:true`                    |\n *\n * The key **is** the credential on these endpoints \u2014 it is sent in the request\n * body in place of an `Authorization` header \u2014 so a key the service cannot\n * resolve at all fails authentication and never reaches the licence lookup.\n * `license_not_found` is therefore only reachable by a key that authenticated:\n * it exists, it is spelled correctly, and no licence matched it *in the\n * environment this request asked for*.\n *\n * That leaves exactly two causes, and the notice in `audit.ts` names both:\n * we asked the wrong environment, or the licence was deleted from the right\n * one. What it must never say is \"check the key for a typo\" \u2014 the 401 above is\n * what a typo actually looks like, and it maps to `invalid_licence`, which says\n * precisely that.\n *\n * No signal here is invented. We do not guess the environment from the shape of\n * the key: customer licence keys are `NUMAYA-XXXXX-XXXXX-XXXXX-X` in **both**\n * environments and carry no sandbox marker. (The `nml_live_` / `nml_test_`\n * prefixes in the spec belong to org API keys, which Greenlight never holds.)\n * The only thing we know is which environment *we* asked for, and that is a\n * local fact we can state without inference.\n * ---------------------------------------------------------------------------\n */\n\nimport { LICENCE_NOT_FOUND_REASON } from 'src/constants/licence-identifiers';\nimport {\n  type LicenceReason,\n  type LicenceValidationResponse,\n} from 'src/licensing/types';\n\n/**\n * The reason strings the service is documented to send when `valid: false`.\n * Anything outside this set is mapped to `invalid_licence` rather than passed\n * through, so a new reason code shipped by Numaya cannot inject an unknown\n * string into audit rows the admin filters on.\n *\n * `device_not_activated` is listed even though it was never observed for our\n * policy types \u2014 see gotcha 2 in `state.ts`. If a future `NodeLocked` policy\n * does produce it, it should read as itself, not as a generic failure.\n */\nconst KNOWN_INVALID_REASONS: ReadonlySet<string> = new Set<LicenceReason>([\n  'revoked',\n  'suspended',\n  'expired',\n  'device_not_activated',\n  'feature_not_licensed',\n  'quota_exceeded',\n]);\n\nexport const normaliseInvalidReason = (\n  reason: string | null,\n): LicenceReason => {\n  // Gotcha 4. Checked before the known-reason set because the service's spelling\n  // (`license_not_found`) is not a member of `LicenceReason` and would otherwise\n  // fall through to `invalid_licence` \u2014 whose admin notice tells the customer to\n  // check their key for a typo, which is the single most misleading thing we\n  // could say about a correctly-typed key pointed at the wrong environment.\n  if (reason !== null && reason.trim().toLowerCase() === LICENCE_NOT_FOUND_REASON) {\n    return 'licence_not_found_in_environment';\n  }\n\n  if (reason !== null && KNOWN_INVALID_REASONS.has(reason)) {\n    return reason as LicenceReason;\n  }\n\n  return 'invalid_licence';\n};\n\n/**\n * The gate. **Both** booleans, always.\n *\n * Takes a structural type rather than the full response so the cached\n * entitlement \u2014 which stores the same two booleans and nothing else about the\n * decision \u2014 goes through the identical predicate. One gate, two callers, no\n * chance of the cache path drifting from the live path.\n */\nexport const isEnrichmentEntitled = (entitlement: {\n  readonly valid: boolean;\n  readonly hasFeature: boolean;\n}): boolean => entitlement.valid === true && entitlement.hasFeature === true;\n\n/**\n * Why enrichment is on or off, for one entitlement.\n *\n * Order matters and is the whole point:\n *\n *  1. `!valid` wins outright \u2014 a suspended licence is suspended regardless of\n *     what its feature list still claims. Reporting `feature_not_licensed` for\n *     a suspended licence with the feature would send the admin to the wrong\n *     support page entirely.\n *  2. `valid && !hasFeature` is the derived `feature_not_licensed` \u2014 the\n *     licence is healthy, this capability simply is not part of it. This is the\n *     upgrade prompt, not an error.\n *  3. Otherwise: licensed.\n */\nexport const entitlementReason = (entitlement: {\n  readonly valid: boolean;\n  readonly hasFeature: boolean;\n  readonly reason: string | null;\n}): LicenceReason => {\n  if (!entitlement.valid) {\n    return normaliseInvalidReason(entitlement.reason);\n  }\n\n  if (!entitlement.hasFeature) {\n    return 'feature_not_licensed';\n  }\n\n  return 'licensed';\n};\n\n/**\n * Convenience for the live path: both answers from one response.\n */\nexport const resolveEntitlement = (\n  response: LicenceValidationResponse,\n): { readonly entitled: boolean; readonly reason: LicenceReason } => ({\n  entitled: isEnrichmentEntitled(response),\n  reason: entitlementReason(response),\n});\n", "/**\n * `resolveLicenceState` \u2014 the entire licensing decision, as one pure function.\n *\n * Everything impure happened before this call: the HTTP adapter turned the wire\n * into a `LicenceValidateOutcome`, the store read the cache, the run masked the\n * key and read the clock. What is left is arithmetic and a decision table, which\n * is why every interesting case in this module has a test rather than a comment\n * promising it works.\n *\n * ===========================================================================\n * ## The golden rule this module exists to protect\n *\n * *Greenlight never hard-blocks on a licence state.* Every branch below returns\n * either `full` or `rules-only`, and `rules-only` is a fully working product:\n * deterministic scoring, the gate, the queue, the audit trail. There is no\n * branch that stops scoring, and there is deliberately no way to express one \u2014\n * `LicenceMode` has two members and neither of them is \"off\".\n *\n * The earlier draft of the integration spec proposed a hard block once the\n * offline grace window expired. It is withdrawn: a Numaya-side outage lasting\n * three days would then start silently dropping a customer's leads on the floor,\n * which is a far worse outcome for them *and* for us than three days of\n * unlicensed enrichment would have been for us alone.\n *\n * ===========================================================================\n * ## Gotcha 2 \u2014 activation is not a precondition for validity\n *\n * The service documents a `device_not_activated` reason, which invites the\n * classic recovery loop: validate, see `device_not_activated`, call `activate`,\n * validate again. For our policies that loop is dead code. Validating a\n * brand-new Trial licence **before any activation at all** returned\n * `valid: true, activatedCount: 0`. Activation on `Trial` and `Subscription`\n * types is slot consumption, not a gate.\n *\n * So this module never treats a missing activation as invalidating, never\n * re-validates after activating, and `activate` is called exactly once, at\n * install, from `licence-run.ts`. `device_not_activated` remains in the reason\n * vocabulary only because a future `NodeLocked` policy might genuinely produce\n * it, and it should then read as itself.\n *\n * ## Why activation state is sticky, and why that is not a contradiction\n *\n * Gotcha 2 says validity does not depend on activation. Gotcha 3 says exceeding\n * the activation limit is a bare `409` on the `activate` call. Put together,\n * they have a consequence that neither states on its own:\n *\n *   A workspace whose activation was refused with `409` \u2014 because the licence\n *   is already bound to a different workspace \u2014 will still get\n *   `valid: true, hasFeature: true` from `validate`, because validation does not\n *   check slots for these licence types.\n *\n * If the `409` were only handled at install time, the very next nightly run\n * would see a healthy validation and quietly switch enrichment back on. The\n * activation limit would be unenforceable in practice. So the `409` is persisted\n * (`LicenceActivationState`) and re-applied on every resolution, *after* the\n * entitlement decision, as an override. It is cleared only by a subsequent\n * successful `activate` \u2014 i.e. by the admin actually freeing a slot or buying a\n * seat, and reinstalling.\n *\n * ===========================================================================\n * ## The decision table\n *\n * | Input                                     | Mode         | Reason                                      | Source  |\n * |-------------------------------------------|--------------|---------------------------------------------|---------|\n * | No `LICENCE_KEY`                          | `rules-only` | `no_licence_key`                            | `none`  |\n * | Live: `valid && hasFeature`               | `full`       | `licensed`                                  | `live`  |\n * | Live: `valid && !hasFeature`              | `rules-only` | `feature_not_licensed` *(derived)*          | `live`  |\n * | Live: `!valid`                            | `rules-only` | `suspended` / `expired` / `revoked` / \u2026     | `live`  |\n * | Live: `!valid`, `license_not_found`       | `rules-only` | `licence_not_found_in_environment`          | `live`  |\n * | `401` \u2014 key refused as a credential       | `rules-only` | `invalid_licence` *(no cache fallback)*     | `none`  |\n * | `404` on activate \u2014 wrong environment     | `rules-only` | `licence_not_found_in_environment`          | `none`  |\n * | Failure, cache `< 24h`                    | as cached    | as cached                                   | `cache` |\n * | Failure, cache `24\u201372h`                   | `rules-only` | `licence_service_unreachable` *(or 429/\u2026)*  | `grace` |\n * | Failure, cache `> 72h` or never validated | `rules-only` | `\u2026_grace_expired` *(or 429/\u2026)*              | `none`  |\n * | Any of the above + sticky `409`           | `rules-only` | `max_activations_reached`                   | *kept*  |\n * ===========================================================================\n */\n\nimport {\n  classifyCache,\n  type CacheFreshness,\n} from 'src/licensing/cache';\nimport {\n  entitlementReason,\n  isEnrichmentEntitled,\n} from 'src/licensing/entitlement';\nimport {\n  type CachedLicenceValidation,\n  type LicenceActivationState,\n  type LicenceCallFailure,\n  type LicenceEnvironmentName,\n  type LicenceKeyPresence,\n  type LicenceReason,\n  type LicenceSeverity,\n  type LicenceState,\n  type LicenceValidateOutcome,\n  type LicenceValidationResponse,\n} from 'src/licensing/types';\n\n/* -------------------------------------------------------------------------- */\n/* Severity                                                                    */\n/* -------------------------------------------------------------------------- */\n\n/**\n * How loudly the admin hears about a reason.\n *\n * `feature_not_licensed` is a **notice**, not a problem: the licence is healthy\n * and the customer simply has not bought enrichment. Rendering that in red next\n * to a genuine revocation would train admins to ignore the red.\n *\n * `no_licence_key` is likewise a notice. Running Greenlight unlicensed is a\n * supported configuration \u2014 it is the free floor, not a fault.\n */\nconst REASON_SEVERITY: Readonly<Record<LicenceReason, LicenceSeverity>> = {\n  licensed: 'ok',\n  no_licence_key: 'notice',\n  feature_not_licensed: 'notice',\n  // A `problem`, and deliberately the same rank as a revocation. The whole\n  // failure mode is that it looks survivable \u2014 a 200, a plausible reason, a\n  // product that keeps working \u2014 so the one place it is allowed to be loud is\n  // here. See gotcha 4 in `entitlement.ts`.\n  licence_not_found_in_environment: 'problem',\n  expired: 'problem',\n  revoked: 'problem',\n  suspended: 'problem',\n  quota_exceeded: 'warning',\n  device_not_activated: 'warning',\n  max_activations_reached: 'problem',\n  invalid_licence: 'problem',\n  licence_service_rate_limited: 'warning',\n  licence_response_malformed: 'warning',\n  licence_service_unreachable: 'warning',\n  licence_service_unreachable_grace_expired: 'problem',\n};\n\nconst SEVERITY_RANK: Readonly<Record<LicenceSeverity, number>> = {\n  ok: 0,\n  notice: 1,\n  warning: 2,\n  problem: 3,\n};\n\nconst worst = (a: LicenceSeverity, b: LicenceSeverity): LicenceSeverity =>\n  SEVERITY_RANK[a] >= SEVERITY_RANK[b] ? a : b;\n\n/**\n * Renewal is a deadline, so it escalates on its own clock rather than waiting\n * for the licence to actually break. `expiryWarning` is the service's own flag;\n * the seven-day floor is ours, because a Trial is fourteen days long and a\n * warning that only fires on the service's schedule may arrive too late for a\n * purchase order to clear.\n */\nexport const EXPIRY_WARNING_DAYS = 7;\n\nconst isExpiringSoon = (\n  daysRemaining: number | null,\n  expiryWarning: boolean,\n): boolean =>\n  expiryWarning || (daysRemaining !== null && daysRemaining <= EXPIRY_WARNING_DAYS);\n\n/* -------------------------------------------------------------------------- */\n/* Failure \u2192 reason                                                            */\n/* -------------------------------------------------------------------------- */\n\n/**\n * What to tell the admin when we could not get a fresh answer and the cache no\n * longer covers us.\n *\n * A `429` and a `503` are not the same event and must not read the same. \"The\n * licence service is unreachable\" sends someone to check DNS and firewalls; if\n * the truth is that we are being rate-limited, that is an hour of their day\n * spent on the wrong system. The mode is identical in every case \u2014 rules-only \u2014\n * so the only thing this choice affects is whether the message is useful.\n */\nconst failureReason = (\n  failure: LicenceCallFailure,\n  covered: boolean,\n): LicenceReason => {\n  switch (failure.kind) {\n    case 'rate_limited':\n      return 'licence_service_rate_limited';\n    case 'malformed_response':\n      return 'licence_response_malformed';\n    case 'key_rejected':\n      // A 401 is the service refusing the key as a credential \u2014 measured live\n      // as what a typo'd or unknown key actually produces. Reporting it as\n      // \"cannot reach the licensing service\" would send an admin to check DNS\n      // and firewalls over a mistyped key.\n      return 'invalid_licence';\n    case 'licence_not_found':\n      // `activate`'s out-of-band form of gotcha 4: a 404 rather than a 200 with\n      // a reason, but the same cause and the same fix.\n      return 'licence_not_found_in_environment';\n    default:\n      return covered\n        ? 'licence_service_unreachable'\n        : 'licence_service_unreachable_grace_expired';\n  }\n};\n\n/**\n * Whether a failure is the service *answering* rather than the service being\n * unavailable.\n *\n * Definitive failures skip the cache \u2192 grace ladder entirely. The ladder exists\n * to ride out an outage; there is no outage to ride out when the service has\n * told us the key is refused or the licence is not in this environment, and\n * serving `full` from a cache for another 24 hours would only delay the notice\n * that fixes it. This is the same treatment `valid: false` already gets.\n */\nconst isDefinitive = (failure: LicenceCallFailure): boolean =>\n  failure.kind === 'key_rejected' || failure.kind === 'licence_not_found';\n\n/* -------------------------------------------------------------------------- */\n/* Cache entry                                                                 */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The snapshot written after a successful live validation.\n *\n * Note what is stored: the two gate booleans, the reason, and the renewal\n * fields. Not the key, not the licence id, not the activation counts. The cache\n * is read by the offline path to answer one question \u2014 \"may enrichment run\" \u2014\n * and storing more than that only widens what a storage dump reveals.\n */\nexport const buildCacheEntry = (\n  response: LicenceValidationResponse,\n  maskedKey: string,\n  now: Date,\n): CachedLicenceValidation => ({\n  cachedAt: now.toISOString(),\n  maskedKey,\n  valid: response.valid,\n  hasFeature: response.hasFeature,\n  reason: response.reason,\n  features: response.features,\n  tier: response.tier,\n  daysRemaining: response.daysRemaining,\n  expiryWarning: response.expiryWarning,\n  expiryAt: response.expiryAt,\n});\n\n/* -------------------------------------------------------------------------- */\n/* Resolution                                                                  */\n/* -------------------------------------------------------------------------- */\n\nexport interface ResolveLicenceStateInput {\n  /** Whether a key is configured, and its mask. Never the key itself. */\n  readonly presence: LicenceKeyPresence;\n  /** `null` when no call was attempted \u2014 i.e. when there is no key. */\n  readonly outcome: LicenceValidateOutcome | null;\n  readonly cached: CachedLicenceValidation | null;\n  readonly activation: LicenceActivationState;\n  /**\n   * Which environment the call was routed to. Required rather than defaulted:\n   * a resolver that quietly assumes `production` is exactly the silent\n   * misrouting this whole module now exists to make visible.\n   */\n  readonly environment: LicenceEnvironmentName;\n  readonly now: Date;\n}\n\nexport interface ResolvedLicence {\n  readonly state: LicenceState;\n  /** Non-null only after a live validation. Nothing else may write the cache. */\n  readonly cacheWrite: CachedLicenceValidation | null;\n}\n\nconst emptyState = (\n  input: ResolveLicenceStateInput,\n  reason: LicenceReason,\n): LicenceState => ({\n  mode: 'rules-only',\n  reason,\n  source: 'none',\n  severity: REASON_SEVERITY[reason],\n  maskedKey: input.presence.maskedKey,\n  environment: input.environment,\n  checkedAt: input.now.toISOString(),\n  cacheAgeMs: null,\n  failureKind: null,\n  observedValid: null,\n  observedHasFeature: null,\n  tier: null,\n  daysRemaining: null,\n  expiryWarning: false,\n  expiryAt: null,\n  features: [],\n});\n\n/**\n * Applied last, over any entitlement decision. See \"Why activation state is\n * sticky\" above \u2014 without this, the `409` is unenforceable because `validate`\n * does not consult activation slots for our licence types.\n *\n * It cannot override `no_licence_key`: a workspace with no key has nothing to\n * say about activation, and reporting `max_activations_reached` there would be\n * nonsense left over from a previous key.\n */\nconst applyActivationOverride = (\n  state: LicenceState,\n  activation: LicenceActivationState,\n): LicenceState => {\n  if (activation.status !== 'limit_reached') {\n    return state;\n  }\n\n  if (state.reason === 'no_licence_key') {\n    return state;\n  }\n\n  return {\n    ...state,\n    mode: 'rules-only',\n    reason: 'max_activations_reached',\n    severity: worst(state.severity, REASON_SEVERITY['max_activations_reached']),\n  };\n};\n\nconst resolveFromLive = (\n  input: ResolveLicenceStateInput,\n  response: LicenceValidationResponse,\n): ResolvedLicence => {\n  const reason = entitlementReason(response);\n  const entitled = isEnrichmentEntitled(response);\n\n  const severity = isExpiringSoon(response.daysRemaining, response.expiryWarning)\n    ? worst(REASON_SEVERITY[reason], 'warning')\n    : REASON_SEVERITY[reason];\n\n  const state: LicenceState = {\n    mode: entitled ? 'full' : 'rules-only',\n    reason,\n    source: 'live',\n    severity,\n    maskedKey: input.presence.maskedKey,\n    environment: input.environment,\n    checkedAt: input.now.toISOString(),\n    cacheAgeMs: 0,\n    failureKind: null,\n    observedValid: response.valid,\n    observedHasFeature: response.hasFeature,\n    tier: response.tier,\n    daysRemaining: response.daysRemaining,\n    expiryWarning: response.expiryWarning,\n    expiryAt: response.expiryAt,\n    features: response.features,\n  };\n\n  return {\n    state: applyActivationOverride(state, input.activation),\n    // The cache records what the service said, not what we decided. A sticky\n    // 409 must not be baked into the entitlement snapshot, or clearing the 409\n    // would leave a poisoned cache behind for up to 24 hours.\n    cacheWrite: buildCacheEntry(response, input.presence.maskedKey, input.now),\n  };\n};\n\nconst resolveFromCache = (\n  input: ResolveLicenceStateInput,\n  failure: LicenceCallFailure,\n  cached: CachedLicenceValidation,\n  freshness: CacheFreshness,\n  ageMs: number,\n): ResolvedLicence => {\n  const covered = freshness === 'grace';\n\n  // `fresh` is the only branch that may still return `full`. Beyond 24h the\n  // entitlement is not trusted for the paid feature \u2014 see cache.ts for why the\n  // two failure directions are not symmetric.\n  const reason: LicenceReason =\n    freshness === 'fresh' ? entitlementReason(cached) : failureReason(failure, covered);\n\n  const mode = freshness === 'fresh' && isEnrichmentEntitled(cached) ? 'full' : 'rules-only';\n\n  const baseSeverity =\n    freshness === 'fresh'\n      ? // Serving from cache is itself worth a notice: the admin should be able\n        // to tell \"validated tonight\" from \"we have not reached Numaya since\n        // Tuesday\", even while nothing is degraded.\n        worst(REASON_SEVERITY[reason], 'notice')\n      : REASON_SEVERITY[reason];\n\n  const severity = isExpiringSoon(cached.daysRemaining, cached.expiryWarning)\n    ? worst(baseSeverity, 'warning')\n    : baseSeverity;\n\n  const state: LicenceState = {\n    mode,\n    reason,\n    source: freshness === 'fresh' ? 'cache' : freshness === 'grace' ? 'grace' : 'none',\n    severity,\n    maskedKey: input.presence.maskedKey,\n    environment: input.environment,\n    checkedAt: input.now.toISOString(),\n    cacheAgeMs: ageMs,\n    failureKind: failure.kind,\n    observedValid: cached.valid,\n    observedHasFeature: cached.hasFeature,\n    tier: cached.tier,\n    daysRemaining: cached.daysRemaining,\n    expiryWarning: cached.expiryWarning,\n    expiryAt: cached.expiryAt,\n    features: cached.features,\n  };\n\n  return {\n    state: applyActivationOverride(state, input.activation),\n    // A failed call never writes the cache. Refreshing `cachedAt` on a failure\n    // would make the 24h and 72h windows unreachable \u2014 the cache would look\n    // permanently fresh while never being re-validated, which is the exact\n    // opposite of what the window is for.\n    cacheWrite: null,\n  };\n};\n\nexport const resolveLicenceState = (\n  input: ResolveLicenceStateInput,\n): ResolvedLicence => {\n  if (!input.presence.hasKey) {\n    return {\n      state: applyActivationOverride(\n        emptyState(input, 'no_licence_key'),\n        input.activation,\n      ),\n      cacheWrite: null,\n    };\n  }\n\n  if (input.outcome !== null && input.outcome.kind === 'validated') {\n    return resolveFromLive(input, input.outcome.response);\n  }\n\n  // No outcome with a key present means the caller could not even attempt the\n  // call (no base URL configured, for instance). Treat it as a transport\n  // failure so it walks the identical cache ladder rather than inventing a\n  // fifteenth branch that would need its own tests.\n  const failure: LicenceCallFailure =\n    input.outcome === null\n      ? { kind: 'transport_failure', detail: 'validation not attempted' }\n      : input.outcome;\n\n  // The service answered. There is nothing to wait out, so no cache ladder.\n  if (isDefinitive(failure)) {\n    const reason = failureReason(failure, false);\n\n    return {\n      state: applyActivationOverride(\n        { ...emptyState(input, reason), failureKind: failure.kind },\n        input.activation,\n      ),\n      cacheWrite: null,\n    };\n  }\n\n  const { freshness, ageMs } = classifyCache(input.cached?.cachedAt, input.now);\n\n  if (input.cached === null || freshness === null || ageMs === null) {\n    const reason = failureReason(failure, false);\n\n    return {\n      state: applyActivationOverride(\n        { ...emptyState(input, reason), failureKind: failure.kind },\n        input.activation,\n      ),\n      cacheWrite: null,\n    };\n  }\n\n  return resolveFromCache(input, failure, input.cached, freshness, ageMs);\n};\n", "/**\n * Canonical serialisation \u2014 the exact bytes that get signed and verified.\n *\n * A detached signature is worthless unless the signer and the verifier agree,\n * byte for byte, on what \"the payload\" is. JSON does not give that for free:\n * key order, whitespace and the treatment of `undefined` are all free choices,\n * and the payload makes a round trip through the licensing service's own JSON\n * storage before we see it again \u2014 a round trip that is entitled to reorder\n * keys and reformat numbers without changing meaning.\n *\n * So this module defines the agreement, and both halves import *this file*\n * rather than reimplementing it. The signer is `ops/calibration/sign.mjs`; the\n * verifier is `verify.ts`. There is no third copy.\n *\n * ---------------------------------------------------------------------------\n * ## The rules\n *\n * 1. **Object keys are sorted** by code unit, recursively. This is the whole\n *    point: a service that re-serialises our JSON may emit keys in any order,\n *    and sorting makes that reordering invisible to the signature.\n * 2. **Arrays keep their order.** Order is meaning here \u2014 `decisionMakerTitles`\n *    is scanned in sequence and the first match wins, so a reordered list is a\n *    genuinely different payload and must break the signature.\n * 3. **No insignificant whitespace**, i.e. `JSON.stringify` with no spacer.\n * 4. **`undefined` and functions are dropped**, exactly as `JSON.stringify`\n *    already drops them, so a key that is absent and a key that is explicitly\n *    `undefined` canonicalise identically. They mean the same thing to every\n *    reader downstream, so they must sign the same.\n * 5. **`null` is preserved**, because `notes: null` and an absent `notes` are\n *    both legal and both mean \"no note\" \u2014 but `null` survives a JSON round trip\n *    and `undefined` does not, so keeping `null` is what makes the round trip\n *    lossless.\n *\n * ## Why not JCS (RFC 8785)\n *\n * JCS is the standards-track answer and would be the right call if the payload\n * were ever exchanged with a third party's implementation. It is not: both ends\n * are this repository. JCS's remaining substance over the four rules above is\n * number canonicalisation, and the payload contains exactly two numbers, both\n * small non-negative integers, for which every JSON encoder in existence agrees.\n * Pulling in a dependency \u2014 into a bundle that ships to customer\n * infrastructure \u2014 to canonicalise `1` was not a trade worth making.\n *\n * If the payload ever grows a float, revisit this. The comment is here so that\n * decision is a decision rather than an accident.\n * ---------------------------------------------------------------------------\n */\n\ntype Json = string | number | boolean | null | Json[] | { [key: string]: Json };\n\n/**\n * Recursively rebuild a value with object keys in sorted order.\n *\n * Note the array branch preserves order and the primitive branch is the\n * identity \u2014 the sort applies to objects and nothing else.\n */\nconst sortValue = (value: unknown, seen: WeakSet<object>): unknown => {\n  if (Array.isArray(value)) {\n    // The cycle guard is checked here as well as on objects, because an array\n    // that contains itself is just as recursive and just as fatal.\n    if (seen.has(value)) {\n      throw new TypeError('circular reference');\n    }\n\n    seen.add(value);\n    const mapped = value.map((entry) => sortValue(entry, seen));\n    seen.delete(value);\n\n    return mapped;\n  }\n\n  if (typeof value === 'object' && value !== null) {\n    // Without this, a cycle recurses until the stack is exhausted and the\n    // `RangeError` is what `canonicalise` ends up catching. That happens to\n    // produce the right answer \u2014 `null` \u2014 but by accident, at the cost of\n    // however deep the runtime's stack is, and it is not a behaviour worth\n    // relying on inside a nightly cron job. `delete` after the descent so a\n    // value legitimately appearing twice in a *tree* is not mistaken for a\n    // cycle.\n    if (seen.has(value)) {\n      throw new TypeError('circular reference');\n    }\n\n    seen.add(value);\n\n    const source = value as Record<string, unknown>;\n    const sorted: Record<string, unknown> = {};\n\n    for (const key of Object.keys(source).sort()) {\n      const entry = source[key];\n\n      // Dropped rather than emitted as `null`: `JSON.stringify` drops\n      // `undefined` properties too, and the two must agree or a payload that\n      // has been through one `JSON.parse` would canonicalise differently from\n      // the same payload in memory.\n      if (entry !== undefined) {\n        sorted[key] = sortValue(entry, seen);\n      }\n    }\n\n    seen.delete(value);\n\n    return sorted;\n  }\n\n  return value;\n};\n\n/**\n * The canonical UTF-8 string for a payload.\n *\n * Returns `null` rather than throwing when the value cannot be represented \u2014\n * a circular reference, or a `BigInt`. Neither can occur in a payload that\n * arrived as parsed JSON, but this function is also called by the signing\n * script on hand-authored input, and a thrown error inside the nightly\n * verification path would be a cron run that dies rather than falls back.\n */\nexport const canonicalise = (value: unknown): string | null => {\n  try {\n    const serialised = JSON.stringify(sortValue(value, new WeakSet()) as Json);\n\n    return typeof serialised === 'string' ? serialised : null;\n  } catch {\n    return null;\n  }\n};\n\n/**\n * The canonical bytes. Split from `canonicalise` only so the verifier can hash\n * without a second UTF-8 conversion, and so tests can assert on the string.\n */\nexport const canonicalBytes = (value: unknown): Uint8Array | null => {\n  const serialised = canonicalise(value);\n\n  return serialised === null ? null : new TextEncoder().encode(serialised);\n};\n", "/**\n * The public keys this build will accept a calibration signature from.\n *\n * ===========================================================================\n * ## These are public keys. Embedding them is correct.\n *\n * A verification key is meant to be published \u2014 it can check a signature and\n * cannot produce one. Shipping it inside a public repository leaks nothing, and\n * shipping it is the *only* way the guarantee works: a key fetched at runtime\n * could be swapped by whoever swapped the payload, which would make the whole\n * exercise a decoration. The trust anchor has to travel with the code that\n * relies on it, and the code is what the customer already chose to install.\n *\n * ## Why this is a Greenlight key and not the licensing service's org key\n *\n * The obvious anchor is the Numaya org's Ed25519 public key, published at\n * `GET /v1/orgs/current/public-key`\n * (`+VrgNZ38aH8OABL0Wt2fDlhHdc52Hy5H7ganNqf541I=`, `keyVersion: 1`). It is not\n * usable here, for two independent reasons, both measured on 2026-08-05:\n *\n *  1. **The service will not sign an arbitrary payload.** Its private half is\n *     internal and the only signing operation it exposes is the offline licence\n *     file (`GET /v1/licenses/{id}/file`), which signs licence fields \u2014\n *     `licenseId`, `status`, `features`, `expiry` \u2014 and nothing we author. There\n *     is no endpoint that takes bytes and returns a signature over them, so\n *     there is no way to obtain a calibration signature that verifies against\n *     that key. `ConfigDownloadResponse` has no `signature` field at all: the\n *     config surface is entirely unsigned.\n *  2. **Its signature is not independently verifiable anyway.** The licence\n *     file's signature was checked against that public key over roughly 150,000\n *     candidate canonicalisations \u2014 every plausible field order, delimiter,\n *     datetime precision and JSON shape \u2014 and none verified. The service does\n *     not publish the byte string it signs, so a third party cannot confirm the\n *     signature at all. A verifier that cannot verify is not an integrity\n *     control; claiming one would be worse than claiming none.\n *\n * So Greenlight signs its own calibration, with a key it holds, and anchors on\n * that. See `verify.ts` for exactly what that does and does not prove.\n *\n * ## Rotation\n *\n * Add the new key here, ship the release, *then* start signing with it. A build\n * that predates the rotation refuses the new payload (`unknown_key`) and falls\n * back to shipped defaults \u2014 degraded, never broken. Retiring a key is the\n * reverse: stop signing with it, wait out the deployed fleet, remove the entry.\n * Never remove and re-add the same `keyId` with different bytes; mint a new id.\n * ===========================================================================\n */\n\n/**\n * `keyId` \u2192 raw 32-byte Ed25519 public key, base64.\n *\n * Raw, not SPKI: `crypto.subtle.importKey('raw', \u2026)` takes the bare 32 bytes\n * for Ed25519, which keeps this table readable and removes a DER prefix that\n * would be one more thing to get wrong by hand.\n */\nexport const CALIBRATION_PUBLIC_KEYS: Readonly<Record<string, string>> = {\n  'greenlight-calibration-1': 'jPjyCrgLvAt3XsBQzUl5T7A/AszXPjL35WY93iPdDcI=',\n};\n\n/** The key new payloads are signed with. Read by the signing script only. */\nexport const CALIBRATION_SIGNING_KEY_ID = 'greenlight-calibration-1';\n\n/**\n * The one signature algorithm accepted.\n *\n * Compared case-sensitively against the envelope's `alg`, and there is no\n * \"choose the algorithm from the envelope\" branch anywhere \u2014 an attacker-chosen\n * algorithm field is the oldest hole in signed-token design and the cheapest\n * way to close it is to have exactly one accepted value.\n */\nexport const CALIBRATION_SIGNATURE_ALGORITHM = 'Ed25519';\n", "/**\n * The vocabulary of licence-delivered scoring calibration.\n *\n * ===========================================================================\n * ## What calibration is, and what it is emphatically not\n *\n * Greenlight ships as readable JavaScript, its `.ts` sources travel inside the\n * published tarball, and the repository is going public. A fork can therefore\n * delete the licence check in an afternoon. The answer to that is *not*\n * obfuscation \u2014 it is to make a licence deliver data that a fork cannot\n * reproduce, while the product keeps working perfectly without it.\n *\n * Calibration is that data: expanded decision-maker and influencer title\n * vocabularies, a shared-inbox and placeholder lexicon, and an industry synonym\n * taxonomy. All of it is accumulated grind, none of it is algorithm. A fork gets\n * the twenty-five English titles in `src/scoring/defaults.ts` and mis-scores\n * every German `Gesch\u00E4ftsf\u00FChrer` and every Brazilian `S\u00F3cio`.\n *\n * ## The rule this module must never break\n *\n * **Fail-open is the product's promise and calibration must not weaken it.**\n * Every type below is optional at the point of use, and every failure \u2014 absent,\n * stale, unlicensed, malformed, unsigned, wrongly signed \u2014 resolves to exactly\n * one behaviour: the in-source defaults, unchanged, with no error surfaced to\n * the lead. An unlicensed workspace scores a lead byte-for-byte identically to\n * how it scored that lead before this feature existed. `apply.ts` is the only\n * consumer and it is a pure function whose fall-back branch is the identity.\n *\n * Nothing server-side is load-bearing. If `licensing.rizvigoc.com` disappeared\n * permanently, Greenlight would keep scoring on shipped defaults forever.\n *\n * ## Where it arrives from \u2014 and why it is not the config endpoint\n *\n * The licensing service publishes `GET /v1/licenses/{id}/config`, and the\n * obvious design is to call it. It cannot be called. Both config endpoints\n * (`/v1/licenses/{id}/config` and `/v1/products/{id}/config`) **reject a licence\n * key as a credential** \u2014 measured on 2026-08-05 as a bare `401` for the licence\n * key in the `X-Numaya-License-Key` header, as an `Authorization: Bearer` value,\n * and as a `?key=` query parameter. The service's own Configurations guide says\n * so in as many words:\n *\n *   > Both config endpoints require an API key or JWT \u2014 the credential a\n *   > *license key* holder would present is turned away. [\u2026] if your product\n *   > ships to a customer's own infrastructure (not a SaaS you host yourself),\n *   > the product binary itself can never call these two endpoints directly.\n *\n * Greenlight runs inside the customer's Twenty instance and may never carry an\n * org-scoped management credential, so that door is shut. The guide's two\n * suggested workarounds are a self-hosted relay (forbidden: no Rizvi\n * infrastructure may be load-bearing) and baking the config into the release\n * (self-defeating: a public tarball hands the calibration to the fork).\n *\n * What *is* reachable with nothing but the customer's licence key is the\n * `validate` response, which Greenlight already calls nightly. It carries\n * `customerMetadata` \u2014 an org-authored JSON object, set when the licence is\n * issued \u2014 straight back to the key holder. That is the delivery channel, and it\n * costs no additional request. See `src/licensing/parse.ts`.\n *\n * ## What integrity can honestly be claimed\n *\n * See `verify.ts`. Short version: the envelope is signed by *us* with a key we\n * hold, not by the licensing service, because the service has no endpoint that\n * will sign an arbitrary payload. That is a real end-to-end authorship\n * guarantee and it is not the same guarantee as \"Numaya signed this\".\n * ===========================================================================\n */\n\n/* -------------------------------------------------------------------------- */\n/* The payload                                                                 */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The calibration pack itself \u2014 pure data, no behaviour, no thresholds.\n *\n * Note what is absent: weights, band thresholds, the gate threshold, and every\n * rule identifier. Those are either scoring logic or the workspace's own\n * configuration, and neither is ours to deliver. Calibration only ever supplies\n * *vocabulary* \u2014 the lists a rule matches against \u2014 so the worst a hostile or\n * corrupted payload could do is match more or fewer job titles. It cannot move a\n * threshold, disable a rule, or change what a score means.\n */\nexport interface Calibration {\n  /** Shape version. A payload from a future shape is refused, not guessed at. */\n  readonly schemaVersion: number;\n  /** Content version, shown to the admin as \"calibration v3\". Monotonic. */\n  readonly calibrationVersion: number;\n  readonly issuedAt: string;\n  readonly notes: string | null;\n  readonly decisionMakerTitles: readonly string[];\n  readonly influencerTitles: readonly string[];\n  readonly roleInboxLocalParts: readonly string[];\n  readonly placeholderValues: readonly string[];\n  /**\n   * Canonical industry term \u2192 the free-text variants that mean the same thing.\n   *\n   * Used to *expand* a workspace's own ICP target list, never to replace it: an\n   * admin who types \"Construction\" gets `bau`, `b\u00E2timent`, `edilizia` and\n   * `construcci\u00F3n` matched too. This is what turns the 40 idle ICP points into\n   * points that actually fire against real free-text industry columns.\n   */\n  readonly industrySynonyms: Readonly<Record<string, readonly string[]>>;\n}\n\n/** The only schema version this build understands. */\nexport const CALIBRATION_SCHEMA_VERSION = 1;\n\n/* -------------------------------------------------------------------------- */\n/* The signed envelope                                                         */\n/* -------------------------------------------------------------------------- */\n\n/**\n * What actually travels: the payload plus a detached signature over its\n * canonical form.\n *\n * `keyId` rather than a bare key so rotation is a data change and not a release.\n * A payload naming a key this build does not carry is refused \u2014 see\n * `verify.ts`, \"Why an unknown key is refused rather than trusted\".\n */\nexport interface SignedCalibrationEnvelope {\n  readonly alg: string;\n  readonly keyId: string;\n  readonly signature: string;\n  readonly payload: Calibration;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Verification outcome                                                        */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Why calibration is or is not in use, as a closed vocabulary.\n *\n * Closed rather than free text because these values reach an admin-facing panel\n * and an audit row, and because every single one of them resolves to the same\n * *behaviour* \u2014 shipped defaults \u2014 so the only thing distinguishing them is how\n * clearly they explain themselves to the person reading.\n */\nexport type CalibrationReason =\n  /** Verified and in use. */\n  | 'calibrated'\n  /** No licence key configured, or the licence is not entitled. */\n  | 'not_licensed'\n  /** Licensed, but this licence carries no calibration. */\n  | 'not_provisioned'\n  /** Present but not a readable envelope. */\n  | 'malformed'\n  /** Present, readable, but carries no signature at all. */\n  | 'unsigned'\n  /** Signed with a key id this build does not know. */\n  | 'unknown_key'\n  /** Signature did not verify against the embedded public key. */\n  | 'signature_invalid'\n  /** A payload shape from a future release. */\n  | 'schema_unsupported'\n  /** Verification could not run \u2014 no crypto primitive in this runtime. */\n  | 'verifier_unavailable'\n  /** Cached calibration older than the offline grace window. */\n  | 'stale'\n  /**\n   * A cached payload whose seal does not verify under the configured licence\n   * key: moved between workspaces, edited in place, or left behind by a key\n   * that has since been changed or removed.\n   */\n  | 'cache_unsealed';\n\nexport type CalibrationVerification =\n  | { readonly kind: 'verified'; readonly calibration: Calibration }\n  | { readonly kind: 'rejected'; readonly reason: CalibrationReason; readonly detail: string };\n\n/* -------------------------------------------------------------------------- */\n/* Cache + published state                                                     */\n/* -------------------------------------------------------------------------- */\n\n/**\n * What survives between runs, beside the entitlement cache and on the same\n * ladder.\n *\n * The verified payload is stored, not the envelope: re-verifying on every lead\n * would spend an Ed25519 check per scoring run to re-derive a fact the nightly\n * run already established, and the signature has no value once the bytes are\n * inside the workspace's own key-value storage \u2014 anything able to tamper with\n * that store is already inside the trust boundary the signature protects.\n * `keyId` and `verifiedAt` are kept so the admin panel can say what was checked\n * and when.\n */\nexport interface CachedCalibration {\n  readonly cachedAt: string;\n  readonly verifiedAt: string;\n  readonly keyId: string;\n  /**\n   * HMAC over this entry's canonical form, under a secret derived from the\n   * licence key. See `seal.ts` for exactly what it does and does not prove \u2014\n   * it is licence-binding and tamper-evidence, not authenticity of origin.\n   *\n   * An entry with no tag is not accepted. There is no \"unsealed\" mode.\n   */\n  readonly sealTag: string;\n  readonly calibration: Calibration;\n}\n\n/**\n * The bytes the seal is computed over: everything in a cache entry except the\n * tag itself.\n *\n * Declared as its own type so the sealing side and the verifying side cannot\n * drift \u2014 a field added to `CachedCalibration` and not to this shape would be\n * a field outside the seal, which is the quiet way a MAC stops covering what\n * everyone assumes it covers.\n */\nexport type SealedCalibrationBody = Omit<CachedCalibration, 'sealTag'>;\n\nexport type CalibrationSource = 'live' | 'cache' | 'none';\n\n/**\n * The admin-facing answer to \"am I running shipped defaults or calibration\n * vNN, and when did it last refresh?\".\n *\n * Published to key-value storage on every nightly run and read by the licence\n * panel. Deliberately a separate record from `LicenceState`: a workspace can be\n * perfectly licensed and still be on shipped defaults (its licence predates\n * calibration provisioning), and collapsing the two would make that state\n * unreadable.\n */\nexport interface CalibrationState {\n  readonly status: 'calibrated' | 'shipped_defaults';\n  readonly reason: CalibrationReason;\n  readonly source: CalibrationSource;\n  readonly calibrationVersion: number | null;\n  readonly issuedAt: string | null;\n  /** When the payload was last fetched and verified. Null on shipped defaults. */\n  readonly refreshedAt: string | null;\n  readonly checkedAt: string;\n  readonly cacheAgeMs: number | null;\n  readonly keyId: string | null;\n  /** Sizes of each list, so the panel can show what arrived without dumping it. */\n  readonly counts: CalibrationCounts | null;\n}\n\nexport interface CalibrationCounts {\n  readonly decisionMakerTitles: number;\n  readonly influencerTitles: number;\n  readonly roleInboxLocalParts: number;\n  readonly placeholderValues: number;\n  readonly industrySynonyms: number;\n}\n\n/**\n * Read-only access to the cached payload, for the scoring path.\n *\n * A reader, never a writer: the scoring run must not be handed something it\n * could use to change what future runs score against, for the same reason\n * `readPublishedLicenceState` is not on `LicenceStorePort`.\n */\nexport interface CalibrationReaderPort {\n  read(): Promise<CachedCalibration | null>;\n}\n\n/**\n * The writer's side, used only by the nightly licence run.\n *\n * Separate from `LicenceStorePort` rather than bolted onto it, because the two\n * have different failure consequences and different lifetimes: a licence cache\n * that fails to write costs an entitlement re-check, whereas a calibration cache\n * that fails to write costs a vocabulary that the very next run replaces. Both\n * swallow their errors, and keeping them apart means neither can be made to fail\n * the other.\n */\nexport interface CalibrationStorePort {\n  readCalibration(): Promise<CachedCalibration | null>;\n  writeCalibration(entry: CachedCalibration): Promise<void>;\n  /**\n   * Drop the cached payload entirely.\n   *\n   * Called on exactly one transition \u2014 a nightly run finding the licence no\n   * longer entitled \u2014 and it is what lets the scoring path skip an entitlement\n   * check: the cache's *existence* carries that fact. See\n   * `src/calibration/state.ts`, \"Why there is no entitlement check here\".\n   */\n  clearCalibration(): Promise<void>;\n  publishCalibrationState(state: CalibrationState): Promise<void>;\n}\n", "/**\n * Reading a calibration envelope out of whatever the licensing service sent.\n *\n * The input is the `customerMetadata` object from a `validate` response \u2014 a\n * free-form JSON document, authored by us but stored, re-serialised and\n * returned by a service we do not deploy in lockstep with this app. It is\n * untrusted in the ordinary sense (it crossed a network) and unstable in the\n * boring sense (a future release may put other things beside ours in there).\n *\n * So parsing is **total**: it never throws, it never returns `undefined`, and\n * every malformed shape becomes a named rejection rather than a `TypeError`\n * inside a customer's nightly cron job.\n *\n * ## Why the envelope lives under one namespaced key\n *\n * `customerMetadata.greenlightCalibration`, not the root. The field is the\n * licence's general-purpose metadata bag and there is every chance it will one\n * day also carry a customer reference, a support tier, or a flag for some\n * feature that does not exist yet. Owning one key means none of that collides\n * with us and none of it changes the bytes we verify \u2014 the signature covers the\n * envelope, not its surroundings, so a sibling key appearing next to ours must\n * not invalidate a payload. Namespacing is what makes that true.\n *\n * ## Every list is normalised here, once\n *\n * Entries are lower-cased, trimmed and de-duplicated at the boundary rather\n * than at the point of use. `src/scoring/rules/helpers.ts` normalises again on\n * every comparison and would cope regardless \u2014 but doing it here means the\n * counts shown to an admin are the counts that actually take effect, and a\n * payload with `\"CEO\"` and `\"ceo\"` in it does not read as 200 titles when it\n * carries 199.\n */\n\nimport {\n  CALIBRATION_SCHEMA_VERSION,\n  type Calibration,\n  type CalibrationVerification,\n  type SignedCalibrationEnvelope,\n} from 'src/calibration/types';\n\n/** The key our envelope occupies inside the licence's metadata bag. */\nexport const CALIBRATION_METADATA_KEY = 'greenlightCalibration';\n\nexport type EnvelopeRead =\n  | { readonly kind: 'envelope'; readonly envelope: SignedCalibrationEnvelope }\n  | {\n      readonly kind: 'rejected';\n      readonly reason: Extract<\n        CalibrationVerification,\n        { kind: 'rejected' }\n      >['reason'];\n      readonly detail: string;\n    };\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n  typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst reject = (\n  reason: Extract<CalibrationVerification, { kind: 'rejected' }>['reason'],\n  detail: string,\n): EnvelopeRead => ({ kind: 'rejected', reason, detail });\n\n/**\n * A list of lowercase, trimmed, de-duplicated non-empty strings.\n *\n * Non-strings are dropped rather than rejected: one bad entry in a list of two\n * hundred should cost that entry, not the whole calibration. A list that is not\n * an array at all is a different matter and is handled by the caller.\n */\nconst readStringList = (value: unknown): readonly string[] | null => {\n  if (!Array.isArray(value)) {\n    return null;\n  }\n\n  const seen = new Set<string>();\n\n  for (const entry of value) {\n    if (typeof entry !== 'string') {\n      continue;\n    }\n\n    const normalised = entry.trim().toLowerCase().replace(/\\s+/g, ' ');\n\n    if (normalised.length > 0) {\n      seen.add(normalised);\n    }\n  }\n\n  return [...seen];\n};\n\n/**\n * The synonym taxonomy: canonical term \u2192 variants.\n *\n * A canonical whose variant list is unusable is dropped whole rather than kept\n * empty, because an empty variant list would expand a workspace's ICP by\n * nothing while still appearing in the admin panel's count as if it had done\n * something.\n */\nconst readSynonyms = (\n  value: unknown,\n): Readonly<Record<string, readonly string[]>> | null => {\n  if (!isRecord(value)) {\n    return null;\n  }\n\n  const table: Record<string, readonly string[]> = {};\n\n  for (const [rawCanonical, rawVariants] of Object.entries(value)) {\n    const canonical = rawCanonical.trim().toLowerCase().replace(/\\s+/g, ' ');\n\n    if (canonical.length === 0) {\n      continue;\n    }\n\n    const variants = readStringList(rawVariants);\n\n    if (variants === null) {\n      continue;\n    }\n\n    // The canonical is never one of its own variants \u2014 it is already matched\n    // directly \u2014 and letting it in would double-count in the admin panel.\n    //\n    // The self-reference is stripped *before* the emptiness check, not after.\n    // Stripping afterwards left `{ construction: ['construction'] }` surviving\n    // as an empty cluster: harmless for scoring, but it inflated the synonym\n    // count the admin panel shows, which is the one thing this filter exists to\n    // stop.\n    const distinct = variants.filter((variant) => variant !== canonical);\n\n    if (distinct.length === 0) {\n      continue;\n    }\n\n    table[canonical] = distinct;\n  }\n\n  return table;\n};\n\nconst readInteger = (value: unknown): number | null =>\n  typeof value === 'number' && Number.isInteger(value) && value >= 0\n    ? value\n    : null;\n\nconst readPayload = (raw: unknown): Calibration | { readonly error: string } => {\n  if (!isRecord(raw)) {\n    return { error: 'payload is not a JSON object' };\n  }\n\n  const schemaVersion = readInteger(raw['schemaVersion']);\n\n  if (schemaVersion === null) {\n    return { error: 'payload has no non-negative integer schemaVersion' };\n  }\n\n  const calibrationVersion = readInteger(raw['calibrationVersion']);\n\n  if (calibrationVersion === null) {\n    return { error: 'payload has no non-negative integer calibrationVersion' };\n  }\n\n  const decisionMakerTitles = readStringList(raw['decisionMakerTitles']);\n  const influencerTitles = readStringList(raw['influencerTitles']);\n  const roleInboxLocalParts = readStringList(raw['roleInboxLocalParts']);\n  const placeholderValues = readStringList(raw['placeholderValues']);\n  const industrySynonyms = readSynonyms(raw['industrySynonyms']);\n\n  // Absent is fine and means \"this pack has no opinion about that list\" \u2014 the\n  // shipped default survives for it. Present-but-not-an-array is not fine: it\n  // means the payload is a shape we do not understand, and guessing would be\n  // how a calibration silently stops applying half of itself.\n  if (\n    (raw['decisionMakerTitles'] !== undefined && decisionMakerTitles === null) ||\n    (raw['influencerTitles'] !== undefined && influencerTitles === null) ||\n    (raw['roleInboxLocalParts'] !== undefined && roleInboxLocalParts === null) ||\n    (raw['placeholderValues'] !== undefined && placeholderValues === null) ||\n    (raw['industrySynonyms'] !== undefined && industrySynonyms === null)\n  ) {\n    return { error: 'one or more calibration lists were not of the expected type' };\n  }\n\n  return {\n    schemaVersion,\n    calibrationVersion,\n    issuedAt:\n      typeof raw['issuedAt'] === 'string' && raw['issuedAt'].length > 0\n        ? raw['issuedAt']\n        : '',\n    notes: typeof raw['notes'] === 'string' ? raw['notes'] : null,\n    decisionMakerTitles: decisionMakerTitles ?? [],\n    influencerTitles: influencerTitles ?? [],\n    roleInboxLocalParts: roleInboxLocalParts ?? [],\n    placeholderValues: placeholderValues ?? [],\n    industrySynonyms: industrySynonyms ?? {},\n  };\n};\n\n/**\n * Pull the envelope out of a licence's `customerMetadata`.\n *\n * Returns `not_provisioned` \u2014 not an error \u2014 when the key is simply absent.\n * That is the normal state of every licence issued before calibration existed,\n * and of every rules-only licence, and it must read as \"nothing to do here\"\n * rather than as a fault in the admin panel.\n */\nexport const readCalibrationEnvelope = (metadata: unknown): EnvelopeRead => {\n  if (!isRecord(metadata)) {\n    return reject('not_provisioned', 'licence carries no metadata');\n  }\n\n  const raw = metadata[CALIBRATION_METADATA_KEY];\n\n  if (raw === undefined || raw === null) {\n    return reject(\n      'not_provisioned',\n      `licence metadata has no \"${CALIBRATION_METADATA_KEY}\" entry`,\n    );\n  }\n\n  if (!isRecord(raw)) {\n    return reject(\n      'malformed',\n      `\"${CALIBRATION_METADATA_KEY}\" is not a JSON object`,\n    );\n  }\n\n  const signature = raw['signature'];\n  const keyId = raw['keyId'];\n  const alg = raw['alg'];\n\n  if (typeof signature !== 'string' || signature.length === 0) {\n    return reject('unsigned', 'envelope carries no signature');\n  }\n\n  if (typeof keyId !== 'string' || keyId.length === 0) {\n    return reject('unsigned', 'envelope names no signing key');\n  }\n\n  if (typeof alg !== 'string' || alg.length === 0) {\n    return reject('unsigned', 'envelope names no signature algorithm');\n  }\n\n  const payload = readPayload(raw['payload']);\n\n  if ('error' in payload) {\n    return reject('malformed', payload.error);\n  }\n\n  // Checked after parsing rather than before, so a future-shaped payload\n  // reports *why* it was refused \u2014 \"schema 2, this build understands 1\" \u2014 with\n  // the version it actually carried, rather than reporting the same generic\n  // \"malformed\" a truncated blob would.\n  if (payload.schemaVersion !== CALIBRATION_SCHEMA_VERSION) {\n    return reject(\n      'schema_unsupported',\n      `payload schemaVersion ${payload.schemaVersion}; this build understands ${CALIBRATION_SCHEMA_VERSION}`,\n    );\n  }\n\n  return { kind: 'envelope', envelope: { alg, keyId, signature, payload } };\n};\n", "/**\n * Signature verification for a calibration envelope.\n *\n * ===========================================================================\n * ## What this proves, and what it does not \u2014 read this before quoting it\n *\n * **It proves:** the payload now in memory is byte-identical, under the\n * canonicalisation in `canonical.ts`, to a payload signed by the holder of the\n * private half of an embedded key. Nobody between the signing step and this\n * function \u2014 the licensing service, its database, a compromised TLS path, a\n * proxy, a workspace administrator with API access \u2014 can substitute or edit the\n * calibration without the signature failing. That is a genuine end-to-end\n * authorship guarantee and it is the property worth having, because the payload\n * spends its life at rest inside a third-party service.\n *\n * **It does not prove that Numaya's licensing service signed anything.** It\n * cannot: the service has no endpoint that signs an arbitrary payload, and its\n * config download surface (`ConfigDownloadResponse`) carries no signature field\n * whatsoever. The transport integrity of the fetch is ordinary TLS to\n * `licensing.rizvigoc.com` plus licence-key authentication, and nothing more.\n * See `keys.ts` for the measurements behind that statement.\n *\n * **It does not prove freshness.** A signature is not a timestamp. Someone who\n * can control what the `validate` response returns could replay an *older*\n * genuinely-signed payload, and this function would accept it. Two things bound\n * that: `calibrationVersion` and `issuedAt` are inside the signed payload and\n * are published to the admin panel, so a rollback is visible rather than silent;\n * and the worst outcome of a successful replay is an older vocabulary, which is\n * strictly bounded by \"the shipped defaults\" in badness. There is no version\n * ratchet, deliberately \u2014 a ratchet would let one bad payload permanently wedge\n * a workspace, and permanently wedged is a worse failure than slightly stale.\n *\n * **It does not prove the payload is sensible.** Signing is authorship, not\n * review. `parse.ts` still validates the shape and `apply.ts` still refuses to\n * let calibration touch anything but vocabulary.\n *\n * ## Every failure lands in the same place\n *\n * Unknown key, wrong algorithm, bad base64, absent signature, no crypto in the\n * runtime, verification throwing \u2014 all of them return `rejected`, and every\n * caller of `rejected` uses the in-source defaults. There is no branch in this\n * file that can make scoring fail, and none that can make it throw.\n *\n * ## Why WebCrypto and not `node:crypto`\n *\n * `crypto.subtle` is a global. Reaching for it costs no import, so nothing here\n * depends on how the logic-function bundle treats Node built-ins \u2014 and a bundler\n * that decided to inline or stub `node:crypto` would have turned signature\n * verification into a silent no-op rather than a build error. A global that is\n * either present or absent fails loudly in the one direction that matters.\n * ===========================================================================\n */\n\nimport { canonicalBytes } from 'src/calibration/canonical';\nimport {\n  CALIBRATION_PUBLIC_KEYS,\n  CALIBRATION_SIGNATURE_ALGORITHM,\n} from 'src/calibration/keys';\nimport {\n  type CalibrationReason,\n  type CalibrationVerification,\n  type SignedCalibrationEnvelope,\n} from 'src/calibration/types';\n\nconst reject = (\n  reason: CalibrationReason,\n  detail: string,\n): CalibrationVerification => ({ kind: 'rejected', reason, detail });\n\n/**\n * Base64 \u2192 bytes without `Buffer`.\n *\n * `atob` is a global in every runtime this ships to and, unlike `Buffer`, needs\n * no Node shim in a browser-ish bundle. Returns `null` on anything unparseable\n * instead of throwing, because \"the signature field held garbage\" is an\n * expected input here, not an exceptional one.\n */\nconst decodeBase64 = (value: string): Uint8Array | null => {\n  try {\n    const binary = atob(value);\n    const bytes = new Uint8Array(binary.length);\n\n    for (let index = 0; index < binary.length; index += 1) {\n      bytes[index] = binary.charCodeAt(index);\n    }\n\n    return bytes;\n  } catch {\n    return null;\n  }\n};\n\n/** Ed25519 signatures are always exactly 64 bytes; keys always exactly 32. */\nconst ED25519_SIGNATURE_BYTES = 64;\nconst ED25519_PUBLIC_KEY_BYTES = 32;\n\nconst subtle = (): SubtleCrypto | null => {\n  const candidate = (globalThis as { crypto?: { subtle?: SubtleCrypto } }).crypto\n    ?.subtle;\n\n  return candidate === undefined ? null : candidate;\n};\n\n/**\n * Verify an envelope whose *shape* has already been checked by `parse.ts`.\n *\n * Split from parsing on purpose: parsing is synchronous and total, verification\n * is asynchronous and needs a runtime capability. Keeping them apart means the\n * shape tests do not need a crypto stub and the crypto tests do not need a\n * fixture with nineteen fields.\n */\nexport const verifyCalibrationEnvelope = async (\n  envelope: SignedCalibrationEnvelope,\n): Promise<CalibrationVerification> => {\n  if (envelope.alg !== CALIBRATION_SIGNATURE_ALGORITHM) {\n    return reject(\n      'unsigned',\n      `unsupported signature algorithm \"${envelope.alg}\"`,\n    );\n  }\n\n  const publicKeyBase64 = CALIBRATION_PUBLIC_KEYS[envelope.keyId];\n\n  // ## Why an unknown key is refused rather than trusted\n  //\n  // The alternative \u2014 accept any key id and verify against whatever the payload\n  // supplies \u2014 verifies that the payload signed itself, which is not a\n  // property. The key table is the trust anchor; a key outside it is a stranger.\n  // A workspace on an older build therefore refuses a payload signed with a\n  // newly rotated key and keeps shipped defaults, which is the correct\n  // degradation and is why `keys.ts` says to ship the key before signing with\n  // it.\n  if (publicKeyBase64 === undefined) {\n    return reject(\n      'unknown_key',\n      `no embedded public key for keyId \"${envelope.keyId}\"`,\n    );\n  }\n\n  const signature = decodeBase64(envelope.signature);\n\n  if (signature === null || signature.length !== ED25519_SIGNATURE_BYTES) {\n    return reject(\n      'signature_invalid',\n      `signature is not ${ED25519_SIGNATURE_BYTES} bytes of base64`,\n    );\n  }\n\n  const publicKey = decodeBase64(publicKeyBase64);\n\n  if (publicKey === null || publicKey.length !== ED25519_PUBLIC_KEY_BYTES) {\n    // An embedded key that does not decode is a build defect, not an input\n    // problem. It still degrades rather than throws \u2014 a customer's nightly cron\n    // is not the place to discover a typo in a constant, and the fall-back is\n    // the same shipped defaults either way.\n    return reject(\n      'verifier_unavailable',\n      `embedded public key \"${envelope.keyId}\" is not ${ED25519_PUBLIC_KEY_BYTES} raw bytes`,\n    );\n  }\n\n  const message = canonicalBytes(envelope.payload);\n\n  if (message === null) {\n    return reject('malformed', 'payload could not be canonicalised');\n  }\n\n  const crypto = subtle();\n\n  if (crypto === null) {\n    return reject(\n      'verifier_unavailable',\n      'this runtime exposes no crypto.subtle, so the signature could not be checked',\n    );\n  }\n\n  try {\n    const key = await crypto.importKey(\n      'raw',\n      publicKey as unknown as BufferSource,\n      { name: CALIBRATION_SIGNATURE_ALGORITHM },\n      false,\n      ['verify'],\n    );\n\n    const verified = await crypto.verify(\n      { name: CALIBRATION_SIGNATURE_ALGORITHM },\n      key,\n      signature as unknown as BufferSource,\n      message as unknown as BufferSource,\n    );\n\n    return verified\n      ? { kind: 'verified', calibration: envelope.payload }\n      : reject(\n          'signature_invalid',\n          'the signature does not match the payload under this build\u2019s canonicalisation',\n        );\n  } catch (error) {\n    // A runtime that has `crypto.subtle` but not Ed25519 throws here rather\n    // than returning false. Same landing place: shipped defaults, and a reason\n    // string an admin can act on.\n    return reject(\n      'verifier_unavailable',\n      `Ed25519 verification is unavailable in this runtime: ${\n        error instanceof Error ? error.name : 'unknown error'\n      }`,\n    );\n  }\n};\n", "/**\n * Binding a cached calibration to the licence key that fetched it.\n *\n * ===========================================================================\n * ## What this is for, stated without decoration\n *\n * The calibration payload spends most of its life at rest in the workspace's own\n * key-value storage. The Ed25519 signature in `verify.ts` proves who *authored*\n * it, and is checked once, when it arrives. This adds a second, different\n * property to the copy that is kept: the cache is sealed with a secret derived\n * from the licence key, and a cache whose seal does not verify under the\n * currently-configured key is refused.\n *\n * **It does buy:** a calibration cache lifted out of a licensed workspace and\n * dropped into an unlicensed one is rejected. The unlicensed install has no key,\n * so it cannot produce a matching tag, and it falls back to shipped defaults.\n * The same applies to a workspace whose licence key has been changed or removed.\n * It is also tamper-evidence: an edited cache entry no longer verifies.\n *\n * **It does not buy:** protection against someone who *holds* a valid licence\n * key. They have the secret; they can seal whatever they like. Nothing that runs\n * entirely inside the customer's own infrastructure can prevent that, and a\n * comment claiming otherwise would be a comfortable story rather than a\n * security property. The signature is the control that survives a hostile\n * licence holder \u2014 they can forge a cache, but they cannot forge a payload that\n * verifies against the embedded public key, so they cannot manufacture a\n * *newer* calibration than the one they were sold.\n *\n * **It is licence-binding and tamper-evidence. It is not authenticity of\n * origin.** Those are different properties and this file provides the first two.\n *\n * ## Why a port rather than the key\n *\n * `src/licensing/types.ts` records a structural guarantee: no type in the\n * licensing core has a `key` field, so there is no variable in the core that\n * *could* leak one. Calibration keeps that guarantee by never receiving key\n * material either. The seal arrives as two functions, implemented at the edge in\n * `src/logic-functions/licence-cache-seal.ts`, which is the only place besides\n * the HTTP adapter that touches the raw key.\n *\n * The consequence worth noting: a `null` implementation \u2014 no licence key\n * configured \u2014 cannot seal and cannot verify, so an unlicensed workspace can\n * never present an acceptable cache. That is the correct direction of failure,\n * and it falls out of the port's shape rather than out of a check somebody has\n * to remember to write.\n * ===========================================================================\n */\n\n/**\n * Seal and verification over the canonical form of a cache entry.\n *\n * `seal` returns `null` when it cannot produce a tag \u2014 no key, or a runtime with\n * no HMAC. The caller then declines to cache at all rather than storing an\n * unsealed entry, because an entry that can be read back without a tag check is\n * exactly the entry this whole file exists to refuse.\n */\nexport interface CalibrationSealPort {\n  seal(canonical: string): Promise<string | null>;\n  verify(canonical: string, tag: string): Promise<boolean>;\n}\n\n/**\n * The seal for a workspace with no licence key: seals nothing, accepts nothing.\n *\n * Used as the default so that a caller which forgets to wire a real seal gets\n * \"no calibration\" rather than \"calibration with no integrity check\". Failing\n * closed on the *cache* is safe precisely because failing closed there means\n * falling back to the shipped defaults, which is a fully working product.\n */\nexport const NO_SEAL: CalibrationSealPort = {\n  seal: async () => null,\n  verify: async () => false,\n};\n", "/**\n * Resolving \"am I running shipped defaults or calibration vNN, and when did it\n * last refresh?\" \u2014 one pure function, `now` injected, no I/O.\n *\n * ===========================================================================\n * ## The same ladder as the entitlement, for the same reasons\n *\n * Calibration is cached beside the entitlement in workspace key-value storage\n * and walks the identical 24h / 72h freshness ladder from\n * `src/licensing/cache.ts`. Sharing the arithmetic is not laziness \u2014 the two\n * caches are refreshed by the *same nightly run*, so any second ladder would\n * differ from this one only by drifting out of step with it.\n *\n * Where the two differ is what the far end of the ladder means, and the\n * difference is the whole point:\n *\n *   | Age        | Entitlement                  | Calibration                 |\n *   |------------|------------------------------|-----------------------------|\n *   | `< 24h`    | used as-is                   | used as-is                  |\n *   | `24\u201372h`   | rules-only (\"grace\")         | **still used**              |\n *   | `> 72h`    | rules-only + loud warning    | shipped defaults            |\n *\n * The entitlement tightens at 24h because trusting a stale one risks serving a\n * paid feature to a revoked licence, and revocation has to work. Calibration\n * carries no such risk: it is a word list. Dropping it the moment Numaya has a\n * bad night would make a customer's scores move for a reason that has nothing to\n * do with their data, which is a worse outcome than a slightly old vocabulary.\n * So calibration survives the grace window and is only abandoned past 72h, at\n * which point \"we have not spoken to Numaya in three days\" is a real statement\n * about the install and the admin should be seeing shipped-default behaviour\n * they can reason about.\n *\n * Note the asymmetry is safe in both directions: past 72h calibration falls back\n * to the shipped defaults, which is a *working product*, not a degraded one.\n * There is no branch below that can stop a lead being scored.\n * ===========================================================================\n */\n\nimport { classifyCache } from 'src/licensing/cache';\nimport {\n  type CachedCalibration,\n  type Calibration,\n  type CalibrationCounts,\n  type CalibrationReason,\n  type CalibrationState,\n} from 'src/calibration/types';\n\nexport const countCalibration = (\n  calibration: Calibration,\n): CalibrationCounts => ({\n  decisionMakerTitles: calibration.decisionMakerTitles.length,\n  influencerTitles: calibration.influencerTitles.length,\n  roleInboxLocalParts: calibration.roleInboxLocalParts.length,\n  placeholderValues: calibration.placeholderValues.length,\n  industrySynonyms: Object.keys(calibration.industrySynonyms).length,\n});\n\n/**\n * The state to publish when no usable calibration exists.\n *\n * Every field that describes a payload is `null` rather than zero or empty\n * string: \"there is no calibration\" and \"there is a calibration with nothing in\n * it\" must not render the same in the admin panel, and a zero count is exactly\n * how the second one would look.\n */\nexport const shippedDefaultsState = (\n  reason: CalibrationReason,\n  now: Date,\n): CalibrationState => ({\n  status: 'shipped_defaults',\n  reason,\n  source: 'none',\n  calibrationVersion: null,\n  issuedAt: null,\n  refreshedAt: null,\n  checkedAt: now.toISOString(),\n  cacheAgeMs: null,\n  keyId: null,\n  counts: null,\n});\n\nexport const calibratedState = (\n  entry: CachedCalibration,\n  source: 'live' | 'cache',\n  cacheAgeMs: number | null,\n  now: Date,\n): CalibrationState => ({\n  status: 'calibrated',\n  reason: 'calibrated',\n  source,\n  calibrationVersion: entry.calibration.calibrationVersion,\n  issuedAt: entry.calibration.issuedAt.length > 0 ? entry.calibration.issuedAt : null,\n  refreshedAt: entry.verifiedAt,\n  checkedAt: now.toISOString(),\n  cacheAgeMs,\n  keyId: entry.keyId,\n  counts: countCalibration(entry.calibration),\n});\n\n/**\n * Age at which a cached calibration stops being used. Deliberately the\n * entitlement's *outer* limit, not its inner one \u2014 see the table above.\n */\nexport const CALIBRATION_MAX_AGE_MS = 72 * 60 * 60 * 1000;\n\nexport interface ResolveCalibrationInput {\n  /** The cached payload, or `null` when none has ever been stored. */\n  readonly cached: CachedCalibration | null;\n  readonly now: Date;\n}\n\nexport interface ResolvedCalibration {\n  readonly state: CalibrationState;\n  /** The payload the scoring path should use, or `null` for shipped defaults. */\n  readonly calibration: Calibration | null;\n}\n\n/**\n * What the scoring path should use right now, and what the admin should be told.\n *\n * ===========================================================================\n * ## Why there is no entitlement check here\n *\n * Because the *existence of the cache is* the entitlement check, and moving it\n * to the writer is what keeps a licence lookup out of the per-lead scoring path.\n *\n * `refreshCalibration` writes the cache only while the licence resolves to\n * `full`, and **deletes it** the moment a nightly run finds the entitlement\n * gone. So a present, fresh cache already means \"this workspace was entitled at\n * its last successful licence check\". Re-deriving that per lead would mean a\n * second key-value read on the hot path to re-establish a fact the nightly run\n * has already written down.\n *\n * The consequence is a revocation latency of at most 24 hours \u2014 one nightly\n * cycle \u2014 which is exactly the latency enrichment revocation already has and is\n * documented as having, for the same reason: the alternative is putting a\n * network call to Numaya in front of a customer's scoring path, which is the\n * coupling the fail-open rule exists to prevent.\n *\n * The reverse case costs the customer one night. A renewed licence re-fetches on\n * the next run, and until then the workspace scores on shipped defaults \u2014 the\n * full working product, exactly as an unlicensed install always has.\n * ===========================================================================\n */\nexport const resolveCalibration = (\n  input: ResolveCalibrationInput,\n): ResolvedCalibration => {\n  if (input.cached === null) {\n    return {\n      state: shippedDefaultsState('not_provisioned', input.now),\n      calibration: null,\n    };\n  }\n\n  const { ageMs } = classifyCache(input.cached.cachedAt, input.now);\n\n  // An unreadable `cachedAt` is treated as expired rather than as fresh. The\n  // opposite choice would make a corrupt timestamp mean \"trust this forever\",\n  // which is the one thing a freshness check must never be talked into.\n  if (ageMs === null || ageMs >= CALIBRATION_MAX_AGE_MS) {\n    return {\n      state: {\n        ...shippedDefaultsState('stale', input.now),\n        cacheAgeMs: ageMs,\n        calibrationVersion: input.cached.calibration.calibrationVersion,\n        keyId: input.cached.keyId,\n      },\n      calibration: null,\n    };\n  }\n\n  return {\n    state: calibratedState(\n      input.cached,\n      ageMs === 0 ? 'live' : 'cache',\n      ageMs,\n      input.now,\n    ),\n    calibration: input.cached.calibration,\n  };\n};\n", "/**\n * The calibration half of a licence run: read what the `validate` response\n * carried, verify it, cache it, publish what an admin should see.\n *\n * ===========================================================================\n * ## Why this rides on the existing nightly revalidation\n *\n * It costs nothing. `runLicenceRevalidation` already calls\n * `POST /v1/licenses/validate` once a night at 03:17 UTC, and the response\n * already carries `customerMetadata` \u2014 the calibration arrives in bytes we were\n * fetching anyway. There is no second endpoint, no second credential, no second\n * timeout to tune, and no new failure mode: a run that could not reach Numaya\n * could not have fetched calibration either, and both fall back together.\n *\n * That was not the original plan. The plan was `GET /v1/licenses/{id}/config`,\n * which is the endpoint built for exactly this and which Greenlight cannot call\n * \u2014 it refuses licence-key credentials by design. `src/calibration/types.ts`\n * has the measurements and the service's own documentation of that refusal.\n *\n * ## Ordering: entitlement first, always\n *\n * `refreshCalibration` runs *after* the licence state is resolved and published,\n * and takes the resolved entitlement as an input. Two reasons, and the second is\n * the load-bearing one:\n *\n *  1. An unentitled licence should not have its calibration refreshed, and\n *     asking the entitlement is how we know.\n *  2. **Nothing in this file may be able to delay or fail the entitlement.**\n *     Enrichment gating, the audit row and the admin notice all depend on the\n *     licence state being published; a signature verification that hung or threw\n *     ahead of it would turn a word list into a dependency of the paid feature.\n *     Running afterwards makes that structurally impossible rather than merely\n *     unlikely.\n *\n * ## Every path ends in a published state\n *\n * Including the paths that fail. An admin who cannot tell the difference between\n * \"calibration is off\" and \"calibration broke and nobody said\" has been given\n * nothing, so `not_provisioned`, `signature_invalid`, `unknown_key` and\n * `verifier_unavailable` are all published as distinct reasons \u2014 even though all\n * four behave identically, which is to say the product keeps working on shipped\n * defaults.\n * ===========================================================================\n */\n\nimport { canonicalise } from 'src/calibration/canonical';\nimport { readCalibrationEnvelope } from 'src/calibration/parse';\nimport { NO_SEAL, type CalibrationSealPort } from 'src/calibration/seal';\nimport {\n  calibratedState,\n  countCalibration,\n  resolveCalibration,\n  shippedDefaultsState,\n} from 'src/calibration/state';\nimport {\n  type CachedCalibration,\n  type CalibrationState,\n  type CalibrationStorePort,\n  type SealedCalibrationBody,\n} from 'src/calibration/types';\nimport { verifyCalibrationEnvelope } from 'src/calibration/verify';\n\n/**\n * The canonical bytes a cache entry's seal covers.\n *\n * Shared by the writer here and the reader in `scoring-run.ts` \u2014 one function,\n * so the two cannot disagree about what is inside the MAC. `canonicalise`\n * sorts keys recursively, which is what makes the tag survive the round trip\n * through the workspace's key-value store.\n */\nexport const sealedCalibrationBody = (\n  body: SealedCalibrationBody,\n): string | null =>\n  canonicalise({\n    cachedAt: body.cachedAt,\n    verifiedAt: body.verifiedAt,\n    keyId: body.keyId,\n    calibration: body.calibration,\n  });\n\nexport interface RefreshCalibrationInput {\n  readonly store: CalibrationStorePort;\n  /**\n   * Binds the cached copy to this workspace's licence key. Defaults to\n   * `NO_SEAL`, which cannot produce a tag \u2014 and an entry that cannot be sealed\n   * is not written at all, so the default is \"no cache\" rather than \"a cache\n   * nothing checks\".\n   */\n  readonly seal?: CalibrationSealPort;\n  /**\n   * `customerMetadata` from the live `validate` response, or `undefined` when\n   * the run had no live response at all \u2014 an outage, or no licence key.\n   */\n  readonly metadata: unknown;\n  /** Whether a live answer was received. `false` means \"do not touch the cache\". */\n  readonly live: boolean;\n  /** The resolved entitlement: `state.mode === 'full'`. */\n  readonly entitled: boolean;\n  readonly now: Date;\n}\n\nexport interface RefreshCalibrationOutcome {\n  readonly state: CalibrationState;\n  /** Non-null only when a fresh payload was verified and written this run. */\n  readonly written: CachedCalibration | null;\n  /** For the structured log line. Never contains payload content. */\n  readonly detail: string | null;\n}\n\n/**\n * Fetch-verify-cache, as one function with no exceptions and no clock read.\n *\n * ## Why an unentitled run clears the cache\n *\n * This is the one place the paid boundary is enforced, and it is enforced by\n * deletion rather than by a flag. The scoring path then needs no entitlement\n * check of its own: a cache that exists is a cache that was written while the\n * licence resolved to `full`. See `src/calibration/state.ts`, \"Why there is no\n * entitlement check here\", for why keeping a licence lookup out of the per-lead\n * path is worth a deletion.\n *\n * The cost is that a licence restored after a lapse waits one nightly cycle\n * before calibration returns. That is a day of shipped-default scoring \u2014 the\n * full working product \u2014 against the alternative of a workspace keeping a paid\n * vocabulary for three days after it stopped paying for it.\n *\n * ## Why a failed verification does *not* clear the cache\n *\n * A payload that arrives corrupted is evidence about *that response*, not about\n * the payload verified last night. Discarding a good cached calibration because\n * a proxy mangled one nightly response would let a single bad night undo a\n * working install. The bad payload is refused, the reason is published, and the\n * previously verified cache continues to age out on its own 72-hour clock \u2014 so\n * a genuinely revoked or permanently-broken calibration still expires, it just\n * does not vanish on the first hiccup.\n */\nexport const refreshCalibration = async (\n  input: RefreshCalibrationInput,\n): Promise<RefreshCalibrationOutcome> => {\n  if (!input.entitled) {\n    await clearQuietly(input.store);\n\n    const state = shippedDefaultsState('not_licensed', input.now);\n    await publish(input.store, state);\n\n    return { state, written: null, detail: null };\n  }\n\n  if (!input.live) {\n    // No live answer: nothing new to say about calibration, so republish what\n    // the cache currently amounts to. Publishing a *failure* here would report\n    // the outage twice \u2014 the licence state already says it \u2014 and would blank the\n    // version number the admin panel is showing.\n    //\n    // The state is derived through `resolveCalibration`, the same function the\n    // scoring path uses, rather than assembled here. That is the whole point:\n    // a cache past 72h is no longer applied to leads, and a panel that said\n    // \"calibration v1\" while the engine was quietly on shipped defaults would be\n    // worse than a panel that said nothing. One function, one answer.\n    const cached = await readQuietly(input.store);\n    const { state } = resolveCalibration({ cached, now: input.now });\n\n    await publish(input.store, state);\n\n    return { state, written: null, detail: null };\n  }\n\n  const read = readCalibrationEnvelope(input.metadata);\n\n  if (read.kind === 'rejected') {\n    const state = shippedDefaultsState(read.reason, input.now);\n    await publish(input.store, state);\n\n    return { state, written: null, detail: read.detail };\n  }\n\n  const verification = await verifyCalibrationEnvelope(read.envelope);\n\n  if (verification.kind === 'rejected') {\n    const state = shippedDefaultsState(verification.reason, input.now);\n    await publish(input.store, state);\n\n    return { state, written: null, detail: verification.detail };\n  }\n\n  const body: SealedCalibrationBody = {\n    cachedAt: input.now.toISOString(),\n    verifiedAt: input.now.toISOString(),\n    keyId: read.envelope.keyId,\n    calibration: verification.calibration,\n  };\n\n  const canonical = sealedCalibrationBody(body);\n  const sealTag =\n    canonical === null ? null : await (input.seal ?? NO_SEAL).seal(canonical);\n\n  // No tag, no cache. Writing an unsealed entry would mean a subsequent read\n  // either has to accept it \u2014 defeating the seal entirely \u2014 or reject it, which\n  // is what happens anyway. Not writing is the same outcome with one fewer\n  // shape in the store.\n  if (sealTag === null) {\n    const state = shippedDefaultsState('cache_unsealed', input.now);\n    await publish(input.store, state);\n\n    return {\n      state,\n      written: null,\n      detail: 'calibration verified but could not be sealed to this licence key',\n    };\n  }\n\n  const entry: CachedCalibration = { ...body, sealTag };\n\n  await writeQuietly(input.store, entry);\n\n  const state = calibratedState(entry, 'live', 0, input.now);\n  await publish(input.store, state);\n\n  return {\n    state,\n    written: entry,\n    detail: `calibration v${verification.calibration.calibrationVersion} verified against ${read.envelope.keyId}`,\n  };\n};\n\n/**\n * A count summary safe to put in a log line.\n *\n * The payload itself is never logged: it is a few hundred kilobytes of word\n * lists, and a nightly cron that dumps it into the platform's log stream would\n * be indistinguishable from a bug.\n */\nexport const describeCalibration = (\n  outcome: RefreshCalibrationOutcome,\n): Record<string, unknown> => ({\n  calibrationStatus: outcome.state.status,\n  calibrationReason: outcome.state.reason,\n  calibrationSource: outcome.state.source,\n  calibrationVersion: outcome.state.calibrationVersion,\n  calibrationKeyId: outcome.state.keyId,\n  calibrationRefreshedAt: outcome.state.refreshedAt,\n  ...(outcome.written === null\n    ? {}\n    : { calibrationCounts: countCalibration(outcome.written.calibration) }),\n  ...(outcome.detail === null ? {} : { calibrationDetail: outcome.detail }),\n});\n\n/* -------------------------------------------------------------------------- */\n/* Storage, swallowed                                                          */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Storage failures are absorbed here rather than at the adapter, so that a\n * *test double* that throws is handled identically to the real store, which\n * logs and returns. A calibration cache is a convenience; failing a licence run\n * over one would be trading the load-bearing thing for the optional one.\n */\nconst readQuietly = async (\n  store: CalibrationStorePort,\n): Promise<CachedCalibration | null> => {\n  try {\n    return await store.readCalibration();\n  } catch {\n    return null;\n  }\n};\n\nconst writeQuietly = async (\n  store: CalibrationStorePort,\n  entry: CachedCalibration,\n): Promise<void> => {\n  try {\n    await store.writeCalibration(entry);\n  } catch {\n    // Swallowed: the state published below still says what was verified, and\n    // the next run re-verifies from the same source.\n  }\n};\n\n/**\n * A clear that fails leaves a workspace calibrated for up to another 72 hours,\n * after which the age check in `resolveCalibration` retires it regardless. So\n * the failure is bounded by the freshness ladder rather than open-ended, and\n * failing the licence run over it would trade a bounded problem for an\n * unbounded one.\n */\nconst clearQuietly = async (store: CalibrationStorePort): Promise<void> => {\n  try {\n    await store.clearCalibration();\n  } catch {\n    // Swallowed. See above.\n  }\n};\n\nconst publish = async (\n  store: CalibrationStorePort,\n  state: CalibrationState,\n): Promise<void> => {\n  try {\n    await store.publishCalibrationState(state);\n  } catch {\n    // Swallowed for the same reason. The admin panel shows a stale state for a\n    // night; nothing about scoring changes.\n  }\n};\n", "/**\n * The enrichable field catalogue: what may be asked for, in what shape.\n *\n * One table, read by three consumers that must not disagree \u2014 the prompt (what\n * the model is asked), the extractor (what is accepted back) and the gap\n * analysis (what is worth asking about at all). Keeping them keyed off the same\n * array is what stops the classic drift where a field is prompted for and then\n * silently discarded, or accepted without ever being requested.\n *\n * Every entry answers a *firmographic* question. Nothing here is a contact\n * detail: see the note on `EnrichableFieldKey` for why that boundary is a hard\n * one rather than a starting point.\n */\n\nimport type {\n  EnrichableFieldKey,\n  EnrichableFieldSpec,\n} from 'src/enrichment/types';\n\nexport const ENRICHABLE_FIELD_SPECS: readonly EnrichableFieldSpec[] = [\n  {\n    key: 'industry',\n    label: 'Industry',\n    question: \"What industry does this person's employer operate in?\",\n    kind: 'text',\n    maxLength: 60,\n  },\n  {\n    key: 'region',\n    label: 'Region',\n    question: 'Which country or region is the employer headquartered in?',\n    kind: 'text',\n    maxLength: 60,\n  },\n  {\n    key: 'employeeCount',\n    label: 'Employee count',\n    question: 'Approximately how many people does the employer employ?',\n    kind: 'integer',\n    maxLength: 12,\n  },\n  {\n    key: 'jobTitle',\n    label: 'Job title',\n    question: 'What is this person\u2019s job title at that employer?',\n    kind: 'text',\n    maxLength: 120,\n  },\n  {\n    key: 'seniority',\n    label: 'Seniority',\n    question:\n      'What seniority level does that job title correspond to (for example: C-level, VP, Director, Manager, Individual contributor)?',\n    kind: 'text',\n    maxLength: 40,\n  },\n];\n\nconst SPEC_BY_KEY: ReadonlyMap<string, EnrichableFieldSpec> = new Map(\n  ENRICHABLE_FIELD_SPECS.map((spec) => [spec.key, spec]),\n);\n\nexport const findFieldSpec = (key: string): EnrichableFieldSpec | null =>\n  SPEC_BY_KEY.get(key) ?? null;\n\nexport const isEnrichableFieldKey = (key: unknown): key is EnrichableFieldKey =>\n  typeof key === 'string' && SPEC_BY_KEY.has(key);\n\n/**\n * The dotted paths a workspace adds to its Layer 3 field mapping to let the\n * deterministic scorer read enriched values.\n *\n * Published from here rather than hard-coded in the config seed because the\n * shape of the provenance blob is this module's business, and a path written out\n * by hand somewhere else is a path that goes stale the first time the blob's\n * version changes.\n *\n * The ordering is the important part: the enriched path goes **after** the\n * workspace's own candidates in every mapping, never before. `field-access.ts`\n * tries candidates in order and takes the first that yields a value, so a human\n * value always wins and enrichment only ever fills a hole. That is the same\n * promise the storage shape makes \u2014 enrichment never overwrites a person \u2014 held\n * at the read side as well as the write side.\n */\nexport const enrichedFieldMappingPath = (key: EnrichableFieldKey): string =>\n  `greenlightEnrichment.fields.${key}.parsedValue`;\n\nexport const ENRICHED_FIELD_MAPPING_PATHS: Readonly<\n  Record<EnrichableFieldKey, string>\n> = Object.freeze(\n  Object.fromEntries(\n    ENRICHABLE_FIELD_SPECS.map((spec) => [\n      spec.key,\n      enrichedFieldMappingPath(spec.key),\n    ]),\n  ) as Record<EnrichableFieldKey, string>,\n);\n", "/**\n * The real calibration cache seal: HMAC-SHA256 under a secret derived from the\n * licence key.\n *\n * This is one of only two modules in Greenlight that touch the raw licence key \u2014\n * the other is the HTTP adapter, which puts it in a request body. Everything\n * else works with a mask or with the ports declared in `src/licensing/types.ts`.\n * That is a structural guarantee rather than a discipline, and this file is\n * written to keep it: the key enters through the factory argument, is\n * immediately turned into a derived secret, and is never stored, returned,\n * logged or included in any error.\n *\n * ---------------------------------------------------------------------------\n * ## Why the key is not used as the HMAC secret directly\n *\n * It would work, and it would be a needless second use of the same secret for a\n * second purpose. The derivation is one HMAC with a fixed, versioned label, so\n * the cache secret and the credential are cryptographically separated: an\n * adversary who somehow recovered the cache secret has not recovered the licence\n * key, and could not present it to the licensing service.\n *\n * The label carries a version. Changing it invalidates every existing seal at\n * once, which is the intended lever if the sealed form ever has to change: every\n * workspace falls back to shipped defaults for one night and re-fetches. That is\n * a safe migration precisely because falling back is not a failure.\n *\n * ## Why WebCrypto\n *\n * Same reason as `src/calibration/verify.ts`: `crypto.subtle` is a global, so\n * nothing here depends on how the logic-function bundler treats Node built-ins.\n * A bundler that stubbed `node:crypto` would have silently turned the seal into\n * a no-op; a missing global cannot, because the absence is checked and produces\n * \"no seal\", which refuses every cache.\n * ---------------------------------------------------------------------------\n */\n\nimport { type CalibrationSealPort, NO_SEAL } from 'src/calibration/seal';\n\n/**\n * Domain-separation label. Versioned so the sealed form can be changed by\n * changing this string, with a one-night fall-back to shipped defaults as the\n * entire migration cost.\n */\nconst SEAL_LABEL = 'greenlight.calibration.cache.v1';\n\nconst subtle = (): SubtleCrypto | null => {\n  const candidate = (globalThis as { crypto?: { subtle?: SubtleCrypto } }).crypto\n    ?.subtle;\n\n  return candidate === undefined ? null : candidate;\n};\n\nconst toBase64 = (bytes: Uint8Array): string => {\n  let binary = '';\n\n  for (const byte of bytes) {\n    binary += String.fromCharCode(byte);\n  }\n\n  return btoa(binary);\n};\n\n/**\n * Constant-time comparison of two base64 tags.\n *\n * The timing channel here is not a realistic attack \u2014 the attacker would need\n * to be inside the customer's own infrastructure, at which point they can read\n * the cache directly \u2014 but an early-exit string compare in code that checks a\n * MAC is the kind of thing that gets copied somewhere it does matter.\n */\nconst equalTags = (left: string, right: string): boolean => {\n  if (left.length !== right.length) {\n    return false;\n  }\n\n  let difference = 0;\n\n  for (let index = 0; index < left.length; index += 1) {\n    difference |= left.charCodeAt(index) ^ right.charCodeAt(index);\n  }\n\n  return difference === 0;\n};\n\n/**\n * Build a seal for a given licence key.\n *\n * A `null` or blank key yields `NO_SEAL`, which seals nothing and accepts\n * nothing \u2014 so an unlicensed workspace cannot present a cache, and cannot\n * accidentally be given one.\n */\nexport const createLicenceKeySeal = (\n  licenceKey: string | null,\n): CalibrationSealPort => {\n  if (typeof licenceKey !== 'string' || licenceKey.trim().length === 0) {\n    return NO_SEAL;\n  }\n\n  const key = licenceKey.trim();\n\n  // Derived once per seal, held in this closure and nowhere else. The raw key\n  // is not captured beyond this line.\n  let cachedSecret: CryptoKey | null = null;\n\n  const secret = async (crypto: SubtleCrypto): Promise<CryptoKey> => {\n    if (cachedSecret !== null) {\n      return cachedSecret;\n    }\n\n    const encoder = new TextEncoder();\n\n    const keyMaterial = await crypto.importKey(\n      'raw',\n      encoder.encode(key) as unknown as BufferSource,\n      { name: 'HMAC', hash: 'SHA-256' },\n      false,\n      ['sign'],\n    );\n\n    const derived = await crypto.sign(\n      'HMAC',\n      keyMaterial,\n      encoder.encode(SEAL_LABEL) as unknown as BufferSource,\n    );\n\n    cachedSecret = await crypto.importKey(\n      'raw',\n      derived,\n      { name: 'HMAC', hash: 'SHA-256' },\n      false,\n      ['sign'],\n    );\n\n    return cachedSecret;\n  };\n\n  const tag = async (canonical: string): Promise<string | null> => {\n    const crypto = subtle();\n\n    if (crypto === null) {\n      return null;\n    }\n\n    try {\n      const signature = await crypto.sign(\n        'HMAC',\n        await secret(crypto),\n        new TextEncoder().encode(canonical) as unknown as BufferSource,\n      );\n\n      return toBase64(new Uint8Array(signature));\n    } catch {\n      // Deliberately no detail. Anything derived from a failure while handling\n      // key material is a string this codebase has promised never to produce.\n      return null;\n    }\n  };\n\n  return {\n    seal: tag,\n    verify: async (canonical, expected) => {\n      const computed = await tag(canonical);\n\n      return computed !== null && equalTags(computed, expected);\n    },\n  };\n};\n", "/**\n * The licensing composition root: reads the environment, drives the ports, hands\n * everything to the pure core, writes the result back out.\n *\n * Kept out of the `define*` file for the same reason `scoring-run.ts` is \u2014 so\n * the whole run can be exercised against fakes, with an injected `now` and no\n * network. There is no `new Date()` and no `new CoreApiClient()` below.\n *\n * ===========================================================================\n * ## Two entry points\n *\n * `runLicenceInstall`  \u2014 called once from the post-install hook. Activates, then\n *                        validates.\n * `runLicenceRevalidation` \u2014 called nightly by the cron function. Validates only.\n *\n * `activate` is **not** called nightly. It is idempotent on the fingerprint and\n * would be harmless, but it is a slot-claim, not a health check, and calling it\n * every night would turn one write per install into one write per workspace per\n * day against a service that gains nothing from them.\n *\n * ## What is sent to Numaya, and what is not\n *\n * The licence key, the workspace id (as `deviceFingerprint`) and the workspace\n * display name (as `deviceName`). That is the entire payload, and the data-egress\n * table in `ARCHITECTURE.md` says so. No lead, contact, company or score data\n * leaves the workspace on this path \u2014 there is no field in the request body that\n * could carry any.\n *\n * ## Why an unresolvable workspace is not fatal\n *\n * `deviceFingerprint` is optional on `validate`. If the workspace identity\n * cannot be read \u2014 the metadata API is unavailable, or a future SDK moves\n * `currentWorkspace` \u2014 the run still validates, just without a fingerprint, and\n * skips activation because activation *requires* one. That is a strictly better\n * outcome than failing the check: the customer keeps their entitlement, and the\n * only thing lost is the readable device name in Numaya's admin view.\n * ===========================================================================\n */\n\nimport {\n  DEFAULT_LICENCE_API_BASE_URL,\n  DEFAULT_LICENCE_ENVIRONMENT,\n  ENRICHMENT_FEATURE_NAME,\n} from 'src/constants/licence-identifiers';\nimport {\n  buildLicenceAuditDetail,\n  buildLicenceAuditRow,\n  describeLicenceForAdmin,\n  maskLicenceKey,\n  redactLicenceKey,\n  resolveLicenceState,\n  type LicenceNotice,\n  type LicenceEnvironmentName,\n  type LicenceState,\n  type LicenceStorePort,\n  type LicensingPort,\n  type LicenceValidateOutcome,\n} from 'src/licensing';\nimport {\n  describeCalibration,\n  refreshCalibration,\n  type CalibrationStorePort,\n  type RefreshCalibrationOutcome,\n} from 'src/calibration';\nimport {\n  describeError,\n  logGreenlight,\n  type GreenlightApiClient,\n} from 'src/logic-functions/greenlight-api';\nimport { createLicenceKeySeal } from 'src/logic-functions/licence-cache-seal';\n\n/* -------------------------------------------------------------------------- */\n/* Ports                                                                       */\n/* -------------------------------------------------------------------------- */\n\nexport interface WorkspaceIdentity {\n  readonly workspaceId: string;\n  readonly workspaceName: string | null;\n}\n\n/**\n * Resolving \"which workspace am I\" is I/O, so it is a port like everything else.\n * The real adapter is `licence-workspace-identity.ts`; `null` means \"could not\n * tell\", which every caller below treats as a degradation rather than a failure.\n */\nexport interface WorkspaceIdentityPort {\n  read(): Promise<WorkspaceIdentity | null>;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Environment                                                                 */\n/* -------------------------------------------------------------------------- */\n\nexport interface LicenceEnvironment {\n  /** `null` when unset or blank \u2014 the supported, unlicensed configuration. */\n  readonly licenceKey: string | null;\n  readonly baseUrl: string;\n  readonly environment: LicenceEnvironmentName;\n}\n\n/**\n * Application variables reach a logic function as environment variables.\n *\n * The `?? DEFAULT_LICENCE_API_BASE_URL` is belt-and-braces: the variable ships\n * with that value declared in `application-config.ts`, so it should always be\n * present. It would be absent on a workspace that installed a build predating\n * the variable and has not re-synced, and defaulting beats an empty base URL\n * only in that the failure is then identical to a normal outage.\n *\n * `LICENCE_ENVIRONMENT` is read the strict way round: **only** the exact string\n * `sandbox` selects sandbox, and everything else \u2014 unset, blank, misspelled,\n * `SANDBOX_`, a value from a future release \u2014 is production. Defaulting an\n * unrecognised value to sandbox would let a typo route a paying customer at the\n * test estate, where their licence does not exist and where this whole exercise\n * started. Casing and surrounding whitespace are forgiven because they are\n * transcription noise, not intent.\n */\nexport const readLicenceEnvironment = (\n  env: Record<string, string | undefined> = process.env,\n): LicenceEnvironment => {\n  const rawKey = env['LICENCE_KEY'];\n  const trimmed = typeof rawKey === 'string' ? rawKey.trim() : '';\n  const rawBase = env['LICENCE_API_BASE_URL'];\n  const rawEnvironment = env['LICENCE_ENVIRONMENT'];\n\n  return {\n    licenceKey: trimmed.length === 0 ? null : trimmed,\n    baseUrl:\n      typeof rawBase === 'string' && rawBase.trim().length > 0\n        ? rawBase.trim()\n        : DEFAULT_LICENCE_API_BASE_URL,\n    environment:\n      typeof rawEnvironment === 'string' &&\n      rawEnvironment.trim().toLowerCase() === 'sandbox'\n        ? 'sandbox'\n        : DEFAULT_LICENCE_ENVIRONMENT,\n  };\n};\n\n/* -------------------------------------------------------------------------- */\n/* Runs                                                                        */\n/* -------------------------------------------------------------------------- */\n\nexport interface LicenceRunDeps {\n  readonly licensing: LicensingPort;\n  readonly store: LicenceStorePort;\n  /**\n   * Where the verified calibration payload is cached and its state published.\n   *\n   * Optional, and its absence is a supported configuration rather than a\n   * mistake: a run without it resolves the entitlement exactly as it always\n   * did and simply does no calibration work. That is what makes this an\n   * additive change to a path whose failure is silent and nightly.\n   */\n  readonly calibration?: CalibrationStorePort | null;\n  readonly identity: WorkspaceIdentityPort;\n  /** Optional: without it the run still resolves state, it just writes no row. */\n  readonly client?: GreenlightApiClient | null;\n  readonly licenceKey: string | null;\n  /**\n   * Which environment the `licensing` port was pointed at. Passed separately\n   * rather than read back off the port because the port is an interface with no\n   * way to ask, and because the resolved state has to record what we *asked\n   * for* even when the call never left the building.\n   */\n  readonly environment: LicenceEnvironmentName;\n  readonly now: Date;\n}\n\nexport interface LicenceRunOutcome {\n  readonly state: LicenceState;\n  readonly notice: LicenceNotice;\n  /** What the activation attempt did, for the install path's return value. */\n  readonly activation: 'claimed' | 'limit_reached' | 'skipped' | 'failed' | 'not_attempted';\n  /** Null when no calibration store was wired. */\n  readonly calibration: RefreshCalibrationOutcome | null;\n}\n\n/**\n * Best-effort provenance row. Never fails the run \u2014 a licence check that\n * succeeded but could not be written to the audit log is still a licence check\n * that succeeded, and the resolved state has already been published to key-value\n * storage where the UI reads it.\n */\nconst writeLicenceAuditRow = async (\n  client: GreenlightApiClient | null | undefined,\n  state: LicenceState,\n  calibration: RefreshCalibrationOutcome | null,\n): Promise<boolean> => {\n  if (client === null || client === undefined) {\n    return false;\n  }\n\n  try {\n    await client.mutation({\n      createGreenlightAuditLog: {\n        __args: {\n          data: buildLicenceAuditRow(state, calibration?.state ?? null),\n        },\n        id: true,\n      },\n    });\n\n    return true;\n  } catch (error) {\n    logGreenlight('licence_audit_write_failed', { error: describeError(error) });\n\n    return false;\n  }\n};\n\n/**\n * Resolve, persist, publish, audit, log. Shared by both entry points so the\n * install path and the nightly path cannot drift.\n */\nconst finishRun = async (\n  deps: LicenceRunDeps,\n  outcome: LicenceValidateOutcome | null,\n  activation: LicenceRunOutcome['activation'],\n): Promise<LicenceRunOutcome> => {\n  const presence = {\n    hasKey: deps.licenceKey !== null,\n    maskedKey: maskLicenceKey(deps.licenceKey),\n  };\n\n  const [cached, activationState] = await Promise.all([\n    deps.store.readCache(),\n    deps.store.readActivation(),\n  ]);\n\n  const { state, cacheWrite } = resolveLicenceState({\n    presence,\n    outcome,\n    cached,\n    activation: activationState,\n    environment: deps.environment,\n    now: deps.now,\n  });\n\n  if (cacheWrite !== null) {\n    await deps.store.writeCache(cacheWrite);\n  }\n\n  await deps.store.publishState(state);\n\n  // Calibration runs strictly after the entitlement has been resolved and\n  // published, and never before. See `src/calibration/refresh.ts`, \"Ordering\":\n  // nothing about a word list may be able to delay, fail or precede the decision\n  // that gates the paid feature and writes the admin notice.\n  const calibration =\n    deps.calibration === null || deps.calibration === undefined\n      ? null\n      : await refreshCalibration({\n          store: deps.calibration,\n          // Built here because this is one of the two modules that legitimately\n          // holds the raw key. The seal itself never receives it \u2014 see\n          // `licence-cache-seal.ts`.\n          seal: createLicenceKeySeal(deps.licenceKey),\n          metadata:\n            outcome !== null && outcome.kind === 'validated'\n              ? outcome.response.customerMetadata\n              : undefined,\n          live: outcome !== null && outcome.kind === 'validated',\n          // The same entitlement that gates enrichment. Calibration is the other\n          // half of what a licence buys, so it is bought on the same terms.\n          entitled: state.mode === 'full',\n          now: deps.now,\n        });\n\n  // Contains the mask, never the key \u2014 `buildLicenceAuditDetail` reads only\n  // fields of `LicenceState`, and `LicenceState` has no field that could hold\n  // a key. `describeCalibration` reads only counts and version numbers, never\n  // payload content.\n  logGreenlight('licence_resolved', {\n    ...buildLicenceAuditDetail(state),\n    activationAttempt: activation,\n    ...(calibration === null ? {} : describeCalibration(calibration)),\n  });\n\n  await writeLicenceAuditRow(deps.client, state, calibration);\n\n  return {\n    state,\n    notice: describeLicenceForAdmin(state),\n    activation,\n    calibration,\n  };\n};\n\nconst validateOnce = async (\n  deps: LicenceRunDeps,\n  workspace: WorkspaceIdentity | null,\n): Promise<LicenceValidateOutcome | null> => {\n  if (deps.licenceKey === null) {\n    return null;\n  }\n\n  try {\n    return await deps.licensing.validate({\n      key: deps.licenceKey,\n      deviceFingerprint: workspace?.workspaceId ?? null,\n      feature: ENRICHMENT_FEATURE_NAME,\n    });\n  } catch (error) {\n    // `LicensingPort` is documented as never throwing and the shipped adapter\n    // honours it. This catch is here because \"documented\" is not \"enforced by\n    // the type system\": a future adapter, or a test double, can throw, and the\n    // consequence would be a cron run that dies before publishing state.\n    // Downgrading it to the outcome the ladder already handles costs three\n    // lines and removes the failure mode entirely.\n    return {\n      kind: 'transport_failure',\n      detail: redactLicenceKey(describeError(error), deps.licenceKey),\n    };\n  }\n};\n\nconst readWorkspace = async (\n  identity: WorkspaceIdentityPort,\n): Promise<WorkspaceIdentity | null> => {\n  try {\n    return await identity.read();\n  } catch (error) {\n    logGreenlight('licence_workspace_identity_failed', {\n      error: describeError(error),\n    });\n\n    return null;\n  }\n};\n\n/**\n * Install: claim the activation slot, then validate.\n *\n * ## Why it validates even after a `409`\n *\n * The integration note's sketch returns early on `409`. This does not, and the\n * difference matters: because `validate` does not consult activation slots for\n * `Trial` / `Subscription` licences (gotcha 2), a refused workspace still gets\n * `valid: true, hasFeature: true`. Returning early would hide that, and \u2014 worse\n * \u2014 the *nightly* run has no activation step at all, so it would quietly switch\n * enrichment on the following morning.\n *\n * Instead the `409` is persisted, and `resolveLicenceState` applies it as an\n * override on every subsequent resolution. Validating anyway is then free\n * information: the admin notice can say how many days are left on the licence\n * that another workspace is holding, which is exactly what they need to decide\n * whether to free a slot or buy a seat.\n *\n * ## Idempotency\n *\n * `activate` is idempotent on `deviceFingerprint` \u2014 a repeat call with the same\n * workspace id returns the existing activation and consumes no second slot.\n * Reinstalling is therefore safe, and a successful activate clears a previously\n * stored `limit_reached`, which is how an admin who freed a slot recovers.\n */\nexport const runLicenceInstall = async (\n  deps: LicenceRunDeps,\n): Promise<LicenceRunOutcome> => {\n  if (deps.licenceKey === null) {\n    logGreenlight('licence_install_skipped', { reason: 'no_licence_key' });\n\n    return finishRun(deps, null, 'not_attempted');\n  }\n\n  const workspace = await readWorkspace(deps.identity);\n  let activation: LicenceRunOutcome['activation'] = 'skipped';\n\n  if (workspace === null) {\n    logGreenlight('licence_activate_skipped', {\n      reason: 'workspace_identity_unavailable',\n    });\n  } else {\n    const result = await deps.licensing.activate({\n      key: deps.licenceKey,\n      deviceFingerprint: workspace.workspaceId,\n      deviceName: workspace.workspaceName,\n    });\n\n    if (result.kind === 'activated') {\n      activation = 'claimed';\n      await deps.store.writeActivation({\n        status: 'claimed',\n        at: deps.now.toISOString(),\n      });\n    } else if (result.kind === 'activation_limit_reached') {\n      activation = 'limit_reached';\n      await deps.store.writeActivation({\n        status: 'limit_reached',\n        at: deps.now.toISOString(),\n      });\n    } else {\n      // A transport failure tells us nothing about the slot, so the stored\n      // activation state is left exactly as it was. Overwriting a known\n      // `claimed` with `unknown` because the network blipped would be losing\n      // information, not recording it.\n      activation = 'failed';\n      logGreenlight('licence_activate_failed', { failureKind: result.kind });\n    }\n  }\n\n  const outcome = await validateOnce(deps, workspace);\n\n  return finishRun(deps, outcome, activation);\n};\n\n/**\n * Nightly: validate, refresh the cache, republish the state, write the row.\n *\n * Never throws. The cron handler has nowhere to report a thrown error to, and a\n * run that dies before `finishRun` is a run that leaves yesterday's state\n * published \u2014 which is fine for a night and wrong for a month.\n */\nexport const runLicenceRevalidation = async (\n  deps: LicenceRunDeps,\n): Promise<LicenceRunOutcome> => {\n  const workspace = deps.licenceKey === null ? null : await readWorkspace(deps.identity);\n  const outcome = await validateOnce(deps, workspace);\n\n  return finishRun(deps, outcome, 'not_attempted');\n};\n", "/**\n * Resolving \"which Twenty workspace is this\" \u2014 the value Numaya stores as the\n * licence's `deviceFingerprint`.\n *\n * ## Why the workspace id is the fingerprint\n *\n * Greenlight's unit of installation is a workspace, not a machine: the app runs\n * inside the customer's Twenty instance, there is no hardware to fingerprint,\n * and a per-seat identifier would meter the wrong thing (the product plan sells\n * a per-workspace base fee, not seats). The workspace UUID is stable across\n * restarts, upgrades and reinstalls, which is exactly what makes `activate`\n * idempotent. `maxActivations: 1` then means \"one workspace per licence\".\n *\n * `deviceName` is the workspace display name \u2014 purely so Numaya's admin view\n * reads \"Acme Corp\" rather than a bare UUID when a customer calls about a slot.\n *\n * ## Why this lives on the metadata API\n *\n * `currentWorkspace` is a metadata-API query, not a core-API one, so this is the\n * only file in the app that instantiates `MetadataApiClient`. It is isolated\n * behind `WorkspaceIdentityPort` for the usual reason \u2014 the client reads its URL\n * and token from the process environment in its constructor, which no unit test\n * has \u2014 and for one more: this is the least-verified call in the licensing path,\n * and isolating it means a wrong guess costs a `null` rather than an exception.\n *\n * Returning `null` is a supported outcome throughout. `licence-run.ts` then\n * validates without a fingerprint and skips activation; the customer keeps their\n * entitlement and only the readable device name is lost.\n */\n\nimport { MetadataApiClient } from 'twenty-client-sdk/metadata';\n\nimport {\n  describeError,\n  logGreenlight,\n} from 'src/logic-functions/greenlight-api';\nimport {\n  type WorkspaceIdentity,\n  type WorkspaceIdentityPort,\n} from 'src/logic-functions/licence-run';\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n  typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/**\n * Tolerant to the point of paranoia, on purpose: the shape of this response is\n * the one thing in the licensing path that was never exercised against a live\n * instance, and the failure mode of getting it wrong must be \"no fingerprint\",\n * never \"install hook throws\".\n */\nexport const readWorkspaceIdentityFrom = (payload: unknown): WorkspaceIdentity | null => {\n  if (!isRecord(payload)) {\n    return null;\n  }\n\n  const workspace = payload['currentWorkspace'];\n\n  if (!isRecord(workspace)) {\n    return null;\n  }\n\n  const id = workspace['id'];\n\n  if (typeof id !== 'string' || id.length === 0) {\n    return null;\n  }\n\n  const displayName = workspace['displayName'];\n\n  return {\n    workspaceId: id,\n    workspaceName:\n      typeof displayName === 'string' && displayName.length > 0 ? displayName : null,\n  };\n};\n\nexport const metadataWorkspaceIdentity: WorkspaceIdentityPort = {\n  read: async () => {\n    try {\n      const client = new MetadataApiClient();\n      const response = (await client.query({\n        currentWorkspace: { id: true, displayName: true },\n      })) as unknown;\n\n      const identity = readWorkspaceIdentityFrom(response);\n\n      if (identity === null) {\n        logGreenlight('licence_workspace_identity_unreadable', {\n          note: 'currentWorkspace query returned an unexpected shape; validating without a device fingerprint',\n        });\n      }\n\n      return identity;\n    } catch (error) {\n      logGreenlight('licence_workspace_identity_failed', {\n        error: describeError(error),\n      });\n\n      return null;\n    }\n  },\n};\n"],
  "mappings": "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",
  "names": ["s", "n", "e", "t", "t", "n", "r", "e", "t", "n", "isRecord", "cacheAgeMs", "b", "isRecord", "reject", "cacheAgeMs", "state", "subtle", "isRecord"]
}
