@happyvertical/smrt-human-resources 0.55.11 → 0.55.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["id","row"],"sources":["../src/__smrt-register__.ts","../src/dates.ts","../src/write.ts","../src/employment/models.ts","../src/qualifications/models.ts","../src/qualifications/internal.ts","../src/service-base.ts","../src/employment/queries.ts","../src/employment/service.ts","../src/qualifications/service.ts"],"sourcesContent":["// Generated by @happyvertical/smrt-core smrtPlugin at library build time.\n// The package manifest is inlined so self-registration cannot miss when a\n// downstream bundler relocates this module away from dist/manifest.json\n// (https://github.com/happyvertical/smrt/issues/1506,\n// https://github.com/happyvertical/smrt/issues/1507).\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\nObjectRegistry.registerPackageManifest(JSON.parse(\"{\\\"version\\\":\\\"1.0.0\\\",\\\"timestamp\\\":0,\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"packageVersion\\\":\\\"0.55.11\\\",\\\"objects\\\":{\\\"@happyvertical/smrt-human-resources:Employment\\\":{\\\"name\\\":\\\"employment\\\",\\\"className\\\":\\\"Employment\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:Employment\\\",\\\"collection\\\":\\\"employments\\\",\\\"description\\\":\\\"One person's employment with a tenant: the stable record applications and payroll key on. There is one per tenant and profile; a rehire adds an EmploymentTerm to the same row.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/employment/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"profileId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-profiles:Profile\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"userId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-users:User\\\",\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"employerProfileId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-profiles:Profile\\\",\\\"_meta\\\":{\\\"nullable\\\":true,\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"employeeNumber\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"workerType\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{}},\\\"position\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"status\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{},\\\"enum\\\":[\\\"active\\\",\\\"on-leave\\\",\\\"ended\\\"]}},\\\"methods\\\":{\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"delete\\\":{\\\"name\\\":\\\"delete\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<void>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"employments\\\",\\\"sensitive\\\":true,\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"employee_number\\\"],\\\"indexes\\\":[{\\\"name\\\":\\\"employments_tenant_profile_uq\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"profileId\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"employments_tenant_user_idx\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"userId\\\"]}],\\\"api\\\":{\\\"include\\\":[]},\\\"cli\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"}},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"Employment\\\",\\\"collectionExportName\\\":\\\"EmploymentCollection\\\",\\\"validationRules\\\":[],\\\"schema\\\":{\\\"tableName\\\":\\\"employments\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"employments\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"profile_id\\\\\\\" UUID,\\\\n \\\\\\\"user_id\\\\\\\" UUID,\\\\n \\\\\\\"employer_profile_id\\\\\\\" UUID,\\\\n \\\\\\\"employee_number\\\\\\\" TEXT,\\\\n \\\\\\\"worker_type\\\\\\\" TEXT,\\\\n \\\\\\\"position\\\\\\\" TEXT,\\\\n \\\\\\\"status\\\\\\\" TEXT\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"user_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"employer_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"employee_number\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"worker_type\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"position\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"status\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"employments_tenant_id_employee_number_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"employee_number\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"employments_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"employments_tenant_profile_uq\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"profile_id\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"employments_tenant_user_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"user_id\\\"]},{\\\"name\\\":\\\"employments_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"employments_profile_id_idx\\\",\\\"columns\\\":[\\\"profile_id\\\"]},{\\\"name\\\":\\\"employments_user_id_idx\\\",\\\"columns\\\":[\\\"user_id\\\"]},{\\\"name\\\":\\\"employments_employer_profile_id_idx\\\",\\\"columns\\\":[\\\"employer_profile_id\\\"]}],\\\"version\\\":\\\"ff7ef7a8\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:EmploymentTerm\\\":{\\\"name\\\":\\\"employmentterm\\\",\\\"className\\\":\\\"EmploymentTerm\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:EmploymentTerm\\\",\\\"collection\\\":\\\"employmentterms\\\",\\\"description\\\":\\\"One continuous period of employment. endedOn is the last day employed (inclusive); null means the term is open. \\\\\\\"Employed on a date\\\\\\\" is answered from terms, never from Employment.status.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/employment/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"employmentId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"Employment\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"startedOn\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{}},\\\"endedOn\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"endReason\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false}},\\\"methods\\\":{\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"delete\\\":{\\\"name\\\":\\\"delete\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<void>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"employment_terms\\\",\\\"sensitive\\\":true,\\\"indexes\\\":[{\\\"name\\\":\\\"employment_terms_employment_idx\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"employmentId\\\",\\\"startedOn\\\"]}],\\\"api\\\":{\\\"include\\\":[]},\\\"cli\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"},\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"EmploymentTerm\\\",\\\"collectionExportName\\\":\\\"EmploymentTermCollection\\\",\\\"validationRules\\\":[],\\\"schema\\\":{\\\"tableName\\\":\\\"employment_terms\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"employment_terms\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"employment_id\\\\\\\" UUID,\\\\n \\\\\\\"started_on\\\\\\\" TEXT,\\\\n \\\\\\\"ended_on\\\\\\\" TEXT,\\\\n \\\\\\\"end_reason\\\\\\\" TEXT\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"employment_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"employments\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"started_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"ended_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"end_reason\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"employment_terms_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"employment_terms_employment_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"employment_id\\\",\\\"started_on\\\"]},{\\\"name\\\":\\\"employment_terms_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"employment_terms_employment_id_idx\\\",\\\"columns\\\":[\\\"employment_id\\\"]}],\\\"version\\\":\\\"165d209b\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:EmploymentChange\\\":{\\\"name\\\":\\\"employmentchange\\\",\\\"className\\\":\\\"EmploymentChange\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:EmploymentChange\\\",\\\"collection\\\":\\\"employmentchanges\\\",\\\"description\\\":\\\"Append-only dated change to an employment, attributed to an actor.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/employment/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"employmentId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"Employment\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"kind\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{},\\\"enum\\\":[\\\"hired\\\",\\\"ended\\\",\\\"rehired\\\",\\\"position-changed\\\",\\\"worker-type-changed\\\",\\\"leave-started\\\",\\\"leave-ended\\\",\\\"login-linked\\\",\\\"login-unlinked\\\"]},\\\"effectiveOn\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{}},\\\"fromValue\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"toValue\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"note\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"actorProfileId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-profiles:Profile\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}}},\\\"methods\\\":{\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"delete\\\":{\\\"name\\\":\\\"delete\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<void>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"employment_changes\\\",\\\"sensitive\\\":true,\\\"indexes\\\":[{\\\"name\\\":\\\"employment_changes_employment_idx\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"employmentId\\\",\\\"effectiveOn\\\"]}],\\\"api\\\":{\\\"include\\\":[]},\\\"cli\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"},\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"EmploymentChange\\\",\\\"collectionExportName\\\":\\\"EmploymentChangeCollection\\\",\\\"validationRules\\\":[],\\\"schema\\\":{\\\"tableName\\\":\\\"employment_changes\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"employment_changes\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"employment_id\\\\\\\" UUID,\\\\n \\\\\\\"kind\\\\\\\" TEXT,\\\\n \\\\\\\"effective_on\\\\\\\" TEXT,\\\\n \\\\\\\"from_value\\\\\\\" TEXT,\\\\n \\\\\\\"to_value\\\\\\\" TEXT,\\\\n \\\\\\\"note\\\\\\\" TEXT,\\\\n \\\\\\\"actor_profile_id\\\\\\\" UUID\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"employment_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"employments\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"kind\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"effective_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"from_value\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"to_value\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"note\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"actor_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"employment_changes_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"employment_changes_employment_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"employment_id\\\",\\\"effective_on\\\"]},{\\\"name\\\":\\\"employment_changes_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"employment_changes_employment_id_idx\\\",\\\"columns\\\":[\\\"employment_id\\\"]},{\\\"name\\\":\\\"employment_changes_actor_profile_id_idx\\\",\\\"columns\\\":[\\\"actor_profile_id\\\"]}],\\\"version\\\":\\\"5d93422f\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:EmploymentCollection\\\":{\\\"name\\\":\\\"employmentcollection\\\",\\\"className\\\":\\\"EmploymentCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:EmploymentCollection\\\",\\\"collection\\\":\\\"employments\\\",\\\"description\\\":\\\"Scoped persistence collection; mutations must use EmploymentService.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/employment/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"api\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"cli\\\":false,\\\"tableName\\\":\\\"employments\\\"},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"Employment\\\",\\\"exportName\\\":\\\"EmploymentCollection\\\",\\\"collectionExportName\\\":\\\"EmploymentCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"employments\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"employments\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"profile_id\\\\\\\" UUID,\\\\n \\\\\\\"user_id\\\\\\\" UUID,\\\\n \\\\\\\"employer_profile_id\\\\\\\" UUID,\\\\n \\\\\\\"employee_number\\\\\\\" TEXT,\\\\n \\\\\\\"worker_type\\\\\\\" TEXT,\\\\n \\\\\\\"position\\\\\\\" TEXT,\\\\n \\\\\\\"status\\\\\\\" TEXT\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"user_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"employer_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"employee_number\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"worker_type\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"position\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"status\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"employments_tenant_id_employee_number_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"employee_number\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"employments_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"employments_tenant_profile_uq\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"profile_id\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"employments_tenant_user_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"user_id\\\"]},{\\\"name\\\":\\\"employments_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"employments_profile_id_idx\\\",\\\"columns\\\":[\\\"profile_id\\\"]},{\\\"name\\\":\\\"employments_user_id_idx\\\",\\\"columns\\\":[\\\"user_id\\\"]},{\\\"name\\\":\\\"employments_employer_profile_id_idx\\\",\\\"columns\\\":[\\\"employer_profile_id\\\"]}],\\\"version\\\":\\\"ff7ef7a8\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:EmploymentTermCollection\\\":{\\\"name\\\":\\\"employmenttermcollection\\\",\\\"className\\\":\\\"EmploymentTermCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:EmploymentTermCollection\\\",\\\"collection\\\":\\\"employmentterms\\\",\\\"description\\\":\\\"Scoped term collection; closed like its model.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/employment/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"api\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"cli\\\":false,\\\"tableName\\\":\\\"employment_terms\\\"},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"EmploymentTerm\\\",\\\"exportName\\\":\\\"EmploymentTermCollection\\\",\\\"collectionExportName\\\":\\\"EmploymentTermCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"employment_terms\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"employment_terms\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"employment_id\\\\\\\" UUID,\\\\n \\\\\\\"started_on\\\\\\\" TEXT,\\\\n \\\\\\\"ended_on\\\\\\\" TEXT,\\\\n \\\\\\\"end_reason\\\\\\\" TEXT\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"employment_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"employments\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"started_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"ended_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"end_reason\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"employment_terms_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"employment_terms_employment_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"employment_id\\\",\\\"started_on\\\"]},{\\\"name\\\":\\\"employment_terms_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"employment_terms_employment_id_idx\\\",\\\"columns\\\":[\\\"employment_id\\\"]}],\\\"version\\\":\\\"165d209b\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:EmploymentChangeCollection\\\":{\\\"name\\\":\\\"employmentchangecollection\\\",\\\"className\\\":\\\"EmploymentChangeCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:EmploymentChangeCollection\\\",\\\"collection\\\":\\\"employmentchanges\\\",\\\"description\\\":\\\"Scoped append-only change collection; closed like its model.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/employment/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"api\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"cli\\\":false,\\\"tableName\\\":\\\"employment_changes\\\"},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"EmploymentChange\\\",\\\"exportName\\\":\\\"EmploymentChangeCollection\\\",\\\"collectionExportName\\\":\\\"EmploymentChangeCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"employment_changes\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"employment_changes\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"employment_id\\\\\\\" UUID,\\\\n \\\\\\\"kind\\\\\\\" TEXT,\\\\n \\\\\\\"effective_on\\\\\\\" TEXT,\\\\n \\\\\\\"from_value\\\\\\\" TEXT,\\\\n \\\\\\\"to_value\\\\\\\" TEXT,\\\\n \\\\\\\"note\\\\\\\" TEXT,\\\\n \\\\\\\"actor_profile_id\\\\\\\" UUID\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"employment_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"employments\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"kind\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"effective_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"from_value\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"to_value\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"note\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"actor_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"employment_changes_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"employment_changes_employment_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"employment_id\\\",\\\"effective_on\\\"]},{\\\"name\\\":\\\"employment_changes_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"employment_changes_employment_id_idx\\\",\\\"columns\\\":[\\\"employment_id\\\"]},{\\\"name\\\":\\\"employment_changes_actor_profile_id_idx\\\",\\\"columns\\\":[\\\"actor_profile_id\\\"]}],\\\"version\\\":\\\"5d93422f\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:Qualification\\\":{\\\"name\\\":\\\"qualification\\\",\\\"className\\\":\\\"Qualification\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:Qualification\\\",\\\"collection\\\":\\\"qualifications\\\",\\\"description\\\":\\\"A qualification definition: a managed list per tenant. Other packages gate work by referencing its id (@happyvertical/smrt-human-resources:Qualification).\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/qualifications/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"key\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"name\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"kind\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{},\\\"enum\\\":[\\\"ticket\\\",\\\"certification\\\",\\\"authorization\\\",\\\"training\\\",\\\"restriction\\\"]},\\\"issuingBody\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"expires\\\":{\\\"type\\\":\\\"boolean\\\",\\\"required\\\":true,\\\"default\\\":false},\\\"validityMonths\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"scope\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{},\\\"enum\\\":[\\\"person\\\",\\\"employment\\\"]},\\\"isActive\\\":{\\\"type\\\":\\\"boolean\\\",\\\"required\\\":true,\\\"default\\\":true}},\\\"methods\\\":{\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"delete\\\":{\\\"name\\\":\\\"delete\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<void>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"qualifications\\\",\\\"sensitive\\\":true,\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"key\\\"],\\\"api\\\":{\\\"include\\\":[]},\\\"cli\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"}},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"Qualification\\\",\\\"collectionExportName\\\":\\\"QualificationCollection\\\",\\\"displayLabelField\\\":\\\"name\\\",\\\"validationRules\\\":[{\\\"field\\\":\\\"expires\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"boolean\\\"},{\\\"field\\\":\\\"isActive\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"boolean\\\"}],\\\"schema\\\":{\\\"tableName\\\":\\\"qualifications\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"qualifications\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"key\\\\\\\" TEXT,\\\\n \\\\\\\"name\\\\\\\" TEXT,\\\\n \\\\\\\"kind\\\\\\\" TEXT,\\\\n \\\\\\\"issuing_body\\\\\\\" TEXT,\\\\n \\\\\\\"expires\\\\\\\" BOOLEAN NOT NULL DEFAULT FALSE,\\\\n \\\\\\\"validity_months\\\\\\\" INTEGER,\\\\n \\\\\\\"scope\\\\\\\" TEXT,\\\\n \\\\\\\"is_active\\\\\\\" BOOLEAN NOT NULL DEFAULT TRUE\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"key\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"kind\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"issuing_body\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"expires\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":true,\\\"unique\\\":false,\\\"default\\\":false},\\\"validity_months\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"scope\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"is_active\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":true,\\\"unique\\\":false,\\\"default\\\":true}},\\\"indexes\\\":[{\\\"name\\\":\\\"qualifications_tenant_id_key_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"key\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"qualifications_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"qualifications_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]}],\\\"version\\\":\\\"c9fa3721\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:HeldQualification\\\":{\\\"name\\\":\\\"heldqualification\\\",\\\"className\\\":\\\"HeldQualification\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:HeldQualification\\\",\\\"collection\\\":\\\"heldqualifications\\\",\\\"description\\\":\\\"A person holds a qualification. employmentId is set when the definition's scope is employment; such a row is revoked when that employment ends.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/qualifications/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"qualificationId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"Qualification\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"profileId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-profiles:Profile\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"employmentId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"Employment\\\",\\\"_meta\\\":{\\\"nullable\\\":true,\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"issuedOn\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{}},\\\"expiresOn\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"certificateNumber\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"status\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{},\\\"enum\\\":[\\\"valid\\\",\\\"expired\\\",\\\"suspended\\\",\\\"revoked\\\"]},\\\"verifiedByProfileId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-profiles:Profile\\\",\\\"_meta\\\":{\\\"nullable\\\":true,\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"verifiedAt\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":false},\\\"documentAssetId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-assets:Asset\\\",\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"renewalOfId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"HeldQualification\\\",\\\"_meta\\\":{\\\"nullable\\\":true,\\\"onDelete\\\":\\\"RESTRICT\\\"}}},\\\"methods\\\":{\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"delete\\\":{\\\"name\\\":\\\"delete\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<void>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"held_qualifications\\\",\\\"sensitive\\\":true,\\\"indexes\\\":[{\\\"name\\\":\\\"held_qualifications_profile_idx\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"profileId\\\",\\\"qualificationId\\\"]},{\\\"name\\\":\\\"held_qualifications_expiry_idx\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"status\\\",\\\"expiresOn\\\"]}],\\\"api\\\":{\\\"include\\\":[]},\\\"cli\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"},\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"HeldQualification\\\",\\\"collectionExportName\\\":\\\"HeldQualificationCollection\\\",\\\"validationRules\\\":[],\\\"schema\\\":{\\\"tableName\\\":\\\"held_qualifications\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"held_qualifications\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"qualification_id\\\\\\\" UUID,\\\\n \\\\\\\"profile_id\\\\\\\" UUID,\\\\n \\\\\\\"employment_id\\\\\\\" UUID,\\\\n \\\\\\\"issued_on\\\\\\\" TEXT,\\\\n \\\\\\\"expires_on\\\\\\\" TEXT,\\\\n \\\\\\\"certificate_number\\\\\\\" TEXT,\\\\n \\\\\\\"status\\\\\\\" TEXT,\\\\n \\\\\\\"verified_by_profile_id\\\\\\\" UUID,\\\\n \\\\\\\"verified_at\\\\\\\" TIMESTAMP,\\\\n \\\\\\\"document_asset_id\\\\\\\" UUID,\\\\n \\\\\\\"renewal_of_id\\\\\\\" UUID\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"qualification_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"qualifications\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"employment_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"employments\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"issued_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"expires_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"certificate_number\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"status\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"verified_by_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"verified_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"document_asset_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"renewal_of_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"held_qualifications\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}}},\\\"indexes\\\":[{\\\"name\\\":\\\"held_qualifications_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"held_qualifications_profile_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"profile_id\\\",\\\"qualification_id\\\"]},{\\\"name\\\":\\\"held_qualifications_expiry_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"status\\\",\\\"expires_on\\\"]},{\\\"name\\\":\\\"held_qualifications_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"held_qualifications_qualification_id_idx\\\",\\\"columns\\\":[\\\"qualification_id\\\"]},{\\\"name\\\":\\\"held_qualifications_profile_id_idx\\\",\\\"columns\\\":[\\\"profile_id\\\"]},{\\\"name\\\":\\\"held_qualifications_employment_id_idx\\\",\\\"columns\\\":[\\\"employment_id\\\"]},{\\\"name\\\":\\\"held_qualifications_verified_by_profile_id_idx\\\",\\\"columns\\\":[\\\"verified_by_profile_id\\\"]},{\\\"name\\\":\\\"held_qualifications_document_asset_id_idx\\\",\\\"columns\\\":[\\\"document_asset_id\\\"]},{\\\"name\\\":\\\"held_qualifications_renewal_of_id_idx\\\",\\\"columns\\\":[\\\"renewal_of_id\\\"]}],\\\"version\\\":\\\"fafbe37f\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:HeldQualificationChange\\\":{\\\"name\\\":\\\"heldqualificationchange\\\",\\\"className\\\":\\\"HeldQualificationChange\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:HeldQualificationChange\\\",\\\"collection\\\":\\\"heldqualificationchanges\\\",\\\"description\\\":\\\"Append-only dated change to a held qualification, attributed to an actor.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/qualifications/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"heldQualificationId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"HeldQualification\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"kind\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{},\\\"enum\\\":[\\\"granted\\\",\\\"renewed\\\",\\\"suspended\\\",\\\"reinstated\\\",\\\"revoked\\\",\\\"expired\\\"]},\\\"effectiveOn\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{}},\\\"reason\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"actorProfileId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-profiles:Profile\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}}},\\\"methods\\\":{\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"delete\\\":{\\\"name\\\":\\\"delete\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<void>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"held_qualification_changes\\\",\\\"sensitive\\\":true,\\\"indexes\\\":[{\\\"name\\\":\\\"held_qualification_changes_held_idx\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"heldQualificationId\\\",\\\"effectiveOn\\\"]}],\\\"api\\\":{\\\"include\\\":[]},\\\"cli\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"},\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"HeldQualificationChange\\\",\\\"collectionExportName\\\":\\\"HeldQualificationChangeCollection\\\",\\\"validationRules\\\":[],\\\"schema\\\":{\\\"tableName\\\":\\\"held_qualification_changes\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"held_qualification_changes\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"held_qualification_id\\\\\\\" UUID,\\\\n \\\\\\\"kind\\\\\\\" TEXT,\\\\n \\\\\\\"effective_on\\\\\\\" TEXT,\\\\n \\\\\\\"reason\\\\\\\" TEXT,\\\\n \\\\\\\"actor_profile_id\\\\\\\" UUID\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"held_qualification_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"held_qualifications\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"kind\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"effective_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"reason\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"actor_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"held_qualification_changes_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"held_qualification_changes_held_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"held_qualification_id\\\",\\\"effective_on\\\"]},{\\\"name\\\":\\\"held_qualification_changes_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"held_qualification_changes_held_qualification_id_idx\\\",\\\"columns\\\":[\\\"held_qualification_id\\\"]},{\\\"name\\\":\\\"held_qualification_changes_actor_profile_id_idx\\\",\\\"columns\\\":[\\\"actor_profile_id\\\"]}],\\\"version\\\":\\\"ed9ed5e1\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:QualificationCollection\\\":{\\\"name\\\":\\\"qualificationcollection\\\",\\\"className\\\":\\\"QualificationCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:QualificationCollection\\\",\\\"collection\\\":\\\"qualifications\\\",\\\"description\\\":\\\"Scoped definition collection; mutations must use QualificationService.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/qualifications/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"api\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"cli\\\":false,\\\"tableName\\\":\\\"qualifications\\\"},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"Qualification\\\",\\\"exportName\\\":\\\"QualificationCollection\\\",\\\"collectionExportName\\\":\\\"QualificationCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"qualifications\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"qualifications\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"key\\\\\\\" TEXT,\\\\n \\\\\\\"name\\\\\\\" TEXT,\\\\n \\\\\\\"kind\\\\\\\" TEXT,\\\\n \\\\\\\"issuing_body\\\\\\\" TEXT,\\\\n \\\\\\\"expires\\\\\\\" BOOLEAN NOT NULL DEFAULT FALSE,\\\\n \\\\\\\"validity_months\\\\\\\" INTEGER,\\\\n \\\\\\\"scope\\\\\\\" TEXT,\\\\n \\\\\\\"is_active\\\\\\\" BOOLEAN NOT NULL DEFAULT TRUE\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"key\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"kind\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"issuing_body\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"expires\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":true,\\\"unique\\\":false,\\\"default\\\":false},\\\"validity_months\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"scope\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"is_active\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":true,\\\"unique\\\":false,\\\"default\\\":true}},\\\"indexes\\\":[{\\\"name\\\":\\\"qualifications_tenant_id_key_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"key\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"qualifications_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"qualifications_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]}],\\\"version\\\":\\\"c9fa3721\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:HeldQualificationCollection\\\":{\\\"name\\\":\\\"heldqualificationcollection\\\",\\\"className\\\":\\\"HeldQualificationCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:HeldQualificationCollection\\\",\\\"collection\\\":\\\"heldqualifications\\\",\\\"description\\\":\\\"Scoped held-qualification collection; closed like its model.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/qualifications/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"api\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"cli\\\":false,\\\"tableName\\\":\\\"held_qualifications\\\"},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"HeldQualification\\\",\\\"exportName\\\":\\\"HeldQualificationCollection\\\",\\\"collectionExportName\\\":\\\"HeldQualificationCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"held_qualifications\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"held_qualifications\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"qualification_id\\\\\\\" UUID,\\\\n \\\\\\\"profile_id\\\\\\\" UUID,\\\\n \\\\\\\"employment_id\\\\\\\" UUID,\\\\n \\\\\\\"issued_on\\\\\\\" TEXT,\\\\n \\\\\\\"expires_on\\\\\\\" TEXT,\\\\n \\\\\\\"certificate_number\\\\\\\" TEXT,\\\\n \\\\\\\"status\\\\\\\" TEXT,\\\\n \\\\\\\"verified_by_profile_id\\\\\\\" UUID,\\\\n \\\\\\\"verified_at\\\\\\\" TIMESTAMP,\\\\n \\\\\\\"document_asset_id\\\\\\\" UUID,\\\\n \\\\\\\"renewal_of_id\\\\\\\" UUID\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"qualification_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"qualifications\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"employment_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"employments\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"issued_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"expires_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"certificate_number\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"status\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"verified_by_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"verified_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"document_asset_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"renewal_of_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"held_qualifications\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}}},\\\"indexes\\\":[{\\\"name\\\":\\\"held_qualifications_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"held_qualifications_profile_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"profile_id\\\",\\\"qualification_id\\\"]},{\\\"name\\\":\\\"held_qualifications_expiry_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"status\\\",\\\"expires_on\\\"]},{\\\"name\\\":\\\"held_qualifications_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"held_qualifications_qualification_id_idx\\\",\\\"columns\\\":[\\\"qualification_id\\\"]},{\\\"name\\\":\\\"held_qualifications_profile_id_idx\\\",\\\"columns\\\":[\\\"profile_id\\\"]},{\\\"name\\\":\\\"held_qualifications_employment_id_idx\\\",\\\"columns\\\":[\\\"employment_id\\\"]},{\\\"name\\\":\\\"held_qualifications_verified_by_profile_id_idx\\\",\\\"columns\\\":[\\\"verified_by_profile_id\\\"]},{\\\"name\\\":\\\"held_qualifications_document_asset_id_idx\\\",\\\"columns\\\":[\\\"document_asset_id\\\"]},{\\\"name\\\":\\\"held_qualifications_renewal_of_id_idx\\\",\\\"columns\\\":[\\\"renewal_of_id\\\"]}],\\\"version\\\":\\\"fafbe37f\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:HeldQualificationChangeCollection\\\":{\\\"name\\\":\\\"heldqualificationchangecollection\\\",\\\"className\\\":\\\"HeldQualificationChangeCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:HeldQualificationChangeCollection\\\",\\\"collection\\\":\\\"heldqualificationchanges\\\",\\\"description\\\":\\\"Scoped append-only change collection; closed like its model.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/qualifications/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"api\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"cli\\\":false,\\\"tableName\\\":\\\"held_qualification_changes\\\"},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"HeldQualificationChange\\\",\\\"exportName\\\":\\\"HeldQualificationChangeCollection\\\",\\\"collectionExportName\\\":\\\"HeldQualificationChangeCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"held_qualification_changes\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"held_qualification_changes\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"held_qualification_id\\\\\\\" UUID,\\\\n \\\\\\\"kind\\\\\\\" TEXT,\\\\n \\\\\\\"effective_on\\\\\\\" TEXT,\\\\n \\\\\\\"reason\\\\\\\" TEXT,\\\\n \\\\\\\"actor_profile_id\\\\\\\" UUID\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"held_qualification_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"held_qualifications\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"kind\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"effective_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"reason\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"actor_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"held_qualification_changes_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"held_qualification_changes_held_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"held_qualification_id\\\",\\\"effective_on\\\"]},{\\\"name\\\":\\\"held_qualification_changes_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"held_qualification_changes_held_qualification_id_idx\\\",\\\"columns\\\":[\\\"held_qualification_id\\\"]},{\\\"name\\\":\\\"held_qualification_changes_actor_profile_id_idx\\\",\\\"columns\\\":[\\\"actor_profile_id\\\"]}],\\\"version\\\":\\\"ed9ed5e1\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"}},\\\"moduleType\\\":\\\"smrt\\\",\\\"smrtDependencies\\\":[\\\"@happyvertical/smrt-core\\\",\\\"@happyvertical/smrt-tenancy\\\"],\\\"browser\\\":{\\\"status\\\":\\\"server-only\\\",\\\"issues\\\":[\\\"#3624\\\"],\\\"via\\\":[\\\"@happyvertical/smrt-tenancy\\\"]},\\\"stampsConstructors\\\":true}\"));\n","/**\n * Calendar-date helpers. HR dates are `YYYY-MM-DD` text, so they compare\n * correctly as strings and carry no timezone; the application converts an\n * instant to its own local date before asking.\n *\n * @packageDocumentation\n */\n\nimport { HrError, type IsoDate } from './types.js';\n\nconst ISO_DATE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n\n/** Whether `value` is a real calendar date written `YYYY-MM-DD`. */\nexport function isIsoDate(value: unknown): value is IsoDate {\n if (typeof value !== 'string') return false;\n const match = ISO_DATE.exec(value);\n if (!match) return false;\n const [year, month, day] = match.slice(1).map(Number);\n const date = new Date(Date.UTC(year, month - 1, day));\n return (\n date.getUTCFullYear() === year &&\n date.getUTCMonth() === month - 1 &&\n date.getUTCDate() === day\n );\n}\n\n/** Throw unless `value` is a real calendar date written `YYYY-MM-DD`. */\nexport function assertIsoDate(fieldName: string, value: unknown): IsoDate {\n if (!isIsoDate(value))\n throw new HrError(\n 'HR_INVALID',\n `${fieldName} must be a calendar date written YYYY-MM-DD, got '${String(value)}'.`,\n );\n return value;\n}\n\n/** The calendar date `days` after `date` (negative moves back). */\nexport function addDays(date: IsoDate, days: number): IsoDate {\n const [year, month, day] = assertIsoDate('date', date).split('-').map(Number);\n return new Date(Date.UTC(year, month - 1, day + days))\n .toISOString()\n .slice(0, 10);\n}\n\n/**\n * The calendar date `months` after `date`, clamped to the end of the target\n * month (31 January + 1 month is the last day of February).\n */\nexport function addMonths(date: IsoDate, months: number): IsoDate {\n const [year, month, day] = assertIsoDate('date', date).split('-').map(Number);\n const first = new Date(Date.UTC(year, month - 1 + months, 1));\n const lastDay = new Date(\n Date.UTC(first.getUTCFullYear(), first.getUTCMonth() + 1, 0),\n ).getUTCDate();\n first.setUTCDate(Math.min(day, lastDay));\n return first.toISOString().slice(0, 10);\n}\n\n/**\n * Whether `on` falls inside the inclusive range `[start, end]`. A null `end`\n * is open-ended.\n */\nexport function dateWithin(\n on: IsoDate,\n start: IsoDate,\n end: IsoDate | null,\n): boolean {\n return start <= on && (end === null || on <= end);\n}\n","import type { SmrtObject, SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { HrError } from './types.js';\n\n// Per-instance capability, deliberately absent from the package export surface.\nconst permitted = new WeakSet<SmrtObject>();\n\n/** Whether a service is saving this instance right now. */\nexport function hasHrWrite(object: SmrtObject): boolean {\n return permitted.has(object);\n}\n\n/**\n * Guard for a model's `save()`.\n *\n * @throws HrError `HR_WRITE_FORBIDDEN` outside a service write\n */\nexport function assertHrWrite(object: SmrtObject): void {\n if (!hasHrWrite(object))\n throw new HrError(\n 'HR_WRITE_FORBIDDEN',\n 'HR records are writable only through EmploymentService and QualificationService.',\n );\n}\n\n/** Save one instance under the write capability; services only. */\nexport async function persistHr<T extends SmrtObject>(object: T): Promise<T> {\n permitted.add(object);\n try {\n await object.save();\n return object;\n } finally {\n permitted.delete(object);\n }\n}\n\n/**\n * The one way a service inserts an HR row. `collection.create()` saves\n * outside the write capability (so it always throws for HR models), and a\n * plain first `save()` would upsert onto an existing natural key. This builds\n * the instance through the model's public constructor and `initialize()`,\n * requires a real INSERT (`requireInsertOnSave()`), and saves it under the\n * capability. It uses no protected collection API.\n */\nexport async function insertHr<T extends SmrtObject>(\n Model: new (options: SmrtObjectOptions) => T,\n db: DatabaseInterface,\n values: Record<string, unknown>,\n): Promise<T> {\n const row = await new Model({ db, _skipLoad: true, ...values }).initialize();\n row.requireInsertOnSave();\n return persistHr(row);\n}\n","import {\n crossPackageRef,\n field,\n foreignKey,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n type EmploymentChangeKind,\n type EmploymentStatus,\n HrError,\n type IsoDate,\n type WorkerType,\n} from '../types.js';\nimport { assertHrWrite, hasHrWrite } from '../write.js';\n\n/** Stored column and model property of each employment column this package owns. */\nconst PACKAGE_OWNED_COLUMNS = [\n ['tenant_id', 'tenantId'],\n ['profile_id', 'profileId'],\n ['user_id', 'userId'],\n ['employer_profile_id', 'employerProfileId'],\n ['employee_number', 'employeeNumber'],\n ['worker_type', 'workerType'],\n ['position', 'position'],\n ['status', 'status'],\n] as const;\n\n/**\n * One person's employment with a tenant: the stable record applications and\n * payroll key on. There is one per tenant and profile; a rehire adds an\n * {@link EmploymentTerm} to the same row. The tenant is the employer.\n *\n * Applications extend it with a same-named subclass over the `employments`\n * table that restates the closed surface (see AGENTS.md). Such a subclass may\n * `save()` its own added fields on an existing row; the columns this package\n * owns change only through `EmploymentService`.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'employments',\n sensitive: true,\n conflictColumns: ['tenant_id', 'employee_number'],\n indexes: [\n {\n name: 'employments_tenant_profile_uq',\n columns: ['tenantId', 'profileId'],\n unique: true,\n },\n { name: 'employments_tenant_user_idx', columns: ['tenantId', 'userId'] },\n ],\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class Employment extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n @tenantId() tenantId = '';\n /** The person employed. */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n onDelete: 'RESTRICT',\n })\n profileId = '';\n /** Optional login; a worker signing in on a shared device may have none. */\n @crossPackageRef('@happyvertical/smrt-users:User', { nullable: true })\n userId: string | null = null;\n /** Optional organization profile when it differs from the tenant itself. */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n nullable: true,\n onDelete: 'RESTRICT',\n })\n employerProfileId: string | null = null;\n /** Free text, unique per tenant. */\n employeeNumber = '';\n @field({ type: 'text' }) workerType: WorkerType = 'employee';\n /** Position or trade label; latest recorded value. */\n @field({ type: 'text', nullable: true }) position: string | null = null;\n @field({ type: 'text' }) status: EmploymentStatus = 'active';\n\n override async save(): Promise<this> {\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n // Without the service's write capability only an application subclass's\n // own fields may change: the row must exist and every package-owned\n // column must still hold its stored value.\n if (\n !hasHrWrite(this) &&\n (!prior ||\n PACKAGE_OWNED_COLUMNS.some(\n ([column, property]) =>\n (prior[column] ?? null) !== (this[property] ?? null),\n ))\n )\n throw new HrError(\n 'HR_WRITE_FORBIDDEN',\n 'Employment columns owned by the HR package are writable only through EmploymentService.',\n );\n if (\n prior &&\n (prior.tenant_id !== this.tenantId || prior.profile_id !== this.profileId)\n )\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Employment identity is immutable.',\n );\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Employment history cannot be deleted; end the employment instead.',\n );\n }\n}\n\n/**\n * One continuous period of employment. `endedOn` is the last day employed\n * (inclusive); null means the term is open. \"Employed on a date\" is answered\n * from terms, never from {@link Employment.status}.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'employment_terms',\n sensitive: true,\n indexes: [\n {\n name: 'employment_terms_employment_idx',\n columns: ['tenantId', 'employmentId', 'startedOn'],\n },\n ],\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class EmploymentTerm extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n @tenantId() tenantId = '';\n @foreignKey('Employment', { onDelete: 'RESTRICT' }) employmentId = '';\n @field({ type: 'text' }) startedOn: IsoDate = '';\n @field({ type: 'text', nullable: true }) endedOn: IsoDate | null = null;\n endReason = '';\n\n override async save(): Promise<this> {\n assertHrWrite(this);\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n if (\n prior &&\n (prior.ended_on != null ||\n prior.employment_id !== this.employmentId ||\n prior.started_on !== this.startedOn)\n )\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'A term can only be closed once; its start and employment never change.',\n );\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Employment terms cannot be deleted.',\n );\n }\n}\n\n/** Append-only dated change to an employment, attributed to an actor. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'employment_changes',\n sensitive: true,\n indexes: [\n {\n name: 'employment_changes_employment_idx',\n columns: ['tenantId', 'employmentId', 'effectiveOn'],\n },\n ],\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class EmploymentChange extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n @tenantId() tenantId = '';\n @foreignKey('Employment', { onDelete: 'RESTRICT' }) employmentId = '';\n @field({ type: 'text' }) kind: EmploymentChangeKind = 'hired';\n @field({ type: 'text' }) effectiveOn: IsoDate = '';\n @field({ type: 'text', nullable: true }) fromValue: string | null = null;\n @field({ type: 'text', nullable: true }) toValue: string | null = null;\n note = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n onDelete: 'RESTRICT',\n })\n actorProfileId = '';\n\n override async save(): Promise<this> {\n assertHrWrite(this);\n if (this.id && (await this.db.get(this.tableName, { id: this.id })))\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Employment changes are append-only.',\n );\n this.requireInsertOnSave();\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Employment changes cannot be deleted.',\n );\n }\n}\n\n/**\n * Scoped persistence collection; mutations must use EmploymentService.\n * Collections are registered too, so each restates the closed surface: an\n * undecorated one would publish its methods as generated tools.\n */\n@smrt({ api: { include: [] }, mcp: { include: [] }, cli: false })\nexport class EmploymentCollection extends SmrtCollection<Employment> { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n static readonly _itemClass = Employment;\n}\n/** Scoped term collection; closed like its model. */\n@smrt({ api: { include: [] }, mcp: { include: [] }, cli: false })\nexport class EmploymentTermCollection extends SmrtCollection<EmploymentTerm> { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n static readonly _itemClass = EmploymentTerm;\n}\n/** Scoped append-only change collection; closed like its model. */\n@smrt({ api: { include: [] }, mcp: { include: [] }, cli: false })\nexport class EmploymentChangeCollection extends SmrtCollection<EmploymentChange> { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n static readonly _itemClass = EmploymentChange;\n}\n","import {\n crossPackageRef,\n field,\n foreignKey,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n type HeldQualificationChangeKind,\n type HeldQualificationStatus,\n HrError,\n type IsoDate,\n type QualificationKind,\n type QualificationScope,\n} from '../types.js';\nimport { assertHrWrite } from '../write.js';\n\n/**\n * A qualification definition: a managed list per tenant. Other packages gate\n * work by referencing its id (`@happyvertical/smrt-human-resources:Qualification`).\n * Not a permission: who may use the software stays with smrt-users.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'qualifications',\n sensitive: true,\n conflictColumns: ['tenant_id', 'key'],\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class Qualification extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n @tenantId() tenantId = '';\n /** Lowercase kebab-case, unique per tenant: `first-aid`. */\n key = '';\n name = '';\n @field({ type: 'text' }) kind: QualificationKind = 'certification';\n issuingBody = '';\n /** Whether held qualifications of this kind expire. */\n expires = false;\n /** Default validity in months, applied when a grant gives no expiry. */\n @field({ type: 'integer', nullable: true })\n validityMonths: number | null = null;\n /** Whether a held qualification belongs to the person or the employment. */\n @field({ type: 'text' }) scope: QualificationScope = 'person';\n isActive = true;\n\n override async save(): Promise<this> {\n assertHrWrite(this);\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n if (prior && (prior.key !== this.key || prior.scope !== this.scope))\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'A qualification key and scope are immutable; deactivate it instead.',\n );\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Qualifications cannot be deleted; deactivate them instead.',\n );\n }\n}\n\n/**\n * Columns of a held qualification that never change once a revocation is\n * stored. Only the verification fields (`verifiedByProfileId`, `verifiedAt`,\n * `documentAssetId`) may still be saved on such a row.\n */\nconst FROZEN_WHEN_REVOKED = [\n ['tenant_id', 'tenantId'],\n ['qualification_id', 'qualificationId'],\n ['profile_id', 'profileId'],\n ['employment_id', 'employmentId'],\n ['issued_on', 'issuedOn'],\n ['expires_on', 'expiresOn'],\n ['certificate_number', 'certificateNumber'],\n ['status', 'status'],\n ['renewal_of_id', 'renewalOfId'],\n] as const;\n\n/**\n * A person holds a qualification. `employmentId` is set when the definition's\n * scope is `employment`; such a row is revoked when that employment ends.\n * A renewal is a new row pointing at the one it renews, so past dates still\n * answer correctly. Expiry on a date is computed from `expiresOn`; the stored\n * `expired` status is set by the service sweep. The stored status records the\n * last change made, not standing on a date, and `revoked` is final: a row\n * stored `revoked` is saved again only to record a verification.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'held_qualifications',\n sensitive: true,\n indexes: [\n {\n name: 'held_qualifications_profile_idx',\n columns: ['tenantId', 'profileId', 'qualificationId'],\n },\n {\n name: 'held_qualifications_expiry_idx',\n columns: ['tenantId', 'status', 'expiresOn'],\n },\n ],\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class HeldQualification extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n @tenantId() tenantId = '';\n @foreignKey(Qualification, { onDelete: 'RESTRICT' }) qualificationId = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n onDelete: 'RESTRICT',\n })\n profileId = '';\n @foreignKey('Employment', { nullable: true, onDelete: 'RESTRICT' })\n employmentId: string | null = null;\n @field({ type: 'text' }) issuedOn: IsoDate = '';\n @field({ type: 'text', nullable: true }) expiresOn: IsoDate | null = null;\n certificateNumber = '';\n @field({ type: 'text' }) status: HeldQualificationStatus = 'valid';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n nullable: true,\n onDelete: 'RESTRICT',\n })\n verifiedByProfileId: string | null = null;\n verifiedAt: Date | null = null;\n @crossPackageRef('@happyvertical/smrt-assets:Asset', { nullable: true })\n documentAssetId: string | null = null;\n @foreignKey('HeldQualification', { nullable: true, onDelete: 'RESTRICT' })\n renewalOfId: string | null = null;\n\n override async save(): Promise<this> {\n assertHrWrite(this);\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n if (\n prior &&\n (prior.tenant_id !== this.tenantId ||\n prior.profile_id !== this.profileId ||\n prior.qualification_id !== this.qualificationId ||\n prior.issued_on !== this.issuedOn)\n )\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'A held qualification keeps its person, qualification and issue date; renew it instead.',\n );\n // A revocation is final. Verification is not a status, so a save that\n // changes only the verifier, its time and the document is still allowed.\n if (\n prior?.status === 'revoked' &&\n FROZEN_WHEN_REVOKED.some(\n ([column, property]) =>\n (prior[column] ?? null) !== (this[property] ?? null),\n )\n )\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'A revoked qualification never changes, apart from its verification; grant a new one.',\n );\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Held qualifications cannot be deleted; revoke them instead.',\n );\n }\n}\n\n/** Append-only dated change to a held qualification, attributed to an actor. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'held_qualification_changes',\n sensitive: true,\n indexes: [\n {\n name: 'held_qualification_changes_held_idx',\n columns: ['tenantId', 'heldQualificationId', 'effectiveOn'],\n },\n ],\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class HeldQualificationChange extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n @tenantId() tenantId = '';\n @foreignKey(HeldQualification, { onDelete: 'RESTRICT' })\n heldQualificationId = '';\n @field({ type: 'text' }) kind: HeldQualificationChangeKind = 'granted';\n @field({ type: 'text' }) effectiveOn: IsoDate = '';\n reason = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n onDelete: 'RESTRICT',\n })\n actorProfileId = '';\n\n override async save(): Promise<this> {\n assertHrWrite(this);\n if (this.id && (await this.db.get(this.tableName, { id: this.id })))\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Held qualification changes are append-only.',\n );\n this.requireInsertOnSave();\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Held qualification changes cannot be deleted.',\n );\n }\n}\n\n/**\n * Scoped definition collection; mutations must use QualificationService.\n * Collections are registered too, so each restates the closed surface: an\n * undecorated one would publish its methods as generated tools.\n */\n@smrt({ api: { include: [] }, mcp: { include: [] }, cli: false })\nexport class QualificationCollection extends SmrtCollection<Qualification> { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n static readonly _itemClass = Qualification;\n}\n/** Scoped held-qualification collection; closed like its model. */\n@smrt({ api: { include: [] }, mcp: { include: [] }, cli: false })\nexport class HeldQualificationCollection extends SmrtCollection<HeldQualification> { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n static readonly _itemClass = HeldQualification;\n}\n/** Scoped append-only change collection; closed like its model. */\n@smrt({ api: { include: [] }, mcp: { include: [] }, cli: false })\nexport class HeldQualificationChangeCollection extends SmrtCollection<HeldQualificationChange> { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n static readonly _itemClass = HeldQualificationChange;\n}\n","import type { DatabaseInterface } from '@happyvertical/sql';\nimport type { HrEventQueue } from '../service-base.js';\nimport type { HrActor, IsoDate } from '../types.js';\nimport { insertHr, persistHr } from '../write.js';\nimport {\n type HeldQualification,\n HeldQualificationChange,\n HeldQualificationChangeCollection,\n HeldQualificationCollection,\n} from './models.js';\n\n/** Reason recorded when an employment-scoped qualification ends with its employment. */\nexport const EMPLOYMENT_ENDED_REASON = 'employment-ended';\n\n/**\n * End every qualification that belongs to an employment, from `effectiveOn`\n * (the day after the last day employed). Called by EmploymentService inside\n * the transaction that ends the employment, so the two never disagree.\n * Person-scoped qualifications are untouched.\n *\n * The decision is made per renewal chain, from the chain's dated history and\n * not from any stored status: the latest row of a chain gets the\n * `employment-ended` revocation, and one event, unless a revocation effective\n * on or before `effectiveOn` is already recorded anywhere in the chain. The\n * rows that latest row renewed are not written: the dated replay cuts them\n * off through the chain. A latest row whose stored status is already\n * `revoked` (a later-dated revocation was recorded) is not saved again; only\n * the change row is appended, and the replay treats the earliest revocation\n * as in force. Returns the latest rows that were cut off.\n */\nexport async function revokeEmploymentQualifications(\n db: DatabaseInterface,\n actor: Readonly<HrActor>,\n employmentId: string,\n effectiveOn: IsoDate,\n queue: HrEventQueue,\n): Promise<HeldQualification[]> {\n const held = await HeldQualificationCollection.create({ db });\n const rows = await held.list({\n where: { tenantId: actor.tenantId, employmentId },\n });\n if (rows.length === 0) return [];\n const changes = await HeldQualificationChangeCollection.create({ db });\n const revokedOn = new Map<string, IsoDate>();\n for (const change of await changes.list({\n where: {\n tenantId: actor.tenantId,\n heldQualificationId: rows.map((row) => row.id as string),\n kind: 'revoked',\n },\n })) {\n const earliest = revokedOn.get(change.heldQualificationId);\n if (earliest === undefined || change.effectiveOn < earliest)\n revokedOn.set(change.heldQualificationId, change.effectiveOn);\n }\n const byId = new Map(rows.map((row) => [row.id as string, row]));\n const renewed = new Set(rows.map((row) => row.renewalOfId));\n const cut: HeldQualification[] = [];\n for (const row of rows) {\n if (renewed.has(row.id as string)) continue;\n // The earliest revocation recorded on the latest row or any row it renews.\n let already: IsoDate | undefined;\n const seen = new Set<string>();\n for (\n let member: HeldQualification | undefined = row;\n member && !seen.has(member.id as string);\n member = member.renewalOfId ? byId.get(member.renewalOfId) : undefined\n ) {\n seen.add(member.id as string);\n const on = revokedOn.get(member.id as string);\n if (on !== undefined && (already === undefined || on < already))\n already = on;\n }\n if (already !== undefined && already <= effectiveOn) continue;\n if (row.status !== 'revoked') {\n row.status = 'revoked';\n await persistHr(row);\n }\n await insertHr(HeldQualificationChange, db, {\n tenantId: actor.tenantId,\n heldQualificationId: row.id as string,\n kind: 'revoked',\n effectiveOn,\n reason: EMPLOYMENT_ENDED_REASON,\n actorProfileId: actor.profileId,\n });\n queue({\n type: 'held-qualification.revoked',\n heldQualification: row,\n effectiveOn,\n at: new Date(),\n byProfileId: actor.profileId,\n });\n cut.push(row);\n }\n return cut;\n}\n","import { createLogger } from '@happyvertical/logger';\nimport {\n isEmbeddedDatabase,\n isPostgresDatabase,\n withEmbeddedWriteQueue,\n} from '@happyvertical/smrt-core';\nimport { getTenantId, withTenant } from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface, TransactionHandle } from '@happyvertical/sql';\nimport {\n type HrActor,\n HrError,\n type HrEvent,\n type HrServiceOptions,\n} from './types.js';\n\nconst logger = createLogger({ level: 'info' });\n\nfunction isNestedTransactionError(error: unknown): boolean {\n return error instanceof Error && error.name === 'NestedTransactionError';\n}\n\n/** Collects events inside a transaction; they are delivered after it commits. */\nexport type HrEventQueue = (event: HrEvent) => void;\n\n/**\n * Shared plumbing for the HR services: a trusted tenant/profile actor, scoped\n * reads, serialized transactional writes, and after-commit event delivery.\n * Applications authorize the actor before constructing a service. Reads and\n * writes reject conflicting ambient tenancy.\n *\n * Construct a service with the root database handle. A mutation begins and\n * commits its own transaction with `beginTransaction()` and then delivers its\n * events; on a handle that is already inside a transaction (the `tx` of\n * `db.transaction()`, or a `beginTransaction()` handle) the commit would\n * belong to the caller, so a mutation is refused with\n * `HR_TRANSACTION_UNSUPPORTED` before anything is written. Such a handle\n * either has no `beginTransaction` or refuses it with the SDK's\n * `NestedTransactionError`; the service never opens a nested (savepoint)\n * transaction. Reads work on any handle.\n */\nexport abstract class HrService {\n protected readonly actor: Readonly<HrActor>;\n private readonly onEvent: HrServiceOptions['onEvent'];\n\n constructor(\n protected readonly db: DatabaseInterface,\n actor: HrActor,\n options: HrServiceOptions = {},\n ) {\n if (!actor?.tenantId?.trim() || !actor?.profileId?.trim())\n throw new HrError(\n 'HR_ACTOR_INVALID',\n 'HR services require a trusted tenant/profile actor.',\n );\n this.actor = Object.freeze({\n tenantId: this.identity(actor.tenantId),\n profileId: this.identity(actor.profileId),\n });\n this.onEvent = options.onEvent;\n }\n\n /** Normalize an id; PostgreSQL stores identities as UUIDs. */\n protected identity(value: string): string {\n if (!isPostgresDatabase(this.db)) return value;\n const hex = value.replace(/[{}-]/g, '').toLowerCase();\n if (!/^[0-9a-f]{32}$/.test(hex))\n throw new HrError(\n 'HR_INVALID',\n 'PostgreSQL HR identities must be UUIDs.',\n );\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n }\n\n /**\n * A required id from caller input, trimmed and normalized.\n *\n * @throws HrError `HR_INVALID` when it is not text or is blank\n */\n protected id(fieldName: string, value: unknown): string {\n if (typeof value !== 'string' || !value.trim())\n throw new HrError('HR_INVALID', `${fieldName} is required.`);\n return this.identity(value.trim());\n }\n\n /** An optional id from caller input: null or undefined is null. */\n protected optionalId(fieldName: string, value: unknown): string | null {\n return value === null || value === undefined\n ? null\n : this.id(fieldName, value);\n }\n\n /** Run a read inside the actor's tenant. */\n protected scope<T>(run: () => Promise<T>): Promise<T> {\n const active = getTenantId();\n if (active && this.identity(active) !== this.actor.tenantId)\n throw new HrError(\n 'HR_TENANT_MISMATCH',\n 'HR actor differs from active tenant.',\n );\n return withTenant({ tenantId: this.actor.tenantId }, run);\n }\n\n /**\n * Run a mutation in one transaction, serialized per tenant, then deliver the\n * events it queued. Nothing is delivered when the transaction fails.\n *\n * @throws HrError `HR_TRANSACTION_UNSUPPORTED` when the service was constructed with a handle that is already inside a transaction\n */\n protected async transact<T>(\n run: (db: DatabaseInterface, queue: HrEventQueue) => Promise<T>,\n ): Promise<T> {\n // `@happyvertical/sql` has no after-commit hook and no marker for \"inside\n // a transaction\", so the service begins a transaction of its own and\n // commits it itself; it never calls `transaction()`, which on a\n // transaction-scoped handle would re-enter the caller's transaction under\n // a savepoint. Only a root handle can begin one: a transaction-scoped\n // handle has no `beginTransaction` (libsql SQLite, PostgreSQL, DuckDB,\n // JSON) or refuses it with `NestedTransactionError` before touching the\n // connection (the native-capabilities SQLite adapter). Both fail closed.\n const unsupported = () =>\n new HrError(\n 'HR_TRANSACTION_UNSUPPORTED',\n 'HR services must be constructed with the root database handle, not one that is already inside a transaction: events are delivered when the service commits its own transaction.',\n );\n const begin = this.db.beginTransaction;\n if (typeof begin !== 'function') throw unsupported();\n const events: HrEvent[] = [];\n const result = await this.scope(() =>\n withEmbeddedWriteQueue(this.db, isEmbeddedDatabase(this.db), async () => {\n let tx: TransactionHandle;\n try {\n tx = await begin.call(this.db);\n } catch (error) {\n // Matched by name: the sql root (pg, node:fs) is not importable from\n // a browser-safe model root, and the SDK sets this name on the class.\n if (isNestedTransactionError(error)) throw unsupported();\n throw error;\n }\n let value: T;\n try {\n if (isPostgresDatabase(this.db))\n await tx.query(\n 'SELECT pg_advisory_xact_lock(hashtextextended($1, 0))',\n JSON.stringify(['smrt.human-resources', this.actor.tenantId]),\n );\n value = await run(tx, (event) => events.push(event));\n } catch (error) {\n await tx.rollback().catch(() => undefined);\n throw error;\n }\n // A failed commit ends the handle itself (the SDK rolls back and\n // releases the connection), so it is not rolled back again here.\n await tx.commit();\n return value;\n }),\n );\n for (const event of events) await this.emit(event);\n return result;\n }\n\n private async emit(event: HrEvent): Promise<void> {\n if (!this.onEvent) return;\n try {\n await this.onEvent(event);\n } catch (error) {\n logger.error(\n `[smrt-human-resources] onEvent handler failed for ${event.type}`,\n { error: error instanceof Error ? error.message : String(error) },\n );\n }\n }\n}\n","import type { DatabaseInterface } from '@happyvertical/sql';\nimport { dateWithin } from '../dates.js';\nimport type { IsoDate } from '../types.js';\nimport {\n type Employment,\n EmploymentCollection,\n type EmploymentTerm,\n EmploymentTermCollection,\n} from './models.js';\n\n/**\n * Terms that cover `on` for one tenant. Run inside the tenant's scope; the\n * caller supplies a validated date. Shared by both services so \"employed on a\n * date\" has one definition.\n */\nexport async function termsCovering(\n db: DatabaseInterface,\n tenantId: string,\n on: IsoDate,\n): Promise<EmploymentTerm[]> {\n const terms = await EmploymentTermCollection.create({ db });\n const started = await terms.list({\n where: { tenantId, 'startedOn <=': on },\n });\n return started.filter((term) => dateWithin(on, term.startedOn, term.endedOn));\n}\n\n/** Employments with a term covering `on`, for one tenant. */\nexport async function employmentsOn(\n db: DatabaseInterface,\n tenantId: string,\n on: IsoDate,\n): Promise<Employment[]> {\n const ids = [\n ...new Set(\n (await termsCovering(db, tenantId, on)).map((t) => t.employmentId),\n ),\n ];\n if (ids.length === 0) return [];\n const employments = await EmploymentCollection.create({ db });\n return employments.list({ where: { tenantId, id: ids } });\n}\n","import type { DatabaseInterface } from '@happyvertical/sql';\nimport { addDays, assertIsoDate, dateWithin } from '../dates.js';\nimport { revokeEmploymentQualifications } from '../qualifications/internal.js';\nimport { HrService } from '../service-base.js';\nimport {\n EMPLOYMENT_STATUS_TRANSITIONS,\n type EmploymentChangeKind,\n type EmploymentCheck,\n type EmploymentStatus,\n HrError,\n type IsoDate,\n type WorkerType,\n} from '../types.js';\nimport { insertHr, persistHr } from '../write.js';\nimport {\n Employment,\n EmploymentChange,\n EmploymentChangeCollection,\n EmploymentCollection,\n EmploymentTerm,\n EmploymentTermCollection,\n} from './models.js';\nimport { employmentsOn } from './queries.js';\n\n/** Input to {@link EmploymentService.hire}. */\nexport interface HireInput {\n /** The person being employed (`smrt-profiles:Profile` id). */\n profileId: string;\n /** Free text, unique per tenant; surrounding whitespace is trimmed. */\n employeeNumber: string;\n /** First day employed. */\n startedOn: IsoDate;\n /** Lowercase kebab-case; defaults to `employee`. */\n workerType?: WorkerType;\n /** Position or trade label; blank means none. */\n position?: string | null;\n /** Optional login (`smrt-users:User` id). */\n userId?: string | null;\n /** Optional organization profile when it differs from the tenant itself. */\n employerProfileId?: string | null;\n}\n\n/** Input to {@link EmploymentService.end}. */\nexport interface EndEmploymentInput {\n /** The last day employed, inclusive. */\n endedOn: IsoDate;\n /** Free-text reason kept on the closed term and the change row. */\n reason?: string;\n}\n\n/** Input to {@link EmploymentService.rehire}. */\nexport interface RehireInput {\n /** First day of the new term; must be after the previous term's last day. */\n startedOn: IsoDate;\n /** New worker type; omitted keeps the previous one. */\n workerType?: WorkerType;\n /** New position (null clears it); omitted keeps the previous one. */\n position?: string | null;\n}\n\n/** Input to {@link EmploymentService.changePosition}. */\nexport interface ChangePositionInput {\n /** The new position; null or blank clears it. */\n position: string | null;\n /** The day the new position takes effect. */\n effectiveOn: IsoDate;\n /** Optional note kept on the change row. */\n note?: string;\n}\n\n/** Input to {@link EmploymentService.changeWorkerType}. */\nexport interface ChangeWorkerTypeInput {\n /** The new worker type, lowercase kebab-case. */\n workerType: WorkerType;\n /** The day the new worker type takes effect. */\n effectiveOn: IsoDate;\n /** Optional note kept on the change row. */\n note?: string;\n}\n\n/** Input to {@link EmploymentService.placeOnLeave} and {@link EmploymentService.returnFromLeave}. */\nexport interface LeaveInput {\n /** The first day of leave, or the first day back. */\n effectiveOn: IsoDate;\n /** Optional note kept on the change row. */\n note?: string;\n}\n\n/** Input to {@link EmploymentService.linkLogin} and {@link EmploymentService.unlinkLogin}. */\nexport interface LoginLinkInput {\n /**\n * The date recorded on the change row. It does not schedule anything: the\n * link or unlink takes effect as soon as it is recorded.\n */\n effectiveOn: IsoDate;\n}\n\n/** An employment's recorded state on one calendar date. */\nexport interface EmploymentAsOf {\n /** Position in effect on the date; before any change, the position at hire. */\n position: string | null;\n /** Worker type in effect on the date; before any change, the type at hire. */\n workerType: string;\n /** Whether a term covers the date. */\n employed: boolean;\n /** Whether the person was employed and on leave on the date. */\n onLeave: boolean;\n}\n\nconst WORKER_TYPE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * Changes that belong to a term and are replayed by `check` and `asOf`. Login\n * links are not: they are not replayed and may be recorded at any time.\n */\nconst TERM_CHANGE_KINDS: readonly EmploymentChangeKind[] = [\n 'hired',\n 'ended',\n 'rehired',\n 'position-changed',\n 'worker-type-changed',\n 'leave-started',\n 'leave-ended',\n];\n\nconst LEAVE_CHANGE_KINDS: readonly EmploymentChangeKind[] = [\n 'leave-started',\n 'leave-ended',\n];\n\nfunction workerTypeOf(value: unknown): WorkerType {\n if (typeof value !== 'string' || !WORKER_TYPE.test(value))\n throw new HrError(\n 'HR_INVALID',\n `workerType must be lowercase kebab-case, got '${String(value)}'.`,\n );\n return value;\n}\n\nfunction positionOf(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n if (typeof value !== 'string')\n throw new HrError('HR_INVALID', 'position must be text or null.');\n return value.trim() || null;\n}\n\nfunction noteOf(value: unknown): string {\n if (value === undefined || value === null) return '';\n if (typeof value !== 'string')\n throw new HrError('HR_INVALID', 'A note or reason must be text.');\n return value.trim();\n}\n\nfunction createdTime(change: EmploymentChange): number {\n const at = change.created_at;\n const time = at ? new Date(at).getTime() : 0;\n return Number.isNaN(time) ? 0 : time;\n}\n\nfunction inHistoryOrder(changes: EmploymentChange[]): EmploymentChange[] {\n return [...changes].sort((a, b) =>\n a.effectiveOn === b.effectiveOn\n ? createdTime(a) - createdTime(b)\n : a.effectiveOn < b.effectiveOn\n ? -1\n : 1,\n );\n}\n\n/** Whether the replayed history leaves the person on leave on `on`. */\nfunction onLeaveOn(history: EmploymentChange[], on: IsoDate): boolean {\n let onLeave = false;\n for (const change of history) {\n if (change.effectiveOn > on) break;\n if (change.kind === 'leave-started') onLeave = true;\n // A new term starts at work; `ended` is the last day and is still on leave.\n else if (change.kind === 'leave-ended' || change.kind === 'rehired')\n onLeave = false;\n }\n return onLeave;\n}\n\n/**\n * The value of a dated attribute on `on`: the latest change on or before it,\n * else the value before the first change, else the current value.\n */\nfunction valueOn(\n history: EmploymentChange[],\n kind: EmploymentChangeKind,\n on: IsoDate,\n current: string | null,\n): string | null {\n const ofKind = history.filter((change) => change.kind === kind);\n if (ofKind.length === 0) return current;\n let value = ofKind[0].fromValue;\n for (const change of ofKind) {\n if (change.effectiveOn > on) break;\n value = change.toValue;\n }\n return value;\n}\n\n/**\n * The only writer of employment records. Construct it from a trusted\n * `{ tenantId, profileId }` actor the application has already authorized.\n * Every mutation runs in one transaction, appends a dated\n * {@link EmploymentChange} attributed to the actor, and delivers its event\n * after the transaction commits. Dates are calendar dates (`YYYY-MM-DD`).\n */\nexport class EmploymentService extends HrService {\n private async load(db: DatabaseInterface, id: string): Promise<Employment> {\n const employments = await EmploymentCollection.create({ db });\n const [employment] = await employments.list({\n where: { tenantId: this.actor.tenantId, id: this.id('employmentId', id) },\n limit: 1,\n });\n if (!employment)\n throw new HrError('HR_NOT_FOUND', 'Employment not found in this tenant.');\n return employment;\n }\n\n private async one(\n where: Record<string, unknown>,\n db: DatabaseInterface = this.db,\n ): Promise<Employment | null> {\n const employments = await EmploymentCollection.create({ db });\n const [employment] = await employments.list({\n where: { tenantId: this.actor.tenantId, ...where },\n limit: 1,\n });\n return employment ?? null;\n }\n\n private async termsOf(\n db: DatabaseInterface,\n employmentId: string,\n ): Promise<EmploymentTerm[]> {\n const terms = await EmploymentTermCollection.create({ db });\n const rows = await terms.list({\n where: { tenantId: this.actor.tenantId, employmentId },\n });\n return rows.sort((a, b) =>\n a.startedOn < b.startedOn ? -1 : a.startedOn > b.startedOn ? 1 : 0,\n );\n }\n\n private async changesOf(\n db: DatabaseInterface,\n employmentId: string,\n ): Promise<EmploymentChange[]> {\n const changes = await EmploymentChangeCollection.create({ db });\n return inHistoryOrder(\n await changes.list({\n where: { tenantId: this.actor.tenantId, employmentId },\n }),\n );\n }\n\n /**\n * Append a change. Creation instants are strictly increasing within one\n * employment (writes are serialized per tenant), so same-day changes replay\n * in the order they were recorded.\n */\n private async record(\n db: DatabaseInterface,\n employmentId: string,\n change: {\n kind: EmploymentChangeKind;\n effectiveOn: IsoDate;\n fromValue?: string | null;\n toValue?: string | null;\n note?: string;\n },\n ): Promise<void> {\n const changes = await EmploymentChangeCollection.create({ db });\n const latest = (\n await changes.list({\n where: { tenantId: this.actor.tenantId, employmentId },\n })\n ).reduce((max, row) => Math.max(max, createdTime(row)), 0);\n await insertHr(EmploymentChange, db, {\n tenantId: this.actor.tenantId,\n employmentId,\n kind: change.kind,\n effectiveOn: change.effectiveOn,\n fromValue: change.fromValue ?? null,\n toValue: change.toValue ?? null,\n note: change.note ?? '',\n actorProfileId: this.actor.profileId,\n created_at: new Date(Math.max(Date.now(), latest + 1)),\n });\n }\n\n /**\n * The term a dated change belongs to, with `effectiveOn` inside it: the\n * latest term, which is the open one until an end is recorded and the final\n * one (up to its last day) afterwards. The stored status plays no part, so\n * a change dated inside a notice period is accepted.\n *\n * @throws HrError `HR_INVALID` before the term started, `HR_STATUS_TRANSITION` after its last day\n */\n private async latestTerm(\n db: DatabaseInterface,\n employment: Employment,\n effectiveOn: IsoDate,\n ): Promise<EmploymentTerm> {\n const term = (await this.termsOf(db, employment.id as string)).at(-1);\n if (!term)\n throw new HrError('HR_STATUS_TRANSITION', 'The employment has no term.');\n if (effectiveOn < term.startedOn)\n throw new HrError(\n 'HR_INVALID',\n `${effectiveOn} is before the current term started (${term.startedOn}).`,\n );\n if (term.endedOn !== null && effectiveOn > term.endedOn)\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n `The employment ended on ${term.endedOn}; rehire it first.`,\n );\n return term;\n }\n\n private async notBeforeLatest(\n db: DatabaseInterface,\n employmentId: string,\n kinds: readonly EmploymentChangeKind[],\n effectiveOn: IsoDate,\n ): Promise<void> {\n const latest = (await this.changesOf(db, employmentId))\n .filter((change) => kinds.includes(change.kind))\n .at(-1);\n if (latest && effectiveOn < latest.effectiveOn)\n throw new HrError(\n 'HR_INVALID',\n `${effectiveOn} is earlier than the latest recorded change (${latest.effectiveOn}).`,\n );\n }\n\n /**\n * A login is exclusive to the employment that stores it until it is\n * explicitly unlinked there, whatever that employment's stored status. An\n * ended employment keeps its login through its notice period and after, so\n * two employments never share one and `findByUser(userId, on)` cannot be\n * ambiguous through the service.\n */\n private async loginFree(\n db: DatabaseInterface,\n userId: string,\n employmentId: string | null,\n ): Promise<void> {\n const employments = await EmploymentCollection.create({ db });\n const linked = await employments.list({\n where: { tenantId: this.actor.tenantId, userId },\n });\n if (linked.some((other) => other.id !== employmentId))\n throw new HrError(\n 'HR_INVALID',\n 'This login is already linked to another employment; unlink it there first.',\n );\n }\n\n private transition(employment: Employment, to: EmploymentStatus): void {\n if (!EMPLOYMENT_STATUS_TRANSITIONS[employment.status]?.includes(to))\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n `An employment cannot go from '${employment.status}' to '${to}'.`,\n );\n employment.status = to;\n }\n\n /**\n * Employ a person: creates the employment, its first term and a `hired`\n * change. A profile has one employment per tenant, so a person who worked\n * here before is rehired instead.\n *\n * @throws HrError `HR_ALREADY_EMPLOYED`, `HR_EMPLOYEE_NUMBER_TAKEN`, `HR_INVALID` (including a login already linked to another employment, ended or not: unlink it there first)\n */\n async hire(input: HireInput): Promise<Employment> {\n const profileId = this.id('profileId', input?.profileId);\n const employeeNumber =\n typeof input.employeeNumber === 'string'\n ? input.employeeNumber.trim()\n : '';\n if (!employeeNumber)\n throw new HrError('HR_INVALID', 'employeeNumber is required.');\n const startedOn = assertIsoDate('startedOn', input.startedOn);\n const workerType = workerTypeOf(input.workerType ?? 'employee');\n const position = positionOf(input.position);\n const userId = this.optionalId('userId', input.userId);\n const employerProfileId = this.optionalId(\n 'employerProfileId',\n input.employerProfileId,\n );\n const id = await this.transact(async (db, queue) => {\n if (await this.one({ profileId }, db))\n throw new HrError(\n 'HR_ALREADY_EMPLOYED',\n 'This profile already has an employment in this tenant; rehire it instead.',\n );\n if (await this.one({ employeeNumber }, db))\n throw new HrError(\n 'HR_EMPLOYEE_NUMBER_TAKEN',\n `Employee number '${employeeNumber}' is already used in this tenant.`,\n );\n if (userId) await this.loginFree(db, userId, null);\n const employment = await insertHr(Employment, db, {\n tenantId: this.actor.tenantId,\n profileId,\n userId,\n employerProfileId,\n employeeNumber,\n workerType,\n position,\n status: 'active',\n });\n const employmentId = employment.id as string;\n await insertHr(EmploymentTerm, db, {\n tenantId: this.actor.tenantId,\n employmentId,\n startedOn,\n });\n await this.record(db, employmentId, {\n kind: 'hired',\n effectiveOn: startedOn,\n });\n queue({\n type: 'employment.hired',\n employment,\n effectiveOn: startedOn,\n at: new Date(),\n byProfileId: this.actor.profileId,\n });\n return employmentId;\n });\n return this.get(id);\n }\n\n /**\n * End an employment: closes the open term on `endedOn` (the last day\n * employed, inclusive) and, in the same transaction, revokes its\n * employment-scoped qualifications from the day after, so they are still\n * good on the last day. `endedOn` cannot be earlier than a position,\n * worker-type or leave change already recorded in the term.\n *\n * `endedOn` may be ahead of the day the end is recorded (a notice period).\n * The stored status becomes `ended` at once, meaning \"an end has been\n * recorded\": the person is still employed up to and including `endedOn`,\n * so ask by date with {@link check}, {@link employedOn}, {@link asOf} or\n * `findByUser(userId, on)`. Position, worker-type and leave changes dated\n * inside the final term are still accepted.\n *\n * @throws HrError `HR_STATUS_TRANSITION` when already ended, `HR_INVALID`, `HR_NOT_FOUND`\n */\n async end(\n employmentId: string,\n input: EndEmploymentInput,\n ): Promise<Employment> {\n const endedOn = assertIsoDate('endedOn', input?.endedOn);\n const reason = noteOf(input.reason);\n const id = await this.transact(async (db, queue) => {\n const employment = await this.load(db, employmentId);\n this.transition(employment, 'ended');\n const term = await this.latestTerm(db, employment, endedOn);\n if (term.endedOn !== null)\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n 'The employment has no open term.',\n );\n await this.notBeforeLatest(\n db,\n employment.id as string,\n TERM_CHANGE_KINDS,\n endedOn,\n );\n term.endedOn = endedOn;\n term.endReason = reason;\n await persistHr(term);\n await persistHr(employment);\n await this.record(db, employment.id as string, {\n kind: 'ended',\n effectiveOn: endedOn,\n note: reason,\n });\n await revokeEmploymentQualifications(\n db,\n this.actor,\n employment.id as string,\n addDays(endedOn, 1),\n queue,\n );\n queue({\n type: 'employment.ended',\n employment,\n effectiveOn: endedOn,\n at: new Date(),\n byProfileId: this.actor.profileId,\n });\n return employment.id as string;\n });\n return this.get(id);\n }\n\n /**\n * Rehire an ended employment: adds a term to the same row, so the\n * employment id stays stable. The new term cannot start before a position,\n * worker-type or leave change already recorded. The employment keeps the\n * login it stored, which no other employment can have taken meanwhile.\n *\n * @throws HrError `HR_STATUS_TRANSITION` unless ended, `HR_TERM_OVERLAP`, `HR_INVALID`, `HR_NOT_FOUND`\n */\n async rehire(employmentId: string, input: RehireInput): Promise<Employment> {\n const startedOn = assertIsoDate('startedOn', input?.startedOn);\n const workerType =\n input.workerType === undefined\n ? undefined\n : workerTypeOf(input.workerType);\n const position =\n input.position === undefined ? undefined : positionOf(input.position);\n const id = await this.transact(async (db, queue) => {\n const employment = await this.load(db, employmentId);\n if (employment.status !== 'ended')\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n `Only an ended employment can be rehired; this one is '${employment.status}'.`,\n );\n const id = employment.id as string;\n const previous = await this.termsOf(db, id);\n const overlapping = previous.find(\n (term) => term.endedOn === null || startedOn <= term.endedOn,\n );\n if (overlapping)\n throw new HrError(\n 'HR_TERM_OVERLAP',\n `A rehire must start after the previous term ended (${overlapping.endedOn ?? 'still open'}).`,\n );\n await this.notBeforeLatest(db, id, TERM_CHANGE_KINDS, startedOn);\n // Backstop: the service never lets a stored login be linked elsewhere.\n if (employment.userId) await this.loginFree(db, employment.userId, id);\n const from = {\n position: employment.position,\n workerType: employment.workerType,\n };\n this.transition(employment, 'active');\n if (position !== undefined) employment.position = position;\n if (workerType !== undefined) employment.workerType = workerType;\n await persistHr(employment);\n await insertHr(EmploymentTerm, db, {\n tenantId: this.actor.tenantId,\n employmentId: id,\n startedOn,\n });\n await this.record(db, id, { kind: 'rehired', effectiveOn: startedOn });\n if (employment.position !== from.position)\n await this.record(db, id, {\n kind: 'position-changed',\n effectiveOn: startedOn,\n fromValue: from.position,\n toValue: employment.position,\n });\n if (employment.workerType !== from.workerType)\n await this.record(db, id, {\n kind: 'worker-type-changed',\n effectiveOn: startedOn,\n fromValue: from.workerType,\n toValue: employment.workerType,\n });\n queue({\n type: 'employment.rehired',\n employment,\n effectiveOn: startedOn,\n at: new Date(),\n byProfileId: this.actor.profileId,\n });\n return id;\n });\n return this.get(id);\n }\n\n /**\n * Record a new position from `effectiveOn`. The employment row holds the\n * latest value; the change row carries the old and new ones.\n *\n * `effectiveOn` must fall inside the latest term. Once an end has been\n * recorded that is the final term, up to and including its `endedOn`, so a\n * change during a notice period is accepted; the stored status stays\n * `ended`.\n *\n * @throws HrError `HR_STATUS_TRANSITION` when `effectiveOn` is after the last day of an ended employment; `HR_INVALID` for a no-op, a date before the term started or before the latest position change; `HR_NOT_FOUND`\n */\n async changePosition(\n employmentId: string,\n input: ChangePositionInput,\n ): Promise<Employment> {\n const effectiveOn = assertIsoDate('effectiveOn', input?.effectiveOn);\n const position = positionOf(input.position);\n const note = noteOf(input.note);\n const id = await this.transact(async (db, queue) => {\n const employment = await this.load(db, employmentId);\n const id = employment.id as string;\n await this.latestTerm(db, employment, effectiveOn);\n if (employment.position === position)\n throw new HrError(\n 'HR_INVALID',\n 'The employment already has this position.',\n );\n await this.notBeforeLatest(db, id, ['position-changed'], effectiveOn);\n const fromValue = employment.position;\n employment.position = position;\n await persistHr(employment);\n await this.record(db, id, {\n kind: 'position-changed',\n effectiveOn,\n fromValue,\n toValue: position,\n note,\n });\n queue({\n type: 'employment.position-changed',\n employment,\n effectiveOn,\n at: new Date(),\n byProfileId: this.actor.profileId,\n });\n return id;\n });\n return this.get(id);\n }\n\n /**\n * Record a new worker type from `effectiveOn`. The employment row holds the\n * latest value; the change row carries the old and new ones.\n *\n * `effectiveOn` must fall inside the latest term. Once an end has been\n * recorded that is the final term, up to and including its `endedOn`, so a\n * change during a notice period is accepted; the stored status stays\n * `ended`.\n *\n * @throws HrError `HR_STATUS_TRANSITION` when `effectiveOn` is after the last day of an ended employment; `HR_INVALID` for a no-op, a bad value, a date before the term started or before the latest worker-type change; `HR_NOT_FOUND`\n */\n async changeWorkerType(\n employmentId: string,\n input: ChangeWorkerTypeInput,\n ): Promise<Employment> {\n const effectiveOn = assertIsoDate('effectiveOn', input?.effectiveOn);\n const workerType = workerTypeOf(input.workerType);\n const note = noteOf(input.note);\n const id = await this.transact(async (db, queue) => {\n const employment = await this.load(db, employmentId);\n const id = employment.id as string;\n await this.latestTerm(db, employment, effectiveOn);\n if (employment.workerType === workerType)\n throw new HrError(\n 'HR_INVALID',\n 'The employment already has this worker type.',\n );\n await this.notBeforeLatest(db, id, ['worker-type-changed'], effectiveOn);\n const fromValue = employment.workerType;\n employment.workerType = workerType;\n await persistHr(employment);\n await this.record(db, id, {\n kind: 'worker-type-changed',\n effectiveOn,\n fromValue,\n toValue: workerType,\n note,\n });\n queue({\n type: 'employment.worker-type-changed',\n employment,\n effectiveOn,\n at: new Date(),\n byProfileId: this.actor.profileId,\n });\n return id;\n });\n return this.get(id);\n }\n\n private async leave(\n employmentId: string,\n input: LeaveInput,\n to: 'active' | 'on-leave',\n ): Promise<Employment> {\n const effectiveOn = assertIsoDate('effectiveOn', input?.effectiveOn);\n const note = noteOf(input.note);\n const kind = to === 'on-leave' ? 'leave-started' : 'leave-ended';\n const id = await this.transact(async (db, queue) => {\n const employment = await this.load(db, employmentId);\n const id = employment.id as string;\n if (employment.status === 'ended') {\n // An end is recorded: the stored status no longer says whether the\n // person is on leave, so the final term's replayed history decides,\n // and the status stays `ended` (`ended → active` is only a rehire).\n await this.latestTerm(db, employment, effectiveOn);\n await this.notBeforeLatest(db, id, LEAVE_CHANGE_KINDS, effectiveOn);\n const onLeave = onLeaveOn(await this.changesOf(db, id), effectiveOn);\n if (onLeave === (to === 'on-leave'))\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n onLeave\n ? `The employment is already on leave on ${effectiveOn}.`\n : `The employment is not on leave on ${effectiveOn}.`,\n );\n } else {\n this.transition(employment, to);\n await this.latestTerm(db, employment, effectiveOn);\n await this.notBeforeLatest(db, id, LEAVE_CHANGE_KINDS, effectiveOn);\n await persistHr(employment);\n }\n await this.record(db, id, { kind, effectiveOn, note });\n queue({\n type: `employment.${kind}`,\n employment,\n effectiveOn,\n at: new Date(),\n byProfileId: this.actor.profileId,\n });\n return id;\n });\n return this.get(id);\n }\n\n /**\n * Place an employment on leave from `effectiveOn`. A person on leave is\n * still employed.\n *\n * Once an end has been recorded, leave dated inside the final term (up to\n * and including its `endedOn`) is still accepted: it is recorded as a\n * change row, the stored status stays `ended`, and whether the person is\n * already on leave is decided by replaying the history as of `effectiveOn`.\n *\n * @throws HrError `HR_STATUS_TRANSITION` when already on leave, or when `effectiveOn` is after the last day of an ended employment; `HR_INVALID` for a date before the term started or before the latest leave change; `HR_NOT_FOUND`\n */\n async placeOnLeave(\n employmentId: string,\n input: LeaveInput,\n ): Promise<Employment> {\n return this.leave(employmentId, input, 'on-leave');\n }\n\n /**\n * Return an employment from leave; `effectiveOn` is the first day back.\n *\n * Once an end has been recorded, a return dated inside the final term (up\n * to and including its `endedOn`) is still accepted: it is recorded as a\n * change row, the stored status stays `ended`, and whether the person is\n * on leave is decided by replaying the history as of `effectiveOn`.\n *\n * @throws HrError `HR_STATUS_TRANSITION` unless on leave, or when `effectiveOn` is after the last day of an ended employment; `HR_INVALID` for a date before the term started or before the latest leave change; `HR_NOT_FOUND`\n */\n async returnFromLeave(\n employmentId: string,\n input: LeaveInput,\n ): Promise<Employment> {\n return this.leave(employmentId, input, 'active');\n }\n\n private async login(\n employmentId: string,\n userId: string | null,\n input: LoginLinkInput,\n ): Promise<Employment> {\n const effectiveOn = assertIsoDate('effectiveOn', input?.effectiveOn);\n const id = await this.transact(async (db) => {\n const employment = await this.load(db, employmentId);\n const id = employment.id as string;\n if (employment.userId === userId)\n throw new HrError(\n 'HR_INVALID',\n userId\n ? 'This login is already linked to the employment.'\n : 'The employment has no linked login.',\n );\n if (userId) await this.loginFree(db, userId, id);\n const fromValue = employment.userId;\n employment.userId = userId;\n await persistHr(employment);\n await this.record(db, id, {\n kind: userId ? 'login-linked' : 'login-unlinked',\n effectiveOn,\n fromValue,\n toValue: userId,\n });\n return id;\n });\n return this.get(id);\n }\n\n /**\n * Link a login (`smrt-users:User` id) to an employment, replacing any\n * previous one. Grants nothing by itself: permissions stay with smrt-users.\n * A login belongs to one employment at a time and stays with it, ended or\n * not, until {@link unlinkLogin} clears it there.\n *\n * The link takes effect at once, whatever `effectiveOn` says: the date is\n * only recorded on the change row, and login links are not replayed by\n * {@link asOf} or `findByUser(userId, on)`.\n *\n * @throws HrError `HR_INVALID` when already linked to this login, or to another employment (unlink it there first); `HR_NOT_FOUND`\n */\n async linkLogin(\n employmentId: string,\n userId: string,\n input: LoginLinkInput,\n ): Promise<Employment> {\n return this.login(employmentId, this.id('userId', userId), input);\n }\n\n /**\n * Clear an employment's login, which frees it for another employment. Works\n * on an ended employment too. The login is cleared at once, whatever\n * `effectiveOn` says; a future date does not keep it linked until then.\n *\n * @throws HrError `HR_INVALID` when no login is linked, `HR_NOT_FOUND`\n */\n async unlinkLogin(\n employmentId: string,\n input: LoginLinkInput,\n ): Promise<Employment> {\n return this.login(employmentId, null, input);\n }\n\n /**\n * One employment in the actor's tenant.\n *\n * @throws HrError `HR_NOT_FOUND` when it does not exist in this tenant\n */\n async get(employmentId: string): Promise<Employment> {\n return this.scope(() => this.load(this.db, employmentId));\n }\n\n /** The profile's employment in this tenant (ended or not), or null. */\n async findByProfile(profileId: string): Promise<Employment | null> {\n if (typeof profileId !== 'string' || !profileId.trim()) return null;\n const id = this.identity(profileId.trim());\n return this.scope(() => this.one({ profileId: id }));\n }\n\n /** The employment with this employee number in this tenant, or null. */\n async findByEmployeeNumber(\n employeeNumber: string,\n ): Promise<Employment | null> {\n if (typeof employeeNumber !== 'string' || !employeeNumber.trim())\n return null;\n return this.scope(() =>\n this.one({ employeeNumber: employeeNumber.trim() }),\n );\n }\n\n /**\n * The employment linked to a login. Fails closed: null when there is none,\n * or when more than one matches (the service keeps a login on one\n * employment, so that takes rows written some other way).\n *\n * With `on`, the answer comes from the dated terms: the employment linked\n * to the login that has a term covering `on`, whatever its stored status.\n * Without `on`, it is the linked employment whose stored status is not\n * `ended`, which stops resolving as soon as an end is recorded. A host that\n * records end dates ahead of time (a notice period) should pass the date,\n * or the worker is locked out before their last day.\n *\n * @throws HrError `HR_INVALID` when `on` is given and is not a calendar date\n */\n async findByUser(userId: string, on?: IsoDate): Promise<Employment | null> {\n const date = on === undefined ? null : assertIsoDate('on', on);\n if (typeof userId !== 'string' || !userId.trim()) return null;\n const id = this.identity(userId.trim());\n return this.scope(async () => {\n const employments = await EmploymentCollection.create({ db: this.db });\n const linked = await employments.list({\n where: { tenantId: this.actor.tenantId, userId: id },\n });\n const matching: Employment[] = [];\n for (const employment of linked) {\n const matches =\n date === null\n ? employment.status !== 'ended'\n : (await this.termsOf(this.db, employment.id as string)).some(\n (term) => dateWithin(date, term.startedOn, term.endedOn),\n );\n if (matches) matching.push(employment);\n }\n return matching.length === 1 ? matching[0] : null;\n });\n }\n\n /** Everyone with a term covering `on`, including people on leave. */\n async employedOn(on: IsoDate): Promise<Employment[]> {\n const date = assertIsoDate('on', on);\n return this.scope(() => employmentsOn(this.db, this.actor.tenantId, date));\n }\n\n /**\n * Whether a profile is employed on a date. Answered from terms, never from\n * the stored status; `onLeave` is computed as of `on`, so past dates answer\n * correctly.\n */\n async check(profileId: string, on: IsoDate): Promise<EmploymentCheck> {\n const date = assertIsoDate('on', on);\n if (typeof profileId !== 'string' || !profileId.trim())\n return { ok: false, reason: 'not-employed' };\n const id = this.identity(profileId.trim());\n return this.scope(async () => {\n const employment = await this.one({ profileId: id });\n if (!employment) return { ok: false, reason: 'not-employed' };\n const employmentId = employment.id as string;\n const terms = await this.termsOf(this.db, employmentId);\n if (terms.length === 0) return { ok: false, reason: 'not-employed' };\n if (!terms.some((term) => dateWithin(date, term.startedOn, term.endedOn)))\n return {\n ok: false,\n reason: date < terms[0].startedOn ? 'not-started' : 'ended',\n };\n return {\n ok: true,\n employmentId,\n onLeave: onLeaveOn(await this.changesOf(this.db, employmentId), date),\n };\n });\n }\n\n /**\n * An employment's terms, oldest first.\n *\n * @throws HrError `HR_NOT_FOUND` when the employment is not in this tenant\n */\n async terms(employmentId: string): Promise<EmploymentTerm[]> {\n return this.scope(async () => {\n const employment = await this.load(this.db, employmentId);\n return this.termsOf(this.db, employment.id as string);\n });\n }\n\n /**\n * An employment's dated changes, by effective date and then in the order\n * they were recorded.\n *\n * @throws HrError `HR_NOT_FOUND` when the employment is not in this tenant\n */\n async history(employmentId: string): Promise<EmploymentChange[]> {\n return this.scope(async () => {\n const employment = await this.load(this.db, employmentId);\n return this.changesOf(this.db, employment.id as string);\n });\n }\n\n /**\n * An employment's position, worker type and standing on one date, replayed\n * from the change history. Before the first term the values are those at\n * hire and `employed` is false.\n *\n * @throws HrError `HR_NOT_FOUND` when the employment is not in this tenant\n */\n async asOf(employmentId: string, on: IsoDate): Promise<EmploymentAsOf> {\n const date = assertIsoDate('on', on);\n return this.scope(async () => {\n const employment = await this.load(this.db, employmentId);\n const id = employment.id as string;\n const history = await this.changesOf(this.db, id);\n const employed = (await this.termsOf(this.db, id)).some((term) =>\n dateWithin(date, term.startedOn, term.endedOn),\n );\n return {\n position: valueOn(\n history,\n 'position-changed',\n date,\n employment.position,\n ),\n workerType:\n valueOn(\n history,\n 'worker-type-changed',\n date,\n employment.workerType,\n ) ?? employment.workerType,\n employed,\n onLeave: employed && onLeaveOn(history, date),\n };\n });\n }\n}\n","import type { DatabaseInterface } from '@happyvertical/sql';\nimport { addDays, addMonths, assertIsoDate, dateWithin } from '../dates.js';\nimport {\n EmploymentCollection,\n EmploymentTermCollection,\n} from '../employment/models.js';\nimport { employmentsOn } from '../employment/queries.js';\nimport { type HrEventQueue, HrService } from '../service-base.js';\nimport {\n type HeldQualificationChangeKind,\n type HeldQualificationEventType,\n type HeldQualificationStatus,\n HrError,\n type IsoDate,\n type QualificationCheck,\n type QualificationKind,\n type QualificationScope,\n} from '../types.js';\nimport { insertHr, persistHr } from '../write.js';\nimport { EMPLOYMENT_ENDED_REASON } from './internal.js';\nimport {\n HeldQualification,\n HeldQualificationChange,\n HeldQualificationChangeCollection,\n HeldQualificationCollection,\n Qualification,\n QualificationCollection,\n} from './models.js';\n\n/** Input for {@link QualificationService.define} and {@link QualificationService.seed}. */\nexport interface DefineQualificationInput {\n /** Lowercase kebab-case, unique per tenant: `first-aid`. */\n key: string;\n /** Display name. */\n name: string;\n /** What the qualification is. */\n kind: QualificationKind;\n /** Who issues it; free text. */\n issuingBody?: string;\n /** Whether held qualifications of this kind expire. Defaults to false. */\n expires?: boolean;\n /** Default validity in months; only meaningful when `expires` is true. */\n validityMonths?: number | null;\n /** Defaults to `employment` for kind `authorization`, else `person`. */\n scope?: QualificationScope;\n}\n\n/** Input for {@link QualificationService.update}. Key and scope never change. */\nexport interface UpdateQualificationInput {\n /** New display name. */\n name?: string;\n /** New issuing body. */\n issuingBody?: string;\n /** Whether new grants expire; turning it off clears `validityMonths`. */\n expires?: boolean;\n /** New default validity in months, or null for none. */\n validityMonths?: number | null;\n /** Deactivate (or reactivate) the definition; inactive ones cannot be granted. */\n isActive?: boolean;\n}\n\n/** Options for {@link QualificationService.list}. */\nexport interface ListQualificationsOptions {\n /** Include deactivated definitions. Defaults to false. */\n includeInactive?: boolean;\n}\n\n/** Input for {@link QualificationService.grant}. */\nexport interface GrantQualificationInput {\n /** The definition being granted. */\n qualificationId: string;\n /** The person receiving it. */\n profileId: string;\n /** First day it is valid. */\n issuedOn: IsoDate;\n /** Last day it is valid (inclusive). Defaults from the definition's validity. */\n expiresOn?: IsoDate | null;\n /** Certificate or ticket number; free text. */\n certificateNumber?: string;\n /** Optional `smrt-assets:Asset` id of the scanned document. */\n documentAssetId?: string | null;\n /** Record the actor as having verified the document, now. */\n verified?: boolean;\n /** For an employment-scoped definition; resolved from the profile when omitted. */\n employmentId?: string | null;\n}\n\n/** Input for {@link QualificationService.renew}. */\nexport interface RenewQualificationInput {\n /** First day the renewal is valid. */\n issuedOn: IsoDate;\n /** Last day it is valid (inclusive). Defaults from the definition's validity. */\n expiresOn?: IsoDate | null;\n /** Certificate or ticket number of the renewal. */\n certificateNumber?: string;\n /** Optional `smrt-assets:Asset` id of the renewal's document. */\n documentAssetId?: string | null;\n /** Record the actor as having verified the document, now. */\n verified?: boolean;\n}\n\n/** Input for suspending or revoking a held qualification. */\nexport interface HeldQualificationReasonInput {\n /** The calendar date the change takes effect (that day included). */\n effectiveOn: IsoDate;\n /** Why; required. */\n reason: string;\n}\n\n/** Input for {@link QualificationService.reinstate}. */\nexport interface ReinstateQualificationInput {\n /** The calendar date the qualification is good again (that day included). */\n effectiveOn: IsoDate;\n /** Optional note. */\n reason?: string;\n}\n\n/** Input for {@link QualificationService.verify}. */\nexport interface VerifyQualificationInput {\n /**\n * `smrt-assets:Asset` id of the document to attach or replace; null removes\n * it, and omitting it keeps the current one.\n */\n documentAssetId?: string | null;\n}\n\n/** Options for {@link QualificationService.holders}. */\nexport interface QualificationHoldersOptions {\n /** Only people with an employment term covering the date. */\n employedOnly?: boolean;\n}\n\n/** Options for {@link QualificationService.sweepExpired}. */\nexport interface SweepExpiredOptions {\n /**\n * Only look at qualifications whose last day is on or after this date.\n * Bounds the work of a scheduled sweep; omit it to look at all history.\n */\n since?: IsoDate | null;\n}\n\n/** Options for {@link QualificationService.expiringWithin}. */\nexport interface ExpiringQualificationsOptions {\n /** Only people with an employment term covering `today`. */\n employedOnly?: boolean;\n}\n\n/** Status of a held qualification computed for one calendar date. */\nexport type HeldQualificationStatusOn =\n | HeldQualificationStatus\n | 'not-yet-issued';\n\n/** One row of {@link QualificationService.listForProfile}. */\nexport interface ProfileQualification {\n /** What the person holds. */\n held: HeldQualification;\n /** Its definition. */\n qualification: Qualification;\n /** Status computed for the requested date. */\n status: HeldQualificationStatusOn;\n}\n\n/**\n * A small, generic starter list an application may pass to\n * {@link QualificationService.seed}. Illustrative only: validity periods and\n * what a restriction means are the application's and its jurisdiction's call.\n */\nexport const SUGGESTED_QUALIFICATIONS: readonly DefineQualificationInput[] =\n Object.freeze([\n Object.freeze({\n key: 'first-aid',\n name: 'First aid',\n kind: 'certification',\n expires: true,\n validityMonths: 36,\n } as const),\n Object.freeze({\n key: 'site-orientation',\n name: 'Site orientation',\n kind: 'training',\n } as const),\n Object.freeze({\n key: 'youth-worker',\n name: 'Youth worker',\n kind: 'restriction',\n } as const),\n ]);\n\n/** The cutoff an already closed employment term puts on a qualification. */\nfunction employmentEnd(endsOn: IsoDate | null): Revocation | null {\n return endsOn === null\n ? null\n : { on: addDays(endsOn, 1), reason: EMPLOYMENT_ENDED_REASON };\n}\n\nconst KEY = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst KINDS: readonly QualificationKind[] = [\n 'ticket',\n 'certification',\n 'authorization',\n 'training',\n 'restriction',\n];\nconst SCOPES: readonly QualificationScope[] = ['person', 'employment'];\nconst IN_CHUNK = 500;\n\ntype Definition = Required<DefineQualificationInput>;\n\nfunction invalid(message: string): HrError {\n return new HrError('HR_INVALID', message);\n}\n\nfunction text(fieldName: string, value: unknown, required = false): string {\n if (value == null && !required) return '';\n if (typeof value !== 'string') throw invalid(`${fieldName} must be text.`);\n const trimmed = value.trim();\n if (required && !trimmed) throw invalid(`${fieldName} is required.`);\n return trimmed;\n}\n\nfunction flag(fieldName: string, value: unknown, fallback: boolean): boolean {\n if (value === undefined) return fallback;\n if (typeof value !== 'boolean')\n throw invalid(`${fieldName} must be true or false.`);\n return value;\n}\n\nfunction validity(expires: boolean, value: unknown): number | null {\n if (value == null) return null;\n if (!Number.isInteger(value) || (value as number) <= 0)\n throw invalid('validityMonths must be a positive whole number or null.');\n if (!expires)\n throw invalid(\n 'validityMonths only applies to a qualification that expires.',\n );\n return value as number;\n}\n\nfunction normalizeDefinition(input: DefineQualificationInput): Definition {\n const key = text('key', input?.key, true);\n if (!KEY.test(key))\n throw invalid(`key must be lowercase kebab-case, got '${key}'.`);\n if (!KINDS.includes(input.kind))\n throw invalid(`kind must be one of ${KINDS.join(', ')}.`);\n const scope =\n input.scope ?? (input.kind === 'authorization' ? 'employment' : 'person');\n if (!SCOPES.includes(scope))\n throw invalid(`scope must be one of ${SCOPES.join(', ')}.`);\n const expires = flag('expires', input.expires, false);\n return {\n key,\n name: text('name', input.name, true),\n kind: input.kind,\n issuingBody: text('issuingBody', input.issuingBody),\n expires,\n validityMonths: validity(expires, input.validityMonths),\n scope,\n };\n}\n\n/** The last day a grant is valid, from the input or the definition's validity. */\nfunction resolveExpiry(\n definition: Qualification,\n issuedOn: IsoDate,\n given: IsoDate | null | undefined,\n): IsoDate | null {\n if (!definition.expires) {\n if (given != null)\n throw invalid(`'${definition.key}' does not expire; omit expiresOn.`);\n return null;\n }\n let expiresOn: IsoDate;\n if (given != null) expiresOn = assertIsoDate('expiresOn', given);\n else if (definition.validityMonths != null)\n expiresOn = addMonths(issuedOn, definition.validityMonths);\n else\n throw invalid(\n `'${definition.key}' expires and has no default validity; give expiresOn.`,\n );\n if (expiresOn < issuedOn)\n throw invalid('expiresOn must not be before issuedOn.');\n return expiresOn;\n}\n\n/**\n * Whether a row is revoked or suspended on a date, replayed from its dated\n * changes. Does not depend on the order of same-day changes: revocation is\n * terminal, and suspensions and reinstatements alternate.\n */\nfunction standingOn(\n changes: readonly HeldQualificationChange[],\n on: IsoDate,\n): 'revoked' | 'suspended' | null {\n let suspensions = 0;\n for (const change of changes) {\n if (change.effectiveOn > on) continue;\n if (change.kind === 'revoked') return 'revoked';\n if (change.kind === 'suspended') suspensions += 1;\n else if (change.kind === 'reinstated') suspensions -= 1;\n }\n return suspensions > 0 ? 'suspended' : null;\n}\n\n/**\n * Status of one row on a date, from its own dates and changes. Never trusts\n * the stored status.\n */\nfunction statusOn(\n row: HeldQualification,\n changes: readonly HeldQualificationChange[],\n on: IsoDate,\n): HeldQualificationStatusOn {\n if (row.issuedOn > on) return 'not-yet-issued';\n const standing = standingOn(changes, on);\n if (standing) return standing;\n if (row.expiresOn != null && on > row.expiresOn) return 'expired';\n return 'valid';\n}\n\n/**\n * Status of every row on a date, with a renewal chain sharing its standing:\n * a row that would be valid is revoked or suspended when a later row in its\n * chain (a renewal of it, or of that renewal) is revoked or suspended on the\n * date. Without this, revoking a renewal would be bypassed by the unexpired\n * row it renewed. `rows` must hold whole chains.\n */\nfunction chainStatusesOn(\n rows: readonly HeldQualification[],\n changes: ReadonlyMap<string, readonly HeldQualificationChange[]>,\n on: IsoDate,\n): Map<string, HeldQualificationStatusOn> {\n const renewalOf = new Map<string, HeldQualification>();\n for (const row of rows)\n if (row.renewalOfId) renewalOf.set(row.renewalOfId, row);\n const statuses = new Map<string, HeldQualificationStatusOn>();\n for (const row of rows) {\n const id = row.id as string;\n let status = statusOn(row, changes.get(id) ?? [], on);\n const seen = new Set([id]);\n let later = renewalOf.get(id);\n while (status !== 'revoked' && later && !seen.has(later.id as string)) {\n seen.add(later.id as string);\n const standing = standingOn(changes.get(later.id as string) ?? [], on);\n if (standing && (status === 'valid' || status === 'suspended'))\n status = standing;\n later = renewalOf.get(later.id as string);\n }\n statuses.set(id, status);\n }\n return statuses;\n}\n\n/**\n * Changes that suspend or lift a suspension. They are recorded in date order;\n * a revocation is terminal and may be recorded for any date.\n */\nconst SUSPENSION_KINDS: readonly HeldQualificationChangeKind[] = [\n 'suspended',\n 'reinstated',\n];\n\n/** A revocation: the day it takes effect and why. */\ninterface Revocation {\n /** First day revoked. */\n on: IsoDate;\n /** The reason recorded with it. */\n reason: string;\n}\n\n/** The revocation in force: the earliest one recorded, or null. */\nfunction revocationOf(\n changes: readonly HeldQualificationChange[],\n): Revocation | null {\n let earliest: HeldQualificationChange | null = null;\n for (const change of changes)\n if (\n change.kind === 'revoked' &&\n (earliest === null || change.effectiveOn < earliest.effectiveOn)\n )\n earliest = change;\n return earliest && { on: earliest.effectiveOn, reason: earliest.reason };\n}\n\n/** The date a revocation takes effect: the earliest one recorded, or null. */\nfunction revokedFrom(\n changes: readonly HeldQualificationChange[],\n): IsoDate | null {\n return revocationOf(changes)?.on ?? null;\n}\n\n/** The latest effective date among changes of the given kinds, or null. */\nfunction latestOf(\n changes: readonly HeldQualificationChange[],\n kinds: readonly HeldQualificationChangeKind[],\n): IsoDate | null {\n let latest: IsoDate | null = null;\n for (const change of changes)\n if (\n kinds.includes(change.kind) &&\n (latest === null || change.effectiveOn > latest)\n )\n latest = change.effectiveOn;\n return latest;\n}\n\nfunction createdTime(row: { created_at?: Date | string | null }): number {\n const time = row.created_at ? new Date(row.created_at).getTime() : 0;\n return Number.isNaN(time) ? 0 : time;\n}\n\n/** Among rows that are good on a date, the one that lasts longest. */\nfunction longestLasting(rows: readonly HeldQualification[]): HeldQualification {\n return rows.reduce((best, row) => {\n if (best.expiresOn === row.expiresOn)\n return row.issuedOn > best.issuedOn ? row : best;\n if (best.expiresOn == null) return best;\n if (row.expiresOn == null) return row;\n return row.expiresOn > best.expiresOn ? row : best;\n });\n}\n\n/**\n * The only writer of qualification definitions and held qualifications, and\n * the gate ({@link QualificationService.check}) applications call before\n * letting a person do qualified work. Constructed from a trusted\n * tenant/profile actor the application has already authorized.\n *\n * Whether a qualification is good on a date is computed from `issuedOn`,\n * `expiresOn` and the dated change history, so past dates keep answering\n * correctly and expiry needs no scheduler. The stored status records the\n * last change made (a revocation or suspension stores at once, even when it\n * takes effect later), so every rule that asks about standing replays the\n * dated history instead, including the rules that decide whether a\n * qualification may be suspended, reinstated, revoked or renewed. A stored\n * `revoked` is final: later changes to such a row are change rows only. The\n * stored `expired` status is written by\n * {@link QualificationService.sweepExpired}, and by a reinstatement of a row\n * whose lapse the sweep has recorded.\n */\nexport class QualificationService extends HrService {\n // ── Definitions ──────────────────────────────────────────────────────────\n\n /** Add a qualification definition; its key is unique per tenant. */\n async define(input: DefineQualificationInput): Promise<Qualification> {\n const definition = normalizeDefinition(input);\n const id = await this.transact(async (db) => {\n if (await this.byKey(db, definition.key))\n throw new HrError(\n 'HR_QUALIFICATION_KEY_TAKEN',\n `A qualification with key '${definition.key}' already exists.`,\n );\n return (await this.insertDefinition(db, definition)).id as string;\n });\n return this.get(id);\n }\n\n /** Change a definition's descriptive fields; key and scope are immutable. */\n async update(\n qualificationId: string,\n input: UpdateQualificationInput,\n ): Promise<Qualification> {\n const id = this.id('qualificationId', qualificationId);\n if (input == null || typeof input !== 'object')\n throw invalid('update needs the fields to change.');\n await this.transact(async (db) => {\n const row = await this.definition(db, id);\n if (input.name !== undefined) row.name = text('name', input.name, true);\n if (input.issuingBody !== undefined)\n row.issuingBody = text('issuingBody', input.issuingBody);\n row.expires = flag('expires', input.expires, row.expires);\n row.isActive = flag('isActive', input.isActive, row.isActive);\n if (input.validityMonths !== undefined)\n row.validityMonths = validity(row.expires, input.validityMonths);\n else if (!row.expires) row.validityMonths = null;\n await persistHr(row);\n });\n return this.get(id);\n }\n\n /**\n * Create the definitions whose key is missing and leave existing rows\n * untouched. Returns one definition per input, in input order.\n */\n async seed(\n definitions: readonly DefineQualificationInput[],\n ): Promise<Qualification[]> {\n const wanted = definitions.map(normalizeDefinition);\n if (new Set(wanted.map((d) => d.key)).size !== wanted.length)\n throw invalid('Seed definitions must have distinct keys.');\n const ids = await this.transact(async (db) => {\n const result: string[] = [];\n for (const definition of wanted) {\n const row =\n (await this.byKey(db, definition.key)) ??\n (await this.insertDefinition(db, definition));\n result.push(row.id as string);\n }\n return result;\n });\n return this.scope(async () => {\n const collection = await QualificationCollection.create({ db: this.db });\n const rows = await collection.list({\n where: { tenantId: this.actor.tenantId },\n });\n const byId = new Map(rows.map((row) => [row.id as string, row]));\n return ids.map((id) => byId.get(id) as Qualification);\n });\n }\n\n /** The tenant's definitions ordered by key; active ones unless asked. */\n async list(\n options: ListQualificationsOptions = {},\n ): Promise<Qualification[]> {\n return this.scope(async () => {\n const collection = await QualificationCollection.create({ db: this.db });\n const rows = await collection.list({\n where: { tenantId: this.actor.tenantId },\n orderBy: 'key ASC',\n });\n return options.includeInactive ? rows : rows.filter((r) => r.isActive);\n });\n }\n\n /**\n * One definition by id.\n *\n * @throws HrError `HR_NOT_FOUND` outside the actor's tenant, `HR_INVALID` for a blank id\n */\n async get(qualificationId: string): Promise<Qualification> {\n const id = this.id('qualificationId', qualificationId);\n return this.scope(() => this.definition(this.db, id));\n }\n\n /** One definition by key, or null. */\n async findByKey(key: string): Promise<Qualification | null> {\n if (typeof key !== 'string' || !key.trim()) return null;\n return this.scope(() => this.byKey(this.db, key.trim()));\n }\n\n // ── Held qualifications ──────────────────────────────────────────────────\n\n /**\n * Record that a person holds a qualification from `issuedOn`.\n *\n * A person has one live grant of a qualification at a time, decided by\n * date: the grant is refused unless everything they already hold of it is\n * revoked effective on or before `issuedOn`. A lapsed ticket (swept or not)\n * is renewed, not granted again, and a revocation scheduled for a later\n * date does not free an earlier issue date.\n *\n * An employment-scoped grant dated inside a term whose end is already\n * recorded ends with that employment: the `employment-ended` revocation,\n * effective the day after the term's last day, is recorded in the same\n * transaction and the row is stored `revoked`.\n *\n * @throws HrError `HR_ALREADY_HELD` unless every earlier grant is revoked effective on or before `issuedOn` (renew the existing one instead), `HR_QUALIFICATION_SCOPE`, `HR_INVALID`, `HR_NOT_FOUND`\n */\n async grant(input: GrantQualificationInput): Promise<HeldQualification> {\n const qualificationId = this.id('qualificationId', input?.qualificationId);\n const profileId = this.id('profileId', input.profileId);\n const issuedOn = assertIsoDate('issuedOn', input.issuedOn);\n const givenEmploymentId = this.optionalId(\n 'employmentId',\n input.employmentId,\n );\n const id = await this.transact(async (db, queue) => {\n const definition = await this.grantable(db, qualificationId);\n const expiresOn = resolveExpiry(definition, issuedOn, input.expiresOn);\n const { employmentId, endsOn } = await this.employmentFor(\n db,\n definition,\n profileId,\n givenEmploymentId,\n issuedOn,\n );\n const rows = await this.rows(db, { profileId, qualificationId });\n const [live] = await this.liveChains(db, rows, issuedOn, null);\n if (live)\n throw new HrError(\n 'HR_ALREADY_HELD',\n live.revokedOn\n ? `This person holds '${definition.key}' until its revocation takes effect on ${live.revokedOn}; renew the existing one instead.`\n : `This person already holds '${definition.key}'; renew it instead.`,\n );\n const row = await this.insertHeld(db, queue, 'granted', {\n qualificationId,\n profileId,\n employmentId,\n revocation: employmentEnd(endsOn),\n issuedOn,\n expiresOn,\n renewalOfId: null,\n input,\n });\n return row.id as string;\n });\n return this.held(this.db, id);\n }\n\n /**\n * Renew a held qualification: a new row pointing at the old one. The old row\n * is not modified, so checks on past dates still answer from it. Only the\n * latest row of a chain can be renewed.\n *\n * The renewal is validated against the old row's dated history, not its\n * stored status: `issuedOn` cannot be earlier than the row's latest\n * suspension or reinstatement (the renewal would otherwise answer for dates\n * the history says were suspended), the row must not be suspended on\n * `issuedOn`, and it must not be revoked on or before `issuedOn`.\n *\n * A revocation recorded for a later date (a scheduled one, or the end of an\n * employment whose last day is ahead) does not stop a renewal: the\n * qualification is still in force, so a ticket that lapses first can be\n * renewed. The renewal carries the cutoff forward. It is stored `revoked`\n * with a revocation at the earlier of the old row's pending revocation\n * (with its reason) and, for an employment-scoped one inside a term whose\n * end is recorded, the day after the last day employed\n * (`employment-ended`); `held-qualification.renewed` and\n * `held-qualification.revoked` are both raised.\n *\n * @throws HrError `HR_STATUS_TRANSITION` when suspended on `issuedOn` (reinstate it first); `HR_ALREADY_HELD` when the person holds another grant outside this chain that is not revoked on or before `issuedOn` and starts before the day this renewal is cut off; `HR_INVALID` for a row revoked on or before `issuedOn`, an already renewed row, an `issuedOn` before the row's own or before its latest suspension or reinstatement, or an expiry earlier than the row's own (revoke it and grant a new one instead); `HR_NOT_FOUND`\n */\n async renew(\n heldQualificationId: string,\n input: RenewQualificationInput,\n ): Promise<HeldQualification> {\n const priorId = this.id('heldQualificationId', heldQualificationId);\n const issuedOn = assertIsoDate('issuedOn', input?.issuedOn);\n const id = await this.transact(async (db, queue) => {\n const prior = await this.held(db, priorId);\n const history =\n (await this.changesByHeld(db, [prior])).get(priorId) ?? [];\n // Revocation is terminal from the day it takes effect; until then the\n // qualification is in force and can be renewed. A stored `revoked` with\n // no dated revocation (a row written outside the service) has no day to\n // go by and is treated as revoked throughout.\n const pending = revocationOf(history);\n if (\n (pending !== null && pending.on <= issuedOn) ||\n (pending === null && prior.status === 'revoked')\n )\n throw invalid(\n 'A qualification revoked on or before the renewal date cannot be renewed; grant a new one.',\n );\n const siblings = await this.rows(db, {\n profileId: prior.profileId,\n qualificationId: prior.qualificationId,\n });\n if (siblings.some((row) => row.renewalOfId === priorId))\n throw invalid(\n 'This qualification was already renewed; renew the latest one.',\n );\n if (issuedOn < prior.issuedOn)\n throw invalid('A renewal cannot be issued before what it renews.');\n // A renewal starts out in good standing from its issue date, so it must\n // not reach back over a suspension the history records.\n const changedOn = latestOf(history, SUSPENSION_KINDS);\n if (changedOn !== null && issuedOn < changedOn)\n throw invalid(\n `A renewal cannot be issued before the latest suspension or reinstatement on ${changedOn}.`,\n );\n if (standingOn(history, issuedOn) === 'suspended')\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n 'A suspended qualification cannot be renewed; reinstate it first.',\n );\n const definition = await this.grantable(db, prior.qualificationId);\n const expiresOn = resolveExpiry(definition, issuedOn, input.expiresOn);\n // The row being renewed keeps answering for its own period, so a\n // renewal that ended sooner would lapse while the person still held the\n // qualification through the old row.\n if (\n expiresOn !== null &&\n (prior.expiresOn === null || expiresOn < prior.expiresOn)\n )\n throw invalid(\n `A renewal cannot end before what it renews (${prior.expiresOn ?? 'no expiry'}). To replace it with a shorter one, revoke it from ${issuedOn} and grant a new one from that day.`,\n );\n const { employmentId, endsOn } = await this.employmentFor(\n db,\n definition,\n prior.profileId,\n prior.employmentId,\n issuedOn,\n );\n // The earlier cutoff wins; on the same day the employment end is the\n // reason kept.\n const ended = employmentEnd(endsOn);\n const revocation =\n ended !== null && (pending === null || ended.on <= pending.on)\n ? ended\n : pending;\n // Earlier rows of this chain are all renewed, so any other latest row\n // belongs to a different chain. One that only starts on or after the\n // day this renewal is cut off never overlaps it, whichever of the two\n // was recorded first.\n const byId = new Map(siblings.map((row) => [row.id as string, row]));\n const startsOn = (row: HeldQualification): IsoDate => {\n let first = row.issuedOn;\n const seen = new Set<string>();\n for (\n let member: HeldQualification | undefined = row;\n member && !seen.has(member.id as string);\n member = member.renewalOfId ? byId.get(member.renewalOfId) : undefined\n ) {\n seen.add(member.id as string);\n if (member.issuedOn < first) first = member.issuedOn;\n }\n return first;\n };\n const overlapping = (\n await this.liveChains(db, siblings, issuedOn, priorId)\n ).filter(\n (other) => revocation === null || startsOn(other.row) < revocation.on,\n );\n if (overlapping.length > 0)\n throw new HrError(\n 'HR_ALREADY_HELD',\n 'This person holds another grant of this qualification outside this renewal chain that overlaps the renewal; renew that one, or revoke it first.',\n );\n const row = await this.insertHeld(db, queue, 'renewed', {\n qualificationId: prior.qualificationId,\n profileId: prior.profileId,\n employmentId,\n revocation,\n issuedOn,\n expiresOn,\n renewalOfId: priorId,\n input,\n });\n return row.id as string;\n });\n return this.held(this.db, id);\n }\n\n /**\n * Suspend a qualification from `effectiveOn` until it is reinstated. Act on\n * the latest row of a renewal chain: the whole chain shares its standing,\n * so the rows it renewed stop passing checks too.\n *\n * Whether it can be suspended is decided from the chain's dated history as\n * of `effectiveOn`, never from the stored status: it must be neither\n * suspended nor revoked on that day. A revocation recorded for a later date\n * (scheduled, or the end of an employment whose last day is ahead) does not\n * prevent it, and neither does a stored `expired`.\n *\n * `effectiveOn` may be any date from the chain's first issue date onward,\n * not before the chain's latest suspension or reinstatement. It may precede\n * the latest row's own issue date: with a renewal recorded ahead of time,\n * that is how the ticket in force today is suspended, and the renewal is\n * then suspended from its issue date until reinstated.\n *\n * The stored status becomes `suspended`, except on a row already stored\n * `revoked`, which keeps it: only the change row is added.\n *\n * @throws HrError `HR_INVALID` for a row that was renewed, a date outside that range or a bad input; `HR_STATUS_TRANSITION` when suspended or revoked on `effectiveOn`; `HR_NOT_FOUND`\n */\n async suspend(\n heldQualificationId: string,\n input: HeldQualificationReasonInput,\n ): Promise<HeldQualification> {\n return this.transition(heldQualificationId, 'suspended', {\n effectiveOn: input?.effectiveOn,\n reason: text('reason', input?.reason, true),\n });\n }\n\n /**\n * Lift a suspension from `effectiveOn`, which follows the same date rule as\n * {@link suspend}. The chain must be suspended on that day by its dated\n * history, and not yet revoked. The stored status becomes `valid`, or\n * `expired` when the sweep has recorded the row's lapse; a stored `revoked`\n * stays.\n *\n * @throws HrError `HR_INVALID` for a row that was renewed, a date before the suspension or a bad input; `HR_STATUS_TRANSITION` unless suspended on `effectiveOn`; `HR_NOT_FOUND`\n */\n async reinstate(\n heldQualificationId: string,\n input: ReinstateQualificationInput,\n ): Promise<HeldQualification> {\n return this.transition(heldQualificationId, 'reinstated', {\n effectiveOn: input?.effectiveOn,\n reason: text('reason', input?.reason),\n });\n }\n\n /**\n * Revoke a qualification from `effectiveOn`. Terminal: grant a new one\n * dated on or after it. Act on the latest row of a renewal chain: the rows\n * it renewed stop passing checks from the same date; dated before a renewal\n * recorded ahead of time, the renewal is revoked from its issue date.\n *\n * `effectiveOn` may be any date from the chain's first issue date onward,\n * whatever else is recorded: an urgent revocation may be dated before a\n * suspension recorded ahead of time, or before a revocation that has not\n * taken effect yet (a scheduled one, or the end of an employment whose last\n * day is ahead). The earliest revocation is the one in force. It is refused\n * only when the chain is already revoked on or before `effectiveOn`.\n *\n * The stored status becomes `revoked` at once, meaning a revocation has\n * been recorded; until `effectiveOn` the qualification still passes\n * {@link check}. A row already stored `revoked` is not saved again: the\n * earlier revocation is one more change row, and the event carries its\n * date.\n *\n * @throws HrError `HR_INVALID` for a row that was renewed, a date before the chain was issued or a bad input; `HR_STATUS_TRANSITION` when already revoked on or before `effectiveOn`; `HR_NOT_FOUND`\n */\n async revoke(\n heldQualificationId: string,\n input: HeldQualificationReasonInput,\n ): Promise<HeldQualification> {\n return this.transition(heldQualificationId, 'revoked', {\n effectiveOn: input?.effectiveOn,\n reason: text('reason', input?.reason, true),\n });\n }\n\n /**\n * Record that the actor has verified a held qualification's document, now.\n * Optionally attaches, replaces or removes the document. The status and the\n * dated history do not change, and no event is raised. Verification is not\n * a status, so it works whatever the standing, including on a row stored\n * `revoked` (which may still be in force until its revocation takes effect).\n *\n * @throws HrError `HR_INVALID`, `HR_NOT_FOUND`\n */\n async verify(\n heldQualificationId: string,\n input: VerifyQualificationInput = {},\n ): Promise<HeldQualification> {\n const id = this.id('heldQualificationId', heldQualificationId);\n if (input == null || typeof input !== 'object')\n throw invalid('verify takes an options object.');\n const documentAssetId =\n input.documentAssetId === undefined\n ? undefined\n : this.optionalId('documentAssetId', input.documentAssetId);\n await this.transact(async (db) => {\n const row = await this.held(db, id);\n if (documentAssetId !== undefined) row.documentAssetId = documentAssetId;\n row.verifiedByProfileId = this.actor.profileId;\n row.verifiedAt = new Date();\n await persistHr(row);\n });\n return this.held(this.db, id);\n }\n\n /**\n * Record the lapse of every qualification whose last day is before `today`\n * and that was still in force on it: an `expired` change dated the day\n * after `expiresOn`. The application calls this from its own scheduler;\n * checks never depend on it. Idempotent: a row is recorded once.\n *\n * Which rows lapsed is decided by date, not by the stored status, so a\n * ticket that runs out while a suspension is in force, or while a\n * revocation is recorded for a later day (a notice period), is recorded\n * too. A row revoked on or before the day after `expiresOn` did not lapse:\n * it was revoked. The stored status becomes `expired` only on a row stored\n * `valid`; a stored `suspended` or `revoked` stays as it is.\n *\n * Every run re-reads the lapsed rows stored `suspended` or `revoked`, a set\n * that only grows with a tenant's history. Pass `since` (a day on or before\n * the last day the previous run covered) to look only at rows whose last\n * day is on or after it.\n *\n * Emits `held-qualification.expired`, and returns the row, only for\n * qualifications that were not renewed.\n *\n * @throws HrError `HR_INVALID` for a bad `today` or `since`\n */\n async sweepExpired(\n today: IsoDate,\n options: SweepExpiredOptions = {},\n ): Promise<HeldQualification[]> {\n assertIsoDate('today', today);\n const since =\n options?.since == null ? null : assertIsoDate('since', options.since);\n const ids = await this.transact(async (db, queue) => {\n const candidates = (\n await this.rows(db, {\n status: ['valid', 'suspended', 'revoked'],\n 'expiresOn <': today,\n ...(since === null ? {} : { 'expiresOn >=': since }),\n })\n ).filter((row) => row.expiresOn != null);\n const changes = await this.changesByHeld(db, candidates);\n const due = candidates.filter((row) => {\n const history = changes.get(row.id as string) ?? [];\n if (history.some((change) => change.kind === 'expired')) return false;\n const revokedOn = revokedFrom(history);\n return (\n revokedOn === null || revokedOn > addDays(row.expiresOn as IsoDate, 1)\n );\n });\n const superseded = await this.supersededIds(db, due);\n const emitted: string[] = [];\n for (const row of due) {\n const effectiveOn = addDays(row.expiresOn as IsoDate, 1);\n if (row.status === 'valid') {\n row.status = 'expired';\n await persistHr(row);\n }\n await this.recordChange(db, row, 'expired', effectiveOn, '');\n if (superseded.has(row.id as string)) continue;\n this.announce(queue, 'held-qualification.expired', row, effectiveOn);\n emitted.push(row.id as string);\n }\n return emitted;\n });\n return this.scope(async () => {\n const rows = await this.rowsById(this.db, ids);\n return rows.sort(\n (a, b) =>\n (a.expiresOn ?? '').localeCompare(b.expiresOn ?? '') ||\n String(a.id).localeCompare(String(b.id)),\n );\n });\n }\n\n // ── Queries ──────────────────────────────────────────────────────────────\n\n /**\n * Whether a person holds a qualification on a calendar date. Computed from\n * issue and expiry dates and the dated change history, never from the stored\n * status alone, so it is correct for past dates and without the sweep. A\n * renewal chain shares its standing: while a renewal is revoked or\n * suspended, the row it renewed does not pass either. A blank profile or\n * qualification id is `not-held`.\n */\n async check(\n profileId: string,\n qualificationId: string,\n on: IsoDate,\n ): Promise<QualificationCheck> {\n assertIsoDate('on', on);\n const blank = (value: unknown) =>\n typeof value !== 'string' || !value.trim();\n if (blank(profileId) || blank(qualificationId))\n return { ok: false, reason: 'not-held' };\n const where = {\n profileId: this.id('profileId', profileId),\n qualificationId: this.id('qualificationId', qualificationId),\n };\n return this.scope(async () => {\n const rows = await this.rows(this.db, where);\n if (rows.length === 0) return { ok: false, reason: 'not-held' };\n const statuses = chainStatusesOn(\n rows,\n await this.changesByHeld(this.db, rows),\n on,\n );\n const good = rows.filter(\n (row) => statuses.get(row.id as string) === 'valid',\n );\n if (good.length > 0) {\n const best = longestLasting(good);\n return {\n ok: true,\n heldQualificationId: best.id as string,\n expiresOn: best.expiresOn,\n };\n }\n const issued = rows.filter((row) => row.issuedOn <= on);\n if (issued.length === 0) return { ok: false, reason: 'not-yet-issued' };\n const latest = issued.reduce((a, b) =>\n b.issuedOn > a.issuedOn ||\n (b.issuedOn === a.issuedOn && createdTime(b) >= createdTime(a))\n ? b\n : a,\n );\n return {\n ok: false,\n reason: statuses.get(latest.id as string) as\n | 'expired'\n | 'suspended'\n | 'revoked',\n };\n });\n }\n\n /**\n * One held qualification per person who passes {@link check} on `on`,\n * ordered by profile id.\n */\n async holders(\n qualificationId: string,\n on: IsoDate,\n options: QualificationHoldersOptions = {},\n ): Promise<HeldQualification[]> {\n assertIsoDate('on', on);\n const id = this.id('qualificationId', qualificationId);\n return this.scope(async () => {\n const rows = await this.rows(this.db, { qualificationId: id });\n const statuses = chainStatusesOn(\n rows,\n await this.changesByHeld(this.db, rows),\n on,\n );\n const employed = options?.employedOnly\n ? new Set(\n (await employmentsOn(this.db, this.actor.tenantId, on)).map(\n (employment) => employment.profileId,\n ),\n )\n : null;\n const good = new Map<string, HeldQualification[]>();\n for (const row of rows) {\n if (employed && !employed.has(row.profileId)) continue;\n if (statuses.get(row.id as string) !== 'valid') continue;\n good.set(row.profileId, [...(good.get(row.profileId) ?? []), row]);\n }\n return [...good.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([, held]) => longestLasting(held));\n });\n }\n\n /**\n * Qualifications good on `today` (issued, and not suspended or revoked by\n * the dated history, exactly as {@link check} decides) whose last day falls\n * in `[today, today + days]`, soonest first. The stored status plays no\n * part. A qualification that was already renewed is left out, and\n * `employedOnly` keeps only people with an employment term covering `today`.\n */\n async expiringWithin(\n days: number,\n today: IsoDate,\n options: ExpiringQualificationsOptions = {},\n ): Promise<HeldQualification[]> {\n assertIsoDate('today', today);\n if (!Number.isInteger(days) || days < 0)\n throw invalid('days must be a whole number, zero or more.');\n return this.scope(async () => {\n const rows = await this.rows(\n this.db,\n {\n 'issuedOn <=': today,\n 'expiresOn >=': today,\n 'expiresOn <=': addDays(today, days),\n },\n 'expiresOn ASC',\n );\n // A row that was renewed is left out, so each remaining row is the\n // latest of its chain and its own history is its chain's standing.\n const superseded = await this.supersededIds(this.db, rows);\n const changes = await this.changesByHeld(this.db, rows);\n const employed = options?.employedOnly\n ? new Set(\n (await employmentsOn(this.db, this.actor.tenantId, today)).map(\n (employment) => employment.profileId,\n ),\n )\n : null;\n return rows.filter(\n (row) =>\n !superseded.has(row.id as string) &&\n statusOn(row, changes.get(row.id as string) ?? [], today) ===\n 'valid' &&\n (!employed || employed.has(row.profileId)),\n );\n });\n }\n\n /**\n * Everything a person holds (renewed rows are replaced by their renewal),\n * each with its definition and its status computed for `on`. The status\n * agrees with {@link check}: it is `valid` when the row, or a row it renews,\n * is good on `on`, and otherwise the status of the latest row of the chain\n * issued by `on`. Ordered by qualification key, then issue date.\n *\n * @throws HrError `HR_INVALID` for a blank profile id or a bad date\n */\n async listForProfile(\n profileId: string,\n on: IsoDate,\n ): Promise<ProfileQualification[]> {\n assertIsoDate('on', on);\n const id = this.id('profileId', profileId);\n return this.scope(async () => {\n const rows = await this.rows(this.db, { profileId: id });\n const superseded = await this.supersededIds(this.db, rows);\n const statuses = chainStatusesOn(\n rows,\n await this.changesByHeld(this.db, rows),\n on,\n );\n const byRowId = new Map(rows.map((row) => [row.id as string, row]));\n // The latest row speaks for its chain: when it is not good on the date\n // but a row it renews is, the person still holds the qualification.\n // Otherwise the latest row already issued answers, as in check(): a\n // renewal recorded ahead of time does not hide a suspended or revoked\n // ticket behind `not-yet-issued`.\n const chainStatus = (\n held: HeldQualification,\n ): HeldQualificationStatusOn => {\n let answer: HeldQualificationStatusOn = 'not-yet-issued';\n const seen = new Set<string>();\n let earlier: HeldQualification | undefined = held;\n while (earlier && !seen.has(earlier.id as string)) {\n seen.add(earlier.id as string);\n const status = statuses.get(\n earlier.id as string,\n ) as HeldQualificationStatusOn;\n if (status === 'valid') return 'valid';\n if (answer === 'not-yet-issued') answer = status;\n earlier = earlier.renewalOfId\n ? byRowId.get(earlier.renewalOfId)\n : undefined;\n }\n return answer;\n };\n const definitions = await QualificationCollection.create({ db: this.db });\n const byId = new Map(\n (\n await definitions.list({ where: { tenantId: this.actor.tenantId } })\n ).map((definition) => [definition.id as string, definition]),\n );\n return rows\n .filter((row) => !superseded.has(row.id as string))\n .map((held) => ({\n held,\n qualification: byId.get(held.qualificationId) as Qualification,\n status: chainStatus(held),\n }))\n .sort(\n (a, b) =>\n a.qualification.key.localeCompare(b.qualification.key) ||\n a.held.issuedOn.localeCompare(b.held.issuedOn) ||\n createdTime(a.held) - createdTime(b.held),\n );\n });\n }\n\n /**\n * The dated changes of one held qualification, oldest first.\n *\n * @throws HrError `HR_NOT_FOUND` outside the actor's tenant, `HR_INVALID` for a blank id\n */\n async history(\n heldQualificationId: string,\n ): Promise<HeldQualificationChange[]> {\n const id = this.id('heldQualificationId', heldQualificationId);\n return this.scope(async () => {\n const row = await this.held(this.db, id);\n const changes = await this.changesByHeld(this.db, [row]);\n return changes.get(id) ?? [];\n });\n }\n\n // ── Internals ────────────────────────────────────────────────────────────\n\n private async byKey(\n db: DatabaseInterface,\n key: string,\n ): Promise<Qualification | null> {\n const collection = await QualificationCollection.create({ db });\n const [row] = await collection.list({\n where: { tenantId: this.actor.tenantId, key },\n limit: 1,\n });\n return row ?? null;\n }\n\n private async definition(\n db: DatabaseInterface,\n id: string,\n ): Promise<Qualification> {\n const collection = await QualificationCollection.create({ db });\n const [row] = await collection.list({\n where: { tenantId: this.actor.tenantId, id },\n limit: 1,\n });\n if (!row) throw new HrError('HR_NOT_FOUND', 'Qualification not found.');\n return row;\n }\n\n private async grantable(\n db: DatabaseInterface,\n id: string,\n ): Promise<Qualification> {\n const definition = await this.definition(db, id);\n if (!definition.isActive)\n throw invalid(`'${definition.key}' is inactive and cannot be granted.`);\n return definition;\n }\n\n private insertDefinition(\n db: DatabaseInterface,\n definition: Definition,\n ): Promise<Qualification> {\n return insertHr(Qualification, db, {\n tenantId: this.actor.tenantId,\n ...definition,\n });\n }\n\n private async held(\n db: DatabaseInterface,\n id: string,\n ): Promise<HeldQualification> {\n const [row] = await this.scope(() => this.rows(db, { id }));\n if (!row)\n throw new HrError('HR_NOT_FOUND', 'Held qualification not found.');\n return row;\n }\n\n /** Held rows in the actor's tenant matching `where`. */\n private async rows(\n db: DatabaseInterface,\n where: Record<string, unknown>,\n orderBy?: string,\n ): Promise<HeldQualification[]> {\n const collection = await HeldQualificationCollection.create({ db });\n return collection.list({\n where: { tenantId: this.actor.tenantId, ...where },\n orderBy,\n });\n }\n\n private async rowsById(\n db: DatabaseInterface,\n ids: readonly string[],\n ): Promise<HeldQualification[]> {\n const rows: HeldQualification[] = [];\n for (let at = 0; at < ids.length; at += IN_CHUNK)\n rows.push(...(await this.rows(db, { id: ids.slice(at, at + IN_CHUNK) })));\n return rows;\n }\n\n /**\n * The chains among `rows` (everything one person holds of one\n * qualification) that are not revoked effective on or before `on`, each\n * represented by its latest row and the date its revocation takes effect,\n * if one is recorded. The chain whose latest row is `exceptId` is skipped.\n * Decided from the dated history: a revocation stored now but effective\n * later still counts as held.\n */\n private async liveChains(\n db: DatabaseInterface,\n rows: readonly HeldQualification[],\n on: IsoDate,\n exceptId: string | null,\n ): Promise<{ row: HeldQualification; revokedOn: IsoDate | null }[]> {\n const renewed = new Set(rows.map((row) => row.renewalOfId));\n const latest = rows.filter(\n (row) => !renewed.has(row.id as string) && row.id !== exceptId,\n );\n const changes = await this.changesByHeld(db, latest);\n return latest\n .map((row) => ({\n row,\n revokedOn: revokedFrom(changes.get(row.id as string) ?? []),\n }))\n .filter(({ revokedOn }) => revokedOn === null || revokedOn > on);\n }\n\n /** Ids, among `rows`, that a later row renews. */\n private async supersededIds(\n db: DatabaseInterface,\n rows: readonly HeldQualification[],\n ): Promise<Set<string>> {\n const ids = rows.map((row) => row.id as string);\n const superseded = new Set<string>();\n for (let at = 0; at < ids.length; at += IN_CHUNK)\n for (const renewal of await this.rows(db, {\n renewalOfId: ids.slice(at, at + IN_CHUNK),\n }))\n if (renewal.renewalOfId) superseded.add(renewal.renewalOfId);\n return superseded;\n }\n\n /** Change rows per held qualification, oldest first. */\n private async changesByHeld(\n db: DatabaseInterface,\n rows: readonly HeldQualification[],\n ): Promise<Map<string, HeldQualificationChange[]>> {\n const ids = rows.map((row) => row.id as string);\n const collection = await HeldQualificationChangeCollection.create({ db });\n const all: HeldQualificationChange[] = [];\n for (let at = 0; at < ids.length; at += IN_CHUNK)\n all.push(\n ...(await collection.list({\n where: {\n tenantId: this.actor.tenantId,\n heldQualificationId: ids.slice(at, at + IN_CHUNK),\n },\n })),\n );\n all.sort(\n (a, b) =>\n a.effectiveOn.localeCompare(b.effectiveOn) ||\n createdTime(a) - createdTime(b),\n );\n const grouped = new Map<string, HeldQualificationChange[]>();\n for (const change of all)\n grouped.set(change.heldQualificationId, [\n ...(grouped.get(change.heldQualificationId) ?? []),\n change,\n ]);\n return grouped;\n }\n\n /**\n * The employment an employment-scoped grant belongs to (null for a\n * person-scoped one) and, when the term covering `issuedOn` already has its\n * end recorded, that term's last day. The person must be employed on\n * `issuedOn`.\n */\n private async employmentFor(\n db: DatabaseInterface,\n definition: Qualification,\n profileId: string,\n given: string | null,\n issuedOn: IsoDate,\n ): Promise<{ employmentId: string | null; endsOn: IsoDate | null }> {\n const mismatch = (message: string) =>\n new HrError('HR_QUALIFICATION_SCOPE', message);\n if (definition.scope !== 'employment') {\n if (given != null)\n throw mismatch(\n `'${definition.key}' belongs to the person; omit employmentId.`,\n );\n return { employmentId: null, endsOn: null };\n }\n const employments = await EmploymentCollection.create({ db });\n const [employment] = await employments.list({\n where: {\n tenantId: this.actor.tenantId,\n profileId,\n ...(given != null ? { id: given } : {}),\n },\n limit: 1,\n });\n if (!employment)\n throw mismatch(\n `'${definition.key}' belongs to an employment, and this person has no matching employment.`,\n );\n const terms = await EmploymentTermCollection.create({ db });\n const term = (\n await terms.list({\n where: {\n tenantId: this.actor.tenantId,\n employmentId: employment.id as string,\n 'startedOn <=': issuedOn,\n },\n })\n ).find((covering) =>\n dateWithin(issuedOn, covering.startedOn, covering.endedOn),\n );\n if (!term)\n throw mismatch(\n `'${definition.key}' belongs to an employment, and this person is not employed on ${issuedOn}.`,\n );\n return { employmentId: employment.id as string, endsOn: term.endedOn };\n }\n\n /**\n * Insert a granted or renewed row with its change and event. With a\n * `revocation` (the end of an employment term that is already closed, or a\n * pending revocation a renewal carries forward) the row starts out cut off,\n * exactly as `EmploymentService.end()` leaves the rows that existed when\n * the end was recorded: stored `revoked`, a revocation on that date, and\n * the revoked event.\n */\n private async insertHeld(\n db: DatabaseInterface,\n queue: HrEventQueue,\n kind: 'granted' | 'renewed',\n values: {\n qualificationId: string;\n profileId: string;\n employmentId: string | null;\n revocation: Revocation | null;\n issuedOn: IsoDate;\n expiresOn: IsoDate | null;\n renewalOfId: string | null;\n input: Pick<\n GrantQualificationInput,\n 'certificateNumber' | 'documentAssetId' | 'verified'\n >;\n },\n ): Promise<HeldQualification> {\n const { input, revocation, ...identityFields } = values;\n const verified = flag('verified', input.verified, false);\n const row = await insertHr(HeldQualification, db, {\n tenantId: this.actor.tenantId,\n ...identityFields,\n certificateNumber: text('certificateNumber', input.certificateNumber),\n documentAssetId: this.optionalId(\n 'documentAssetId',\n input.documentAssetId,\n ),\n status: revocation === null ? 'valid' : 'revoked',\n verifiedByProfileId: verified ? this.actor.profileId : null,\n verifiedAt: verified ? new Date() : null,\n });\n await this.recordChange(db, row, kind, values.issuedOn, '');\n this.announce(queue, `held-qualification.${kind}`, row, values.issuedOn);\n if (revocation !== null) {\n await this.recordChange(\n db,\n row,\n 'revoked',\n revocation.on,\n revocation.reason,\n );\n this.announce(queue, 'held-qualification.revoked', row, revocation.on);\n }\n return row;\n }\n\n /**\n * Suspend, reinstate or revoke the latest row of a chain. The change is\n * validated against the chain's dated history as of `effectiveOn`; the\n * stored status is not consulted, except that a row stored `revoked` is\n * never saved again and gets the change row only.\n */\n private async transition(\n heldQualificationId: string,\n kind: 'suspended' | 'reinstated' | 'revoked',\n input: { effectiveOn: IsoDate; reason: string },\n ): Promise<HeldQualification> {\n const id = this.id('heldQualificationId', heldQualificationId);\n const effectiveOn = assertIsoDate('effectiveOn', input.effectiveOn);\n await this.transact(async (db, queue) => {\n const row = await this.held(db, id);\n const siblings = await this.rows(db, {\n profileId: row.profileId,\n qualificationId: row.qualificationId,\n });\n if (siblings.some((other) => other.renewalOfId === id))\n throw invalid(\n `This qualification was renewed and cannot be ${kind} by itself; act on the latest row in its renewal chain.`,\n );\n // The change is the chain's: it may take effect while an earlier row is\n // the one in force, before this row's own issue date.\n const byId = new Map(\n siblings.map((other) => [other.id as string, other]),\n );\n const chain: HeldQualification[] = [];\n for (\n let member: HeldQualification | undefined = row;\n member && !chain.includes(member);\n member = member.renewalOfId ? byId.get(member.renewalOfId) : undefined\n )\n chain.push(member);\n const firstIssuedOn = chain.reduce(\n (first, member) => (member.issuedOn < first ? member.issuedOn : first),\n row.issuedOn,\n );\n if (effectiveOn < firstIssuedOn)\n throw invalid(\n `effectiveOn must not be before the qualification was issued on ${firstIssuedOn}.`,\n );\n const changes = await this.changesByHeld(db, chain);\n const history = chain.flatMap(\n (member) => changes.get(member.id as string) ?? [],\n );\n if (kind === 'revoked') {\n // Terminal, and the earliest one wins in replay, so it needs no\n // ordering against other changes: only an earlier or same-day\n // revocation makes it pointless.\n const revokedOn = revokedFrom(history);\n if (revokedOn !== null && revokedOn <= effectiveOn)\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n `This qualification is already revoked from ${revokedOn}.`,\n );\n } else {\n // Suspensions and reinstatements are recorded in date order, so the\n // replay never has to reorder them.\n const changedOn = latestOf(history, SUSPENSION_KINDS);\n if (changedOn !== null && effectiveOn < changedOn)\n throw invalid(\n `effectiveOn must not be before the latest suspension or reinstatement on ${changedOn}.`,\n );\n const standing = standingOn(history, effectiveOn) ?? 'valid';\n if (standing !== (kind === 'suspended' ? 'valid' : 'suspended'))\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n standing === 'valid'\n ? `A qualification that is not suspended on ${effectiveOn} cannot be reinstated.`\n : `A qualification that is ${standing} on ${effectiveOn} cannot be ${kind}.`,\n );\n }\n // `revoked` is final in storage: a row that already stores it is never\n // saved again, and the dated change alone carries the new fact.\n if (row.status !== 'revoked') {\n const lapsed = (changes.get(id) ?? []).some(\n (change) => change.kind === 'expired',\n );\n row.status =\n kind === 'reinstated' ? (lapsed ? 'expired' : 'valid') : kind;\n await persistHr(row);\n }\n await this.recordChange(db, row, kind, effectiveOn, input.reason);\n this.announce(queue, `held-qualification.${kind}`, row, effectiveOn);\n });\n return this.held(this.db, id);\n }\n\n private async recordChange(\n db: DatabaseInterface,\n row: HeldQualification,\n kind: HeldQualificationChangeKind,\n effectiveOn: IsoDate,\n reason: string,\n ): Promise<void> {\n await insertHr(HeldQualificationChange, db, {\n tenantId: this.actor.tenantId,\n heldQualificationId: row.id as string,\n kind,\n effectiveOn,\n reason,\n actorProfileId: this.actor.profileId,\n });\n }\n\n private announce(\n queue: HrEventQueue,\n type: HeldQualificationEventType,\n heldQualification: HeldQualification,\n effectiveOn: IsoDate,\n ): void {\n queue({\n type,\n heldQualification,\n effectiveOn,\n at: new Date(),\n byProfileId: this.actor.profileId,\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["id","row"],"sources":["../src/__smrt-register__.ts","../src/dates.ts","../src/write.ts","../src/employment/models.ts","../src/qualifications/models.ts","../src/qualifications/internal.ts","../src/service-base.ts","../src/employment/queries.ts","../src/employment/service.ts","../src/qualifications/service.ts"],"sourcesContent":["// Generated by @happyvertical/smrt-core smrtPlugin at library build time.\n// The package manifest is inlined so self-registration cannot miss when a\n// downstream bundler relocates this module away from dist/manifest.json\n// (https://github.com/happyvertical/smrt/issues/1506,\n// https://github.com/happyvertical/smrt/issues/1507).\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\nObjectRegistry.registerPackageManifest(JSON.parse(\"{\\\"version\\\":\\\"1.0.0\\\",\\\"timestamp\\\":0,\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"packageVersion\\\":\\\"0.55.12\\\",\\\"objects\\\":{\\\"@happyvertical/smrt-human-resources:Employment\\\":{\\\"name\\\":\\\"employment\\\",\\\"className\\\":\\\"Employment\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:Employment\\\",\\\"collection\\\":\\\"employments\\\",\\\"description\\\":\\\"One person's employment with a tenant: the stable record applications and payroll key on. There is one per tenant and profile; a rehire adds an EmploymentTerm to the same row.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/employment/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"profileId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-profiles:Profile\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"userId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-users:User\\\",\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"employerProfileId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-profiles:Profile\\\",\\\"_meta\\\":{\\\"nullable\\\":true,\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"employeeNumber\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"workerType\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{}},\\\"position\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"status\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{},\\\"enum\\\":[\\\"active\\\",\\\"on-leave\\\",\\\"ended\\\"]}},\\\"methods\\\":{\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"delete\\\":{\\\"name\\\":\\\"delete\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<void>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"employments\\\",\\\"sensitive\\\":true,\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"employee_number\\\"],\\\"indexes\\\":[{\\\"name\\\":\\\"employments_tenant_profile_uq\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"profileId\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"employments_tenant_user_idx\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"userId\\\"]}],\\\"api\\\":{\\\"include\\\":[]},\\\"cli\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"}},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"Employment\\\",\\\"collectionExportName\\\":\\\"EmploymentCollection\\\",\\\"validationRules\\\":[],\\\"schema\\\":{\\\"tableName\\\":\\\"employments\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"employments\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"profile_id\\\\\\\" UUID,\\\\n \\\\\\\"user_id\\\\\\\" UUID,\\\\n \\\\\\\"employer_profile_id\\\\\\\" UUID,\\\\n \\\\\\\"employee_number\\\\\\\" TEXT,\\\\n \\\\\\\"worker_type\\\\\\\" TEXT,\\\\n \\\\\\\"position\\\\\\\" TEXT,\\\\n \\\\\\\"status\\\\\\\" TEXT\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"user_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"employer_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"employee_number\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"worker_type\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"position\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"status\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"employments_tenant_id_employee_number_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"employee_number\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"employments_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"employments_tenant_profile_uq\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"profile_id\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"employments_tenant_user_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"user_id\\\"]},{\\\"name\\\":\\\"employments_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"employments_profile_id_idx\\\",\\\"columns\\\":[\\\"profile_id\\\"]},{\\\"name\\\":\\\"employments_user_id_idx\\\",\\\"columns\\\":[\\\"user_id\\\"]},{\\\"name\\\":\\\"employments_employer_profile_id_idx\\\",\\\"columns\\\":[\\\"employer_profile_id\\\"]}],\\\"version\\\":\\\"ff7ef7a8\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:EmploymentTerm\\\":{\\\"name\\\":\\\"employmentterm\\\",\\\"className\\\":\\\"EmploymentTerm\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:EmploymentTerm\\\",\\\"collection\\\":\\\"employmentterms\\\",\\\"description\\\":\\\"One continuous period of employment. endedOn is the last day employed (inclusive); null means the term is open. \\\\\\\"Employed on a date\\\\\\\" is answered from terms, never from Employment.status.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/employment/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"employmentId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"Employment\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"startedOn\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{}},\\\"endedOn\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"endReason\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false}},\\\"methods\\\":{\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"delete\\\":{\\\"name\\\":\\\"delete\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<void>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"employment_terms\\\",\\\"sensitive\\\":true,\\\"indexes\\\":[{\\\"name\\\":\\\"employment_terms_employment_idx\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"employmentId\\\",\\\"startedOn\\\"]}],\\\"api\\\":{\\\"include\\\":[]},\\\"cli\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"},\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"EmploymentTerm\\\",\\\"collectionExportName\\\":\\\"EmploymentTermCollection\\\",\\\"validationRules\\\":[],\\\"schema\\\":{\\\"tableName\\\":\\\"employment_terms\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"employment_terms\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"employment_id\\\\\\\" UUID,\\\\n \\\\\\\"started_on\\\\\\\" TEXT,\\\\n \\\\\\\"ended_on\\\\\\\" TEXT,\\\\n \\\\\\\"end_reason\\\\\\\" TEXT\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"employment_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"employments\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"started_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"ended_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"end_reason\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"employment_terms_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"employment_terms_employment_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"employment_id\\\",\\\"started_on\\\"]},{\\\"name\\\":\\\"employment_terms_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"employment_terms_employment_id_idx\\\",\\\"columns\\\":[\\\"employment_id\\\"]}],\\\"version\\\":\\\"165d209b\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:EmploymentChange\\\":{\\\"name\\\":\\\"employmentchange\\\",\\\"className\\\":\\\"EmploymentChange\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:EmploymentChange\\\",\\\"collection\\\":\\\"employmentchanges\\\",\\\"description\\\":\\\"Append-only dated change to an employment, attributed to an actor.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/employment/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"employmentId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"Employment\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"kind\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{},\\\"enum\\\":[\\\"hired\\\",\\\"ended\\\",\\\"rehired\\\",\\\"position-changed\\\",\\\"worker-type-changed\\\",\\\"leave-started\\\",\\\"leave-ended\\\",\\\"login-linked\\\",\\\"login-unlinked\\\"]},\\\"effectiveOn\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{}},\\\"fromValue\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"toValue\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"note\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"actorProfileId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-profiles:Profile\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}}},\\\"methods\\\":{\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"delete\\\":{\\\"name\\\":\\\"delete\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<void>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"employment_changes\\\",\\\"sensitive\\\":true,\\\"indexes\\\":[{\\\"name\\\":\\\"employment_changes_employment_idx\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"employmentId\\\",\\\"effectiveOn\\\"]}],\\\"api\\\":{\\\"include\\\":[]},\\\"cli\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"},\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"EmploymentChange\\\",\\\"collectionExportName\\\":\\\"EmploymentChangeCollection\\\",\\\"validationRules\\\":[],\\\"schema\\\":{\\\"tableName\\\":\\\"employment_changes\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"employment_changes\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"employment_id\\\\\\\" UUID,\\\\n \\\\\\\"kind\\\\\\\" TEXT,\\\\n \\\\\\\"effective_on\\\\\\\" TEXT,\\\\n \\\\\\\"from_value\\\\\\\" TEXT,\\\\n \\\\\\\"to_value\\\\\\\" TEXT,\\\\n \\\\\\\"note\\\\\\\" TEXT,\\\\n \\\\\\\"actor_profile_id\\\\\\\" UUID\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"employment_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"employments\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"kind\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"effective_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"from_value\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"to_value\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"note\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"actor_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"employment_changes_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"employment_changes_employment_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"employment_id\\\",\\\"effective_on\\\"]},{\\\"name\\\":\\\"employment_changes_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"employment_changes_employment_id_idx\\\",\\\"columns\\\":[\\\"employment_id\\\"]},{\\\"name\\\":\\\"employment_changes_actor_profile_id_idx\\\",\\\"columns\\\":[\\\"actor_profile_id\\\"]}],\\\"version\\\":\\\"5d93422f\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:EmploymentCollection\\\":{\\\"name\\\":\\\"employmentcollection\\\",\\\"className\\\":\\\"EmploymentCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:EmploymentCollection\\\",\\\"collection\\\":\\\"employments\\\",\\\"description\\\":\\\"Scoped persistence collection; mutations must use EmploymentService.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/employment/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"api\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"cli\\\":false,\\\"tableName\\\":\\\"employments\\\"},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"Employment\\\",\\\"exportName\\\":\\\"EmploymentCollection\\\",\\\"collectionExportName\\\":\\\"EmploymentCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"employments\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"employments\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"profile_id\\\\\\\" UUID,\\\\n \\\\\\\"user_id\\\\\\\" UUID,\\\\n \\\\\\\"employer_profile_id\\\\\\\" UUID,\\\\n \\\\\\\"employee_number\\\\\\\" TEXT,\\\\n \\\\\\\"worker_type\\\\\\\" TEXT,\\\\n \\\\\\\"position\\\\\\\" TEXT,\\\\n \\\\\\\"status\\\\\\\" TEXT\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"user_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"employer_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"employee_number\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"worker_type\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"position\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"status\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"employments_tenant_id_employee_number_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"employee_number\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"employments_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"employments_tenant_profile_uq\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"profile_id\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"employments_tenant_user_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"user_id\\\"]},{\\\"name\\\":\\\"employments_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"employments_profile_id_idx\\\",\\\"columns\\\":[\\\"profile_id\\\"]},{\\\"name\\\":\\\"employments_user_id_idx\\\",\\\"columns\\\":[\\\"user_id\\\"]},{\\\"name\\\":\\\"employments_employer_profile_id_idx\\\",\\\"columns\\\":[\\\"employer_profile_id\\\"]}],\\\"version\\\":\\\"ff7ef7a8\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:EmploymentTermCollection\\\":{\\\"name\\\":\\\"employmenttermcollection\\\",\\\"className\\\":\\\"EmploymentTermCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:EmploymentTermCollection\\\",\\\"collection\\\":\\\"employmentterms\\\",\\\"description\\\":\\\"Scoped term collection; closed like its model.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/employment/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"api\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"cli\\\":false,\\\"tableName\\\":\\\"employment_terms\\\"},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"EmploymentTerm\\\",\\\"exportName\\\":\\\"EmploymentTermCollection\\\",\\\"collectionExportName\\\":\\\"EmploymentTermCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"employment_terms\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"employment_terms\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"employment_id\\\\\\\" UUID,\\\\n \\\\\\\"started_on\\\\\\\" TEXT,\\\\n \\\\\\\"ended_on\\\\\\\" TEXT,\\\\n \\\\\\\"end_reason\\\\\\\" TEXT\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"employment_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"employments\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"started_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"ended_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"end_reason\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"employment_terms_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"employment_terms_employment_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"employment_id\\\",\\\"started_on\\\"]},{\\\"name\\\":\\\"employment_terms_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"employment_terms_employment_id_idx\\\",\\\"columns\\\":[\\\"employment_id\\\"]}],\\\"version\\\":\\\"165d209b\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:EmploymentChangeCollection\\\":{\\\"name\\\":\\\"employmentchangecollection\\\",\\\"className\\\":\\\"EmploymentChangeCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:EmploymentChangeCollection\\\",\\\"collection\\\":\\\"employmentchanges\\\",\\\"description\\\":\\\"Scoped append-only change collection; closed like its model.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/employment/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"api\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"cli\\\":false,\\\"tableName\\\":\\\"employment_changes\\\"},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"EmploymentChange\\\",\\\"exportName\\\":\\\"EmploymentChangeCollection\\\",\\\"collectionExportName\\\":\\\"EmploymentChangeCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"employment_changes\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"employment_changes\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"employment_id\\\\\\\" UUID,\\\\n \\\\\\\"kind\\\\\\\" TEXT,\\\\n \\\\\\\"effective_on\\\\\\\" TEXT,\\\\n \\\\\\\"from_value\\\\\\\" TEXT,\\\\n \\\\\\\"to_value\\\\\\\" TEXT,\\\\n \\\\\\\"note\\\\\\\" TEXT,\\\\n \\\\\\\"actor_profile_id\\\\\\\" UUID\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"employment_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"employments\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"kind\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"effective_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"from_value\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"to_value\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"note\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"actor_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"employment_changes_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"employment_changes_employment_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"employment_id\\\",\\\"effective_on\\\"]},{\\\"name\\\":\\\"employment_changes_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"employment_changes_employment_id_idx\\\",\\\"columns\\\":[\\\"employment_id\\\"]},{\\\"name\\\":\\\"employment_changes_actor_profile_id_idx\\\",\\\"columns\\\":[\\\"actor_profile_id\\\"]}],\\\"version\\\":\\\"5d93422f\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:Qualification\\\":{\\\"name\\\":\\\"qualification\\\",\\\"className\\\":\\\"Qualification\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:Qualification\\\",\\\"collection\\\":\\\"qualifications\\\",\\\"description\\\":\\\"A qualification definition: a managed list per tenant. Other packages gate work by referencing its id (@happyvertical/smrt-human-resources:Qualification).\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/qualifications/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"key\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"name\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"kind\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{},\\\"enum\\\":[\\\"ticket\\\",\\\"certification\\\",\\\"authorization\\\",\\\"training\\\",\\\"restriction\\\"]},\\\"issuingBody\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"expires\\\":{\\\"type\\\":\\\"boolean\\\",\\\"required\\\":true,\\\"default\\\":false},\\\"validityMonths\\\":{\\\"type\\\":\\\"integer\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"scope\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{},\\\"enum\\\":[\\\"person\\\",\\\"employment\\\"]},\\\"isActive\\\":{\\\"type\\\":\\\"boolean\\\",\\\"required\\\":true,\\\"default\\\":true}},\\\"methods\\\":{\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"delete\\\":{\\\"name\\\":\\\"delete\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<void>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"qualifications\\\",\\\"sensitive\\\":true,\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"key\\\"],\\\"api\\\":{\\\"include\\\":[]},\\\"cli\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"}},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"Qualification\\\",\\\"collectionExportName\\\":\\\"QualificationCollection\\\",\\\"displayLabelField\\\":\\\"name\\\",\\\"validationRules\\\":[{\\\"field\\\":\\\"expires\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"boolean\\\"},{\\\"field\\\":\\\"isActive\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"boolean\\\"}],\\\"schema\\\":{\\\"tableName\\\":\\\"qualifications\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"qualifications\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"key\\\\\\\" TEXT,\\\\n \\\\\\\"name\\\\\\\" TEXT,\\\\n \\\\\\\"kind\\\\\\\" TEXT,\\\\n \\\\\\\"issuing_body\\\\\\\" TEXT,\\\\n \\\\\\\"expires\\\\\\\" BOOLEAN NOT NULL DEFAULT FALSE,\\\\n \\\\\\\"validity_months\\\\\\\" INTEGER,\\\\n \\\\\\\"scope\\\\\\\" TEXT,\\\\n \\\\\\\"is_active\\\\\\\" BOOLEAN NOT NULL DEFAULT TRUE\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"key\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"kind\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"issuing_body\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"expires\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":true,\\\"unique\\\":false,\\\"default\\\":false},\\\"validity_months\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"scope\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"is_active\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":true,\\\"unique\\\":false,\\\"default\\\":true}},\\\"indexes\\\":[{\\\"name\\\":\\\"qualifications_tenant_id_key_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"key\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"qualifications_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"qualifications_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]}],\\\"version\\\":\\\"c9fa3721\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:HeldQualification\\\":{\\\"name\\\":\\\"heldqualification\\\",\\\"className\\\":\\\"HeldQualification\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:HeldQualification\\\",\\\"collection\\\":\\\"heldqualifications\\\",\\\"description\\\":\\\"A person holds a qualification. employmentId is set when the definition's scope is employment; such a row is revoked when that employment ends.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/qualifications/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"qualificationId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"Qualification\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"profileId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-profiles:Profile\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"employmentId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"Employment\\\",\\\"_meta\\\":{\\\"nullable\\\":true,\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"issuedOn\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{}},\\\"expiresOn\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"certificateNumber\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"status\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{},\\\"enum\\\":[\\\"valid\\\",\\\"expired\\\",\\\"suspended\\\",\\\"revoked\\\"]},\\\"verifiedByProfileId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-profiles:Profile\\\",\\\"_meta\\\":{\\\"nullable\\\":true,\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"verifiedAt\\\":{\\\"type\\\":\\\"datetime\\\",\\\"required\\\":false},\\\"documentAssetId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-assets:Asset\\\",\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"renewalOfId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"HeldQualification\\\",\\\"_meta\\\":{\\\"nullable\\\":true,\\\"onDelete\\\":\\\"RESTRICT\\\"}}},\\\"methods\\\":{\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"delete\\\":{\\\"name\\\":\\\"delete\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<void>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"held_qualifications\\\",\\\"sensitive\\\":true,\\\"indexes\\\":[{\\\"name\\\":\\\"held_qualifications_profile_idx\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"profileId\\\",\\\"qualificationId\\\"]},{\\\"name\\\":\\\"held_qualifications_expiry_idx\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"status\\\",\\\"expiresOn\\\"]}],\\\"api\\\":{\\\"include\\\":[]},\\\"cli\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"},\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"HeldQualification\\\",\\\"collectionExportName\\\":\\\"HeldQualificationCollection\\\",\\\"validationRules\\\":[],\\\"schema\\\":{\\\"tableName\\\":\\\"held_qualifications\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"held_qualifications\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"qualification_id\\\\\\\" UUID,\\\\n \\\\\\\"profile_id\\\\\\\" UUID,\\\\n \\\\\\\"employment_id\\\\\\\" UUID,\\\\n \\\\\\\"issued_on\\\\\\\" TEXT,\\\\n \\\\\\\"expires_on\\\\\\\" TEXT,\\\\n \\\\\\\"certificate_number\\\\\\\" TEXT,\\\\n \\\\\\\"status\\\\\\\" TEXT,\\\\n \\\\\\\"verified_by_profile_id\\\\\\\" UUID,\\\\n \\\\\\\"verified_at\\\\\\\" TIMESTAMP,\\\\n \\\\\\\"document_asset_id\\\\\\\" UUID,\\\\n \\\\\\\"renewal_of_id\\\\\\\" UUID\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"qualification_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"qualifications\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"employment_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"employments\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"issued_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"expires_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"certificate_number\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"status\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"verified_by_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"verified_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"document_asset_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"renewal_of_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"held_qualifications\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}}},\\\"indexes\\\":[{\\\"name\\\":\\\"held_qualifications_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"held_qualifications_profile_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"profile_id\\\",\\\"qualification_id\\\"]},{\\\"name\\\":\\\"held_qualifications_expiry_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"status\\\",\\\"expires_on\\\"]},{\\\"name\\\":\\\"held_qualifications_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"held_qualifications_qualification_id_idx\\\",\\\"columns\\\":[\\\"qualification_id\\\"]},{\\\"name\\\":\\\"held_qualifications_profile_id_idx\\\",\\\"columns\\\":[\\\"profile_id\\\"]},{\\\"name\\\":\\\"held_qualifications_employment_id_idx\\\",\\\"columns\\\":[\\\"employment_id\\\"]},{\\\"name\\\":\\\"held_qualifications_verified_by_profile_id_idx\\\",\\\"columns\\\":[\\\"verified_by_profile_id\\\"]},{\\\"name\\\":\\\"held_qualifications_document_asset_id_idx\\\",\\\"columns\\\":[\\\"document_asset_id\\\"]},{\\\"name\\\":\\\"held_qualifications_renewal_of_id_idx\\\",\\\"columns\\\":[\\\"renewal_of_id\\\"]}],\\\"version\\\":\\\"fafbe37f\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:HeldQualificationChange\\\":{\\\"name\\\":\\\"heldqualificationchange\\\",\\\"className\\\":\\\"HeldQualificationChange\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:HeldQualificationChange\\\",\\\"collection\\\":\\\"heldqualificationchanges\\\",\\\"description\\\":\\\"Append-only dated change to a held qualification, attributed to an actor.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/qualifications/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"__tenancy\\\":{\\\"isTenantIdField\\\":true,\\\"autoFilter\\\":true,\\\"required\\\":true,\\\"autoPopulate\\\":true,\\\"nullable\\\":false,\\\"mode\\\":\\\"required\\\",\\\"field\\\":\\\"tenantId\\\",\\\"allowSuperAdminBypass\\\":false}}},\\\"heldQualificationId\\\":{\\\"type\\\":\\\"foreignKey\\\",\\\"required\\\":false,\\\"related\\\":\\\"HeldQualification\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}},\\\"kind\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{},\\\"enum\\\":[\\\"granted\\\",\\\"renewed\\\",\\\"suspended\\\",\\\"reinstated\\\",\\\"revoked\\\",\\\"expired\\\"]},\\\"effectiveOn\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{}},\\\"reason\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false},\\\"actorProfileId\\\":{\\\"type\\\":\\\"crossPackageRef\\\",\\\"required\\\":false,\\\"related\\\":\\\"@happyvertical/smrt-profiles:Profile\\\",\\\"_meta\\\":{\\\"onDelete\\\":\\\"RESTRICT\\\"}}},\\\"methods\\\":{\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"delete\\\":{\\\"name\\\":\\\"delete\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<void>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"held_qualification_changes\\\",\\\"sensitive\\\":true,\\\"indexes\\\":[{\\\"name\\\":\\\"held_qualification_changes_held_idx\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"heldQualificationId\\\",\\\"effectiveOn\\\"]}],\\\"api\\\":{\\\"include\\\":[]},\\\"cli\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"tenantScoped\\\":{\\\"mode\\\":\\\"required\\\"},\\\"conflictColumns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"HeldQualificationChange\\\",\\\"collectionExportName\\\":\\\"HeldQualificationChangeCollection\\\",\\\"validationRules\\\":[],\\\"schema\\\":{\\\"tableName\\\":\\\"held_qualification_changes\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"held_qualification_changes\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"held_qualification_id\\\\\\\" UUID,\\\\n \\\\\\\"kind\\\\\\\" TEXT,\\\\n \\\\\\\"effective_on\\\\\\\" TEXT,\\\\n \\\\\\\"reason\\\\\\\" TEXT,\\\\n \\\\\\\"actor_profile_id\\\\\\\" UUID\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"held_qualification_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"held_qualifications\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"kind\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"effective_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"reason\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"actor_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"held_qualification_changes_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"held_qualification_changes_held_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"held_qualification_id\\\",\\\"effective_on\\\"]},{\\\"name\\\":\\\"held_qualification_changes_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"held_qualification_changes_held_qualification_id_idx\\\",\\\"columns\\\":[\\\"held_qualification_id\\\"]},{\\\"name\\\":\\\"held_qualification_changes_actor_profile_id_idx\\\",\\\"columns\\\":[\\\"actor_profile_id\\\"]}],\\\"version\\\":\\\"ed9ed5e1\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:QualificationCollection\\\":{\\\"name\\\":\\\"qualificationcollection\\\",\\\"className\\\":\\\"QualificationCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:QualificationCollection\\\",\\\"collection\\\":\\\"qualifications\\\",\\\"description\\\":\\\"Scoped definition collection; mutations must use QualificationService.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/qualifications/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"api\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"cli\\\":false,\\\"tableName\\\":\\\"qualifications\\\"},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"Qualification\\\",\\\"exportName\\\":\\\"QualificationCollection\\\",\\\"collectionExportName\\\":\\\"QualificationCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"qualifications\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"qualifications\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"key\\\\\\\" TEXT,\\\\n \\\\\\\"name\\\\\\\" TEXT,\\\\n \\\\\\\"kind\\\\\\\" TEXT,\\\\n \\\\\\\"issuing_body\\\\\\\" TEXT,\\\\n \\\\\\\"expires\\\\\\\" BOOLEAN NOT NULL DEFAULT FALSE,\\\\n \\\\\\\"validity_months\\\\\\\" INTEGER,\\\\n \\\\\\\"scope\\\\\\\" TEXT,\\\\n \\\\\\\"is_active\\\\\\\" BOOLEAN NOT NULL DEFAULT TRUE\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"key\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"name\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"kind\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"issuing_body\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"expires\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":true,\\\"unique\\\":false,\\\"default\\\":false},\\\"validity_months\\\":{\\\"type\\\":\\\"INTEGER\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"scope\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"is_active\\\":{\\\"type\\\":\\\"BOOLEAN\\\",\\\"notNull\\\":true,\\\"unique\\\":false,\\\"default\\\":true}},\\\"indexes\\\":[{\\\"name\\\":\\\"qualifications_tenant_id_key_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"key\\\"],\\\"unique\\\":true,\\\"nullsNotDistinct\\\":true},{\\\"name\\\":\\\"qualifications_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"qualifications_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]}],\\\"version\\\":\\\"c9fa3721\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:HeldQualificationCollection\\\":{\\\"name\\\":\\\"heldqualificationcollection\\\",\\\"className\\\":\\\"HeldQualificationCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:HeldQualificationCollection\\\",\\\"collection\\\":\\\"heldqualifications\\\",\\\"description\\\":\\\"Scoped held-qualification collection; closed like its model.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/qualifications/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"api\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"cli\\\":false,\\\"tableName\\\":\\\"held_qualifications\\\"},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"HeldQualification\\\",\\\"exportName\\\":\\\"HeldQualificationCollection\\\",\\\"collectionExportName\\\":\\\"HeldQualificationCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"held_qualifications\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"held_qualifications\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"qualification_id\\\\\\\" UUID,\\\\n \\\\\\\"profile_id\\\\\\\" UUID,\\\\n \\\\\\\"employment_id\\\\\\\" UUID,\\\\n \\\\\\\"issued_on\\\\\\\" TEXT,\\\\n \\\\\\\"expires_on\\\\\\\" TEXT,\\\\n \\\\\\\"certificate_number\\\\\\\" TEXT,\\\\n \\\\\\\"status\\\\\\\" TEXT,\\\\n \\\\\\\"verified_by_profile_id\\\\\\\" UUID,\\\\n \\\\\\\"verified_at\\\\\\\" TIMESTAMP,\\\\n \\\\\\\"document_asset_id\\\\\\\" UUID,\\\\n \\\\\\\"renewal_of_id\\\\\\\" UUID\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"qualification_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"qualifications\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"employment_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"employments\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"issued_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"expires_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"certificate_number\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"status\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"verified_by_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"verified_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"document_asset_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"renewal_of_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"held_qualifications\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}}},\\\"indexes\\\":[{\\\"name\\\":\\\"held_qualifications_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"held_qualifications_profile_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"profile_id\\\",\\\"qualification_id\\\"]},{\\\"name\\\":\\\"held_qualifications_expiry_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"status\\\",\\\"expires_on\\\"]},{\\\"name\\\":\\\"held_qualifications_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"held_qualifications_qualification_id_idx\\\",\\\"columns\\\":[\\\"qualification_id\\\"]},{\\\"name\\\":\\\"held_qualifications_profile_id_idx\\\",\\\"columns\\\":[\\\"profile_id\\\"]},{\\\"name\\\":\\\"held_qualifications_employment_id_idx\\\",\\\"columns\\\":[\\\"employment_id\\\"]},{\\\"name\\\":\\\"held_qualifications_verified_by_profile_id_idx\\\",\\\"columns\\\":[\\\"verified_by_profile_id\\\"]},{\\\"name\\\":\\\"held_qualifications_document_asset_id_idx\\\",\\\"columns\\\":[\\\"document_asset_id\\\"]},{\\\"name\\\":\\\"held_qualifications_renewal_of_id_idx\\\",\\\"columns\\\":[\\\"renewal_of_id\\\"]}],\\\"version\\\":\\\"fafbe37f\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"},\\\"@happyvertical/smrt-human-resources:HeldQualificationChangeCollection\\\":{\\\"name\\\":\\\"heldqualificationchangecollection\\\",\\\"className\\\":\\\"HeldQualificationChangeCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-human-resources:HeldQualificationChangeCollection\\\",\\\"collection\\\":\\\"heldqualificationchanges\\\",\\\"description\\\":\\\"Scoped append-only change collection; closed like its model.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/human-resources/src/qualifications/models.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-human-resources\\\",\\\"fields\\\":{},\\\"methods\\\":{},\\\"decoratorConfig\\\":{\\\"api\\\":{\\\"include\\\":[]},\\\"mcp\\\":{\\\"include\\\":[]},\\\"cli\\\":false,\\\"tableName\\\":\\\"held_qualification_changes\\\"},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"HeldQualificationChange\\\",\\\"exportName\\\":\\\"HeldQualificationChangeCollection\\\",\\\"collectionExportName\\\":\\\"HeldQualificationChangeCollectionCollection\\\",\\\"schema\\\":{\\\"tableName\\\":\\\"held_qualification_changes\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"held_qualification_changes\\\\\\\" (\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"tenant_id\\\\\\\" UUID NOT NULL,\\\\n \\\\\\\"held_qualification_id\\\\\\\" UUID,\\\\n \\\\\\\"kind\\\\\\\" TEXT,\\\\n \\\\\\\"effective_on\\\\\\\" TEXT,\\\\n \\\\\\\"reason\\\\\\\" TEXT,\\\\n \\\\\\\"actor_profile_id\\\\\\\" UUID\\\\n);\\\",\\\"columns\\\":{\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"tenantId\\\",\\\"notNull\\\":true,\\\"unique\\\":false},\\\"held_qualification_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"foreignKey\\\",\\\"notNull\\\":false,\\\"unique\\\":false,\\\"foreignKey\\\":{\\\"table\\\":\\\"held_qualifications\\\",\\\"column\\\":\\\"id\\\",\\\"onDelete\\\":\\\"RESTRICT\\\",\\\"onUpdate\\\":\\\"CASCADE\\\"}},\\\"kind\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"effective_on\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"reason\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"actor_profile_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"referenceKind\\\":\\\"crossPackageRef\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"held_qualification_changes_slug_context_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"slug\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"held_qualification_changes_held_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"held_qualification_id\\\",\\\"effective_on\\\"]},{\\\"name\\\":\\\"held_qualification_changes_tenant_id_created_at_idx\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"created_at\\\"]},{\\\"name\\\":\\\"held_qualification_changes_held_qualification_id_idx\\\",\\\"columns\\\":[\\\"held_qualification_id\\\"]},{\\\"name\\\":\\\"held_qualification_changes_actor_profile_id_idx\\\",\\\"columns\\\":[\\\"actor_profile_id\\\"]}],\\\"version\\\":\\\"ed9ed5e1\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-human-resources\\\"}},\\\"moduleType\\\":\\\"smrt\\\",\\\"smrtDependencies\\\":[\\\"@happyvertical/smrt-core\\\",\\\"@happyvertical/smrt-tenancy\\\"],\\\"browser\\\":{\\\"status\\\":\\\"server-only\\\",\\\"issues\\\":[\\\"#3624\\\"],\\\"via\\\":[\\\"@happyvertical/smrt-tenancy\\\"]},\\\"stampsConstructors\\\":true}\"));\n","/**\n * Calendar-date helpers. HR dates are `YYYY-MM-DD` text, so they compare\n * correctly as strings and carry no timezone; the application converts an\n * instant to its own local date before asking.\n *\n * @packageDocumentation\n */\n\nimport { HrError, type IsoDate } from './types.js';\n\nconst ISO_DATE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n\n/** Whether `value` is a real calendar date written `YYYY-MM-DD`. */\nexport function isIsoDate(value: unknown): value is IsoDate {\n if (typeof value !== 'string') return false;\n const match = ISO_DATE.exec(value);\n if (!match) return false;\n const [year, month, day] = match.slice(1).map(Number);\n const date = new Date(Date.UTC(year, month - 1, day));\n return (\n date.getUTCFullYear() === year &&\n date.getUTCMonth() === month - 1 &&\n date.getUTCDate() === day\n );\n}\n\n/** Throw unless `value` is a real calendar date written `YYYY-MM-DD`. */\nexport function assertIsoDate(fieldName: string, value: unknown): IsoDate {\n if (!isIsoDate(value))\n throw new HrError(\n 'HR_INVALID',\n `${fieldName} must be a calendar date written YYYY-MM-DD, got '${String(value)}'.`,\n );\n return value;\n}\n\n/** The calendar date `days` after `date` (negative moves back). */\nexport function addDays(date: IsoDate, days: number): IsoDate {\n const [year, month, day] = assertIsoDate('date', date).split('-').map(Number);\n return new Date(Date.UTC(year, month - 1, day + days))\n .toISOString()\n .slice(0, 10);\n}\n\n/**\n * The calendar date `months` after `date`, clamped to the end of the target\n * month (31 January + 1 month is the last day of February).\n */\nexport function addMonths(date: IsoDate, months: number): IsoDate {\n const [year, month, day] = assertIsoDate('date', date).split('-').map(Number);\n const first = new Date(Date.UTC(year, month - 1 + months, 1));\n const lastDay = new Date(\n Date.UTC(first.getUTCFullYear(), first.getUTCMonth() + 1, 0),\n ).getUTCDate();\n first.setUTCDate(Math.min(day, lastDay));\n return first.toISOString().slice(0, 10);\n}\n\n/**\n * Whether `on` falls inside the inclusive range `[start, end]`. A null `end`\n * is open-ended.\n */\nexport function dateWithin(\n on: IsoDate,\n start: IsoDate,\n end: IsoDate | null,\n): boolean {\n return start <= on && (end === null || on <= end);\n}\n","import type { SmrtObject, SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { HrError } from './types.js';\n\n// Per-instance capability, deliberately absent from the package export surface.\nconst permitted = new WeakSet<SmrtObject>();\n\n/** Whether a service is saving this instance right now. */\nexport function hasHrWrite(object: SmrtObject): boolean {\n return permitted.has(object);\n}\n\n/**\n * Guard for a model's `save()`.\n *\n * @throws HrError `HR_WRITE_FORBIDDEN` outside a service write\n */\nexport function assertHrWrite(object: SmrtObject): void {\n if (!hasHrWrite(object))\n throw new HrError(\n 'HR_WRITE_FORBIDDEN',\n 'HR records are writable only through EmploymentService and QualificationService.',\n );\n}\n\n/** Save one instance under the write capability; services only. */\nexport async function persistHr<T extends SmrtObject>(object: T): Promise<T> {\n permitted.add(object);\n try {\n await object.save();\n return object;\n } finally {\n permitted.delete(object);\n }\n}\n\n/**\n * The one way a service inserts an HR row. `collection.create()` saves\n * outside the write capability (so it always throws for HR models), and a\n * plain first `save()` would upsert onto an existing natural key. This builds\n * the instance through the model's public constructor and `initialize()`,\n * requires a real INSERT (`requireInsertOnSave()`), and saves it under the\n * capability. It uses no protected collection API.\n */\nexport async function insertHr<T extends SmrtObject>(\n Model: new (options: SmrtObjectOptions) => T,\n db: DatabaseInterface,\n values: Record<string, unknown>,\n): Promise<T> {\n const row = await new Model({ db, _skipLoad: true, ...values }).initialize();\n row.requireInsertOnSave();\n return persistHr(row);\n}\n","import {\n crossPackageRef,\n field,\n foreignKey,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n type EmploymentChangeKind,\n type EmploymentStatus,\n HrError,\n type IsoDate,\n type WorkerType,\n} from '../types.js';\nimport { assertHrWrite, hasHrWrite } from '../write.js';\n\n/** Stored column and model property of each employment column this package owns. */\nconst PACKAGE_OWNED_COLUMNS = [\n ['tenant_id', 'tenantId'],\n ['profile_id', 'profileId'],\n ['user_id', 'userId'],\n ['employer_profile_id', 'employerProfileId'],\n ['employee_number', 'employeeNumber'],\n ['worker_type', 'workerType'],\n ['position', 'position'],\n ['status', 'status'],\n] as const;\n\n/**\n * One person's employment with a tenant: the stable record applications and\n * payroll key on. There is one per tenant and profile; a rehire adds an\n * {@link EmploymentTerm} to the same row. The tenant is the employer.\n *\n * Applications extend it with a same-named subclass over the `employments`\n * table that restates the closed surface (see AGENTS.md). Such a subclass may\n * `save()` its own added fields on an existing row; the columns this package\n * owns change only through `EmploymentService`.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'employments',\n sensitive: true,\n conflictColumns: ['tenant_id', 'employee_number'],\n indexes: [\n {\n name: 'employments_tenant_profile_uq',\n columns: ['tenantId', 'profileId'],\n unique: true,\n },\n { name: 'employments_tenant_user_idx', columns: ['tenantId', 'userId'] },\n ],\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class Employment extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n @tenantId() tenantId = '';\n /** The person employed. */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n onDelete: 'RESTRICT',\n })\n profileId = '';\n /** Optional login; a worker signing in on a shared device may have none. */\n @crossPackageRef('@happyvertical/smrt-users:User', { nullable: true })\n userId: string | null = null;\n /** Optional organization profile when it differs from the tenant itself. */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n nullable: true,\n onDelete: 'RESTRICT',\n })\n employerProfileId: string | null = null;\n /** Free text, unique per tenant. */\n employeeNumber = '';\n @field({ type: 'text' }) workerType: WorkerType = 'employee';\n /** Position or trade label; latest recorded value. */\n @field({ type: 'text', nullable: true }) position: string | null = null;\n @field({ type: 'text' }) status: EmploymentStatus = 'active';\n\n override async save(): Promise<this> {\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n // Without the service's write capability only an application subclass's\n // own fields may change: the row must exist and every package-owned\n // column must still hold its stored value.\n if (\n !hasHrWrite(this) &&\n (!prior ||\n PACKAGE_OWNED_COLUMNS.some(\n ([column, property]) =>\n (prior[column] ?? null) !== (this[property] ?? null),\n ))\n )\n throw new HrError(\n 'HR_WRITE_FORBIDDEN',\n 'Employment columns owned by the HR package are writable only through EmploymentService.',\n );\n if (\n prior &&\n (prior.tenant_id !== this.tenantId || prior.profile_id !== this.profileId)\n )\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Employment identity is immutable.',\n );\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Employment history cannot be deleted; end the employment instead.',\n );\n }\n}\n\n/**\n * One continuous period of employment. `endedOn` is the last day employed\n * (inclusive); null means the term is open. \"Employed on a date\" is answered\n * from terms, never from {@link Employment.status}.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'employment_terms',\n sensitive: true,\n indexes: [\n {\n name: 'employment_terms_employment_idx',\n columns: ['tenantId', 'employmentId', 'startedOn'],\n },\n ],\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class EmploymentTerm extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n @tenantId() tenantId = '';\n @foreignKey('Employment', { onDelete: 'RESTRICT' }) employmentId = '';\n @field({ type: 'text' }) startedOn: IsoDate = '';\n @field({ type: 'text', nullable: true }) endedOn: IsoDate | null = null;\n endReason = '';\n\n override async save(): Promise<this> {\n assertHrWrite(this);\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n if (\n prior &&\n (prior.ended_on != null ||\n prior.employment_id !== this.employmentId ||\n prior.started_on !== this.startedOn)\n )\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'A term can only be closed once; its start and employment never change.',\n );\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Employment terms cannot be deleted.',\n );\n }\n}\n\n/** Append-only dated change to an employment, attributed to an actor. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'employment_changes',\n sensitive: true,\n indexes: [\n {\n name: 'employment_changes_employment_idx',\n columns: ['tenantId', 'employmentId', 'effectiveOn'],\n },\n ],\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class EmploymentChange extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n @tenantId() tenantId = '';\n @foreignKey('Employment', { onDelete: 'RESTRICT' }) employmentId = '';\n @field({ type: 'text' }) kind: EmploymentChangeKind = 'hired';\n @field({ type: 'text' }) effectiveOn: IsoDate = '';\n @field({ type: 'text', nullable: true }) fromValue: string | null = null;\n @field({ type: 'text', nullable: true }) toValue: string | null = null;\n note = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n onDelete: 'RESTRICT',\n })\n actorProfileId = '';\n\n override async save(): Promise<this> {\n assertHrWrite(this);\n if (this.id && (await this.db.get(this.tableName, { id: this.id })))\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Employment changes are append-only.',\n );\n this.requireInsertOnSave();\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Employment changes cannot be deleted.',\n );\n }\n}\n\n/**\n * Scoped persistence collection; mutations must use EmploymentService.\n * Collections are registered too, so each restates the closed surface: an\n * undecorated one would publish its methods as generated tools.\n */\n@smrt({ api: { include: [] }, mcp: { include: [] }, cli: false })\nexport class EmploymentCollection extends SmrtCollection<Employment> { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n static readonly _itemClass = Employment;\n}\n/** Scoped term collection; closed like its model. */\n@smrt({ api: { include: [] }, mcp: { include: [] }, cli: false })\nexport class EmploymentTermCollection extends SmrtCollection<EmploymentTerm> { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n static readonly _itemClass = EmploymentTerm;\n}\n/** Scoped append-only change collection; closed like its model. */\n@smrt({ api: { include: [] }, mcp: { include: [] }, cli: false })\nexport class EmploymentChangeCollection extends SmrtCollection<EmploymentChange> { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n static readonly _itemClass = EmploymentChange;\n}\n","import {\n crossPackageRef,\n field,\n foreignKey,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n type HeldQualificationChangeKind,\n type HeldQualificationStatus,\n HrError,\n type IsoDate,\n type QualificationKind,\n type QualificationScope,\n} from '../types.js';\nimport { assertHrWrite } from '../write.js';\n\n/**\n * A qualification definition: a managed list per tenant. Other packages gate\n * work by referencing its id (`@happyvertical/smrt-human-resources:Qualification`).\n * Not a permission: who may use the software stays with smrt-users.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'qualifications',\n sensitive: true,\n conflictColumns: ['tenant_id', 'key'],\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class Qualification extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n @tenantId() tenantId = '';\n /** Lowercase kebab-case, unique per tenant: `first-aid`. */\n key = '';\n name = '';\n @field({ type: 'text' }) kind: QualificationKind = 'certification';\n issuingBody = '';\n /** Whether held qualifications of this kind expire. */\n expires = false;\n /** Default validity in months, applied when a grant gives no expiry. */\n @field({ type: 'integer', nullable: true })\n validityMonths: number | null = null;\n /** Whether a held qualification belongs to the person or the employment. */\n @field({ type: 'text' }) scope: QualificationScope = 'person';\n isActive = true;\n\n override async save(): Promise<this> {\n assertHrWrite(this);\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n if (prior && (prior.key !== this.key || prior.scope !== this.scope))\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'A qualification key and scope are immutable; deactivate it instead.',\n );\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Qualifications cannot be deleted; deactivate them instead.',\n );\n }\n}\n\n/**\n * Columns of a held qualification that never change once a revocation is\n * stored. Only the verification fields (`verifiedByProfileId`, `verifiedAt`,\n * `documentAssetId`) may still be saved on such a row.\n */\nconst FROZEN_WHEN_REVOKED = [\n ['tenant_id', 'tenantId'],\n ['qualification_id', 'qualificationId'],\n ['profile_id', 'profileId'],\n ['employment_id', 'employmentId'],\n ['issued_on', 'issuedOn'],\n ['expires_on', 'expiresOn'],\n ['certificate_number', 'certificateNumber'],\n ['status', 'status'],\n ['renewal_of_id', 'renewalOfId'],\n] as const;\n\n/**\n * A person holds a qualification. `employmentId` is set when the definition's\n * scope is `employment`; such a row is revoked when that employment ends.\n * A renewal is a new row pointing at the one it renews, so past dates still\n * answer correctly. Expiry on a date is computed from `expiresOn`; the stored\n * `expired` status is set by the service sweep. The stored status records the\n * last change made, not standing on a date, and `revoked` is final: a row\n * stored `revoked` is saved again only to record a verification.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'held_qualifications',\n sensitive: true,\n indexes: [\n {\n name: 'held_qualifications_profile_idx',\n columns: ['tenantId', 'profileId', 'qualificationId'],\n },\n {\n name: 'held_qualifications_expiry_idx',\n columns: ['tenantId', 'status', 'expiresOn'],\n },\n ],\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class HeldQualification extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n @tenantId() tenantId = '';\n @foreignKey(Qualification, { onDelete: 'RESTRICT' }) qualificationId = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n onDelete: 'RESTRICT',\n })\n profileId = '';\n @foreignKey('Employment', { nullable: true, onDelete: 'RESTRICT' })\n employmentId: string | null = null;\n @field({ type: 'text' }) issuedOn: IsoDate = '';\n @field({ type: 'text', nullable: true }) expiresOn: IsoDate | null = null;\n certificateNumber = '';\n @field({ type: 'text' }) status: HeldQualificationStatus = 'valid';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n nullable: true,\n onDelete: 'RESTRICT',\n })\n verifiedByProfileId: string | null = null;\n verifiedAt: Date | null = null;\n @crossPackageRef('@happyvertical/smrt-assets:Asset', { nullable: true })\n documentAssetId: string | null = null;\n @foreignKey('HeldQualification', { nullable: true, onDelete: 'RESTRICT' })\n renewalOfId: string | null = null;\n\n override async save(): Promise<this> {\n assertHrWrite(this);\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n if (\n prior &&\n (prior.tenant_id !== this.tenantId ||\n prior.profile_id !== this.profileId ||\n prior.qualification_id !== this.qualificationId ||\n prior.issued_on !== this.issuedOn)\n )\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'A held qualification keeps its person, qualification and issue date; renew it instead.',\n );\n // A revocation is final. Verification is not a status, so a save that\n // changes only the verifier, its time and the document is still allowed.\n if (\n prior?.status === 'revoked' &&\n FROZEN_WHEN_REVOKED.some(\n ([column, property]) =>\n (prior[column] ?? null) !== (this[property] ?? null),\n )\n )\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'A revoked qualification never changes, apart from its verification; grant a new one.',\n );\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Held qualifications cannot be deleted; revoke them instead.',\n );\n }\n}\n\n/** Append-only dated change to a held qualification, attributed to an actor. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'held_qualification_changes',\n sensitive: true,\n indexes: [\n {\n name: 'held_qualification_changes_held_idx',\n columns: ['tenantId', 'heldQualificationId', 'effectiveOn'],\n },\n ],\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class HeldQualificationChange extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n @tenantId() tenantId = '';\n @foreignKey(HeldQualification, { onDelete: 'RESTRICT' })\n heldQualificationId = '';\n @field({ type: 'text' }) kind: HeldQualificationChangeKind = 'granted';\n @field({ type: 'text' }) effectiveOn: IsoDate = '';\n reason = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n onDelete: 'RESTRICT',\n })\n actorProfileId = '';\n\n override async save(): Promise<this> {\n assertHrWrite(this);\n if (this.id && (await this.db.get(this.tableName, { id: this.id })))\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Held qualification changes are append-only.',\n );\n this.requireInsertOnSave();\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new HrError(\n 'HR_HISTORY_IMMUTABLE',\n 'Held qualification changes cannot be deleted.',\n );\n }\n}\n\n/**\n * Scoped definition collection; mutations must use QualificationService.\n * Collections are registered too, so each restates the closed surface: an\n * undecorated one would publish its methods as generated tools.\n */\n@smrt({ api: { include: [] }, mcp: { include: [] }, cli: false })\nexport class QualificationCollection extends SmrtCollection<Qualification> { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n static readonly _itemClass = Qualification;\n}\n/** Scoped held-qualification collection; closed like its model. */\n@smrt({ api: { include: [] }, mcp: { include: [] }, cli: false })\nexport class HeldQualificationCollection extends SmrtCollection<HeldQualification> { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n static readonly _itemClass = HeldQualification;\n}\n/** Scoped append-only change collection; closed like its model. */\n@smrt({ api: { include: [] }, mcp: { include: [] }, cli: false })\nexport class HeldQualificationChangeCollection extends SmrtCollection<HeldQualificationChange> { static __smrtPackage__ = \"@happyvertical/smrt-human-resources\";\n static readonly _itemClass = HeldQualificationChange;\n}\n","import type { DatabaseInterface } from '@happyvertical/sql';\nimport type { HrEventQueue } from '../service-base.js';\nimport type { HrActor, IsoDate } from '../types.js';\nimport { insertHr, persistHr } from '../write.js';\nimport {\n type HeldQualification,\n HeldQualificationChange,\n HeldQualificationChangeCollection,\n HeldQualificationCollection,\n} from './models.js';\n\n/** Reason recorded when an employment-scoped qualification ends with its employment. */\nexport const EMPLOYMENT_ENDED_REASON = 'employment-ended';\n\n/**\n * End every qualification that belongs to an employment, from `effectiveOn`\n * (the day after the last day employed). Called by EmploymentService inside\n * the transaction that ends the employment, so the two never disagree.\n * Person-scoped qualifications are untouched.\n *\n * The decision is made per renewal chain, from the chain's dated history and\n * not from any stored status: the latest row of a chain gets the\n * `employment-ended` revocation, and one event, unless a revocation effective\n * on or before `effectiveOn` is already recorded anywhere in the chain. The\n * rows that latest row renewed are not written: the dated replay cuts them\n * off through the chain. A latest row whose stored status is already\n * `revoked` (a later-dated revocation was recorded) is not saved again; only\n * the change row is appended, and the replay treats the earliest revocation\n * as in force. Returns the latest rows that were cut off.\n */\nexport async function revokeEmploymentQualifications(\n db: DatabaseInterface,\n actor: Readonly<HrActor>,\n employmentId: string,\n effectiveOn: IsoDate,\n queue: HrEventQueue,\n): Promise<HeldQualification[]> {\n const held = await HeldQualificationCollection.create({ db });\n const rows = await held.list({\n where: { tenantId: actor.tenantId, employmentId },\n });\n if (rows.length === 0) return [];\n const changes = await HeldQualificationChangeCollection.create({ db });\n const revokedOn = new Map<string, IsoDate>();\n for (const change of await changes.list({\n where: {\n tenantId: actor.tenantId,\n heldQualificationId: rows.map((row) => row.id as string),\n kind: 'revoked',\n },\n })) {\n const earliest = revokedOn.get(change.heldQualificationId);\n if (earliest === undefined || change.effectiveOn < earliest)\n revokedOn.set(change.heldQualificationId, change.effectiveOn);\n }\n const byId = new Map(rows.map((row) => [row.id as string, row]));\n const renewed = new Set(rows.map((row) => row.renewalOfId));\n const cut: HeldQualification[] = [];\n for (const row of rows) {\n if (renewed.has(row.id as string)) continue;\n // The earliest revocation recorded on the latest row or any row it renews.\n let already: IsoDate | undefined;\n const seen = new Set<string>();\n for (\n let member: HeldQualification | undefined = row;\n member && !seen.has(member.id as string);\n member = member.renewalOfId ? byId.get(member.renewalOfId) : undefined\n ) {\n seen.add(member.id as string);\n const on = revokedOn.get(member.id as string);\n if (on !== undefined && (already === undefined || on < already))\n already = on;\n }\n if (already !== undefined && already <= effectiveOn) continue;\n if (row.status !== 'revoked') {\n row.status = 'revoked';\n await persistHr(row);\n }\n await insertHr(HeldQualificationChange, db, {\n tenantId: actor.tenantId,\n heldQualificationId: row.id as string,\n kind: 'revoked',\n effectiveOn,\n reason: EMPLOYMENT_ENDED_REASON,\n actorProfileId: actor.profileId,\n });\n queue({\n type: 'held-qualification.revoked',\n heldQualification: row,\n effectiveOn,\n at: new Date(),\n byProfileId: actor.profileId,\n });\n cut.push(row);\n }\n return cut;\n}\n","import { createLogger } from '@happyvertical/logger';\nimport {\n isEmbeddedDatabase,\n isPostgresDatabase,\n withEmbeddedWriteQueue,\n} from '@happyvertical/smrt-core';\nimport { getTenantId, withTenant } from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface, TransactionHandle } from '@happyvertical/sql';\nimport {\n type HrActor,\n HrError,\n type HrEvent,\n type HrServiceOptions,\n} from './types.js';\n\nconst logger = createLogger({ level: 'info' });\n\nfunction isNestedTransactionError(error: unknown): boolean {\n return error instanceof Error && error.name === 'NestedTransactionError';\n}\n\n/** Collects events inside a transaction; they are delivered after it commits. */\nexport type HrEventQueue = (event: HrEvent) => void;\n\n/**\n * Shared plumbing for the HR services: a trusted tenant/profile actor, scoped\n * reads, serialized transactional writes, and after-commit event delivery.\n * Applications authorize the actor before constructing a service. Reads and\n * writes reject conflicting ambient tenancy.\n *\n * Construct a service with the root database handle. A mutation begins and\n * commits its own transaction with `beginTransaction()` and then delivers its\n * events; on a handle that is already inside a transaction (the `tx` of\n * `db.transaction()`, or a `beginTransaction()` handle) the commit would\n * belong to the caller, so a mutation is refused with\n * `HR_TRANSACTION_UNSUPPORTED` before anything is written. Such a handle\n * either has no `beginTransaction` or refuses it with the SDK's\n * `NestedTransactionError`; the service never opens a nested (savepoint)\n * transaction. Reads work on any handle.\n */\nexport abstract class HrService {\n protected readonly actor: Readonly<HrActor>;\n private readonly onEvent: HrServiceOptions['onEvent'];\n\n constructor(\n protected readonly db: DatabaseInterface,\n actor: HrActor,\n options: HrServiceOptions = {},\n ) {\n if (!actor?.tenantId?.trim() || !actor?.profileId?.trim())\n throw new HrError(\n 'HR_ACTOR_INVALID',\n 'HR services require a trusted tenant/profile actor.',\n );\n this.actor = Object.freeze({\n tenantId: this.identity(actor.tenantId),\n profileId: this.identity(actor.profileId),\n });\n this.onEvent = options.onEvent;\n }\n\n /** Normalize an id; PostgreSQL stores identities as UUIDs. */\n protected identity(value: string): string {\n if (!isPostgresDatabase(this.db)) return value;\n const hex = value.replace(/[{}-]/g, '').toLowerCase();\n if (!/^[0-9a-f]{32}$/.test(hex))\n throw new HrError(\n 'HR_INVALID',\n 'PostgreSQL HR identities must be UUIDs.',\n );\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n }\n\n /**\n * A required id from caller input, trimmed and normalized.\n *\n * @throws HrError `HR_INVALID` when it is not text or is blank\n */\n protected id(fieldName: string, value: unknown): string {\n if (typeof value !== 'string' || !value.trim())\n throw new HrError('HR_INVALID', `${fieldName} is required.`);\n return this.identity(value.trim());\n }\n\n /** An optional id from caller input: null or undefined is null. */\n protected optionalId(fieldName: string, value: unknown): string | null {\n return value === null || value === undefined\n ? null\n : this.id(fieldName, value);\n }\n\n /** Run a read inside the actor's tenant. */\n protected scope<T>(run: () => Promise<T>): Promise<T> {\n const active = getTenantId();\n if (active && this.identity(active) !== this.actor.tenantId)\n throw new HrError(\n 'HR_TENANT_MISMATCH',\n 'HR actor differs from active tenant.',\n );\n return withTenant({ tenantId: this.actor.tenantId }, run);\n }\n\n /**\n * Run a mutation in one transaction, serialized per tenant, then deliver the\n * events it queued. Nothing is delivered when the transaction fails.\n *\n * @throws HrError `HR_TRANSACTION_UNSUPPORTED` when the service was constructed with a handle that is already inside a transaction\n */\n protected async transact<T>(\n run: (db: DatabaseInterface, queue: HrEventQueue) => Promise<T>,\n ): Promise<T> {\n // `@happyvertical/sql` has no after-commit hook and no marker for \"inside\n // a transaction\", so the service begins a transaction of its own and\n // commits it itself; it never calls `transaction()`, which on a\n // transaction-scoped handle would re-enter the caller's transaction under\n // a savepoint. Only a root handle can begin one: a transaction-scoped\n // handle has no `beginTransaction` (libsql SQLite, PostgreSQL, DuckDB,\n // JSON) or refuses it with `NestedTransactionError` before touching the\n // connection (the native-capabilities SQLite adapter). Both fail closed.\n const unsupported = () =>\n new HrError(\n 'HR_TRANSACTION_UNSUPPORTED',\n 'HR services must be constructed with the root database handle, not one that is already inside a transaction: events are delivered when the service commits its own transaction.',\n );\n const begin = this.db.beginTransaction;\n if (typeof begin !== 'function') throw unsupported();\n const events: HrEvent[] = [];\n const result = await this.scope(() =>\n withEmbeddedWriteQueue(this.db, isEmbeddedDatabase(this.db), async () => {\n let tx: TransactionHandle;\n try {\n tx = await begin.call(this.db);\n } catch (error) {\n // Matched by name: the sql root (pg, node:fs) is not importable from\n // a browser-safe model root, and the SDK sets this name on the class.\n if (isNestedTransactionError(error)) throw unsupported();\n throw error;\n }\n let value: T;\n try {\n if (isPostgresDatabase(this.db))\n await tx.query(\n 'SELECT pg_advisory_xact_lock(hashtextextended($1, 0))',\n JSON.stringify(['smrt.human-resources', this.actor.tenantId]),\n );\n value = await run(tx, (event) => events.push(event));\n } catch (error) {\n await tx.rollback().catch(() => undefined);\n throw error;\n }\n // A failed commit ends the handle itself (the SDK rolls back and\n // releases the connection), so it is not rolled back again here.\n await tx.commit();\n return value;\n }),\n );\n for (const event of events) await this.emit(event);\n return result;\n }\n\n private async emit(event: HrEvent): Promise<void> {\n if (!this.onEvent) return;\n try {\n await this.onEvent(event);\n } catch (error) {\n logger.error(\n `[smrt-human-resources] onEvent handler failed for ${event.type}`,\n { error: error instanceof Error ? error.message : String(error) },\n );\n }\n }\n}\n","import type { DatabaseInterface } from '@happyvertical/sql';\nimport { dateWithin } from '../dates.js';\nimport type { IsoDate } from '../types.js';\nimport {\n type Employment,\n EmploymentCollection,\n type EmploymentTerm,\n EmploymentTermCollection,\n} from './models.js';\n\n/**\n * Terms that cover `on` for one tenant. Run inside the tenant's scope; the\n * caller supplies a validated date. Shared by both services so \"employed on a\n * date\" has one definition.\n */\nexport async function termsCovering(\n db: DatabaseInterface,\n tenantId: string,\n on: IsoDate,\n): Promise<EmploymentTerm[]> {\n const terms = await EmploymentTermCollection.create({ db });\n const started = await terms.list({\n where: { tenantId, 'startedOn <=': on },\n });\n return started.filter((term) => dateWithin(on, term.startedOn, term.endedOn));\n}\n\n/** Employments with a term covering `on`, for one tenant. */\nexport async function employmentsOn(\n db: DatabaseInterface,\n tenantId: string,\n on: IsoDate,\n): Promise<Employment[]> {\n const ids = [\n ...new Set(\n (await termsCovering(db, tenantId, on)).map((t) => t.employmentId),\n ),\n ];\n if (ids.length === 0) return [];\n const employments = await EmploymentCollection.create({ db });\n return employments.list({ where: { tenantId, id: ids } });\n}\n","import type { DatabaseInterface } from '@happyvertical/sql';\nimport { addDays, assertIsoDate, dateWithin } from '../dates.js';\nimport { revokeEmploymentQualifications } from '../qualifications/internal.js';\nimport { HrService } from '../service-base.js';\nimport {\n EMPLOYMENT_STATUS_TRANSITIONS,\n type EmploymentChangeKind,\n type EmploymentCheck,\n type EmploymentStatus,\n HrError,\n type IsoDate,\n type WorkerType,\n} from '../types.js';\nimport { insertHr, persistHr } from '../write.js';\nimport {\n Employment,\n EmploymentChange,\n EmploymentChangeCollection,\n EmploymentCollection,\n EmploymentTerm,\n EmploymentTermCollection,\n} from './models.js';\nimport { employmentsOn } from './queries.js';\n\n/** Input to {@link EmploymentService.hire}. */\nexport interface HireInput {\n /** The person being employed (`smrt-profiles:Profile` id). */\n profileId: string;\n /** Free text, unique per tenant; surrounding whitespace is trimmed. */\n employeeNumber: string;\n /** First day employed. */\n startedOn: IsoDate;\n /** Lowercase kebab-case; defaults to `employee`. */\n workerType?: WorkerType;\n /** Position or trade label; blank means none. */\n position?: string | null;\n /** Optional login (`smrt-users:User` id). */\n userId?: string | null;\n /** Optional organization profile when it differs from the tenant itself. */\n employerProfileId?: string | null;\n}\n\n/** Input to {@link EmploymentService.end}. */\nexport interface EndEmploymentInput {\n /** The last day employed, inclusive. */\n endedOn: IsoDate;\n /** Free-text reason kept on the closed term and the change row. */\n reason?: string;\n}\n\n/** Input to {@link EmploymentService.rehire}. */\nexport interface RehireInput {\n /** First day of the new term; must be after the previous term's last day. */\n startedOn: IsoDate;\n /** New worker type; omitted keeps the previous one. */\n workerType?: WorkerType;\n /** New position (null clears it); omitted keeps the previous one. */\n position?: string | null;\n}\n\n/** Input to {@link EmploymentService.changePosition}. */\nexport interface ChangePositionInput {\n /** The new position; null or blank clears it. */\n position: string | null;\n /** The day the new position takes effect. */\n effectiveOn: IsoDate;\n /** Optional note kept on the change row. */\n note?: string;\n}\n\n/** Input to {@link EmploymentService.changeWorkerType}. */\nexport interface ChangeWorkerTypeInput {\n /** The new worker type, lowercase kebab-case. */\n workerType: WorkerType;\n /** The day the new worker type takes effect. */\n effectiveOn: IsoDate;\n /** Optional note kept on the change row. */\n note?: string;\n}\n\n/** Input to {@link EmploymentService.placeOnLeave} and {@link EmploymentService.returnFromLeave}. */\nexport interface LeaveInput {\n /** The first day of leave, or the first day back. */\n effectiveOn: IsoDate;\n /** Optional note kept on the change row. */\n note?: string;\n}\n\n/** Input to {@link EmploymentService.linkLogin} and {@link EmploymentService.unlinkLogin}. */\nexport interface LoginLinkInput {\n /**\n * The date recorded on the change row. It does not schedule anything: the\n * link or unlink takes effect as soon as it is recorded.\n */\n effectiveOn: IsoDate;\n}\n\n/** An employment's recorded state on one calendar date. */\nexport interface EmploymentAsOf {\n /** Position in effect on the date; before any change, the position at hire. */\n position: string | null;\n /** Worker type in effect on the date; before any change, the type at hire. */\n workerType: string;\n /** Whether a term covers the date. */\n employed: boolean;\n /** Whether the person was employed and on leave on the date. */\n onLeave: boolean;\n}\n\nconst WORKER_TYPE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * Changes that belong to a term and are replayed by `check` and `asOf`. Login\n * links are not: they are not replayed and may be recorded at any time.\n */\nconst TERM_CHANGE_KINDS: readonly EmploymentChangeKind[] = [\n 'hired',\n 'ended',\n 'rehired',\n 'position-changed',\n 'worker-type-changed',\n 'leave-started',\n 'leave-ended',\n];\n\nconst LEAVE_CHANGE_KINDS: readonly EmploymentChangeKind[] = [\n 'leave-started',\n 'leave-ended',\n];\n\nfunction workerTypeOf(value: unknown): WorkerType {\n if (typeof value !== 'string' || !WORKER_TYPE.test(value))\n throw new HrError(\n 'HR_INVALID',\n `workerType must be lowercase kebab-case, got '${String(value)}'.`,\n );\n return value;\n}\n\nfunction positionOf(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n if (typeof value !== 'string')\n throw new HrError('HR_INVALID', 'position must be text or null.');\n return value.trim() || null;\n}\n\nfunction noteOf(value: unknown): string {\n if (value === undefined || value === null) return '';\n if (typeof value !== 'string')\n throw new HrError('HR_INVALID', 'A note or reason must be text.');\n return value.trim();\n}\n\nfunction createdTime(change: EmploymentChange): number {\n const at = change.created_at;\n const time = at ? new Date(at).getTime() : 0;\n return Number.isNaN(time) ? 0 : time;\n}\n\nfunction inHistoryOrder(changes: EmploymentChange[]): EmploymentChange[] {\n return [...changes].sort((a, b) =>\n a.effectiveOn === b.effectiveOn\n ? createdTime(a) - createdTime(b)\n : a.effectiveOn < b.effectiveOn\n ? -1\n : 1,\n );\n}\n\n/** Whether the replayed history leaves the person on leave on `on`. */\nfunction onLeaveOn(history: EmploymentChange[], on: IsoDate): boolean {\n let onLeave = false;\n for (const change of history) {\n if (change.effectiveOn > on) break;\n if (change.kind === 'leave-started') onLeave = true;\n // A new term starts at work; `ended` is the last day and is still on leave.\n else if (change.kind === 'leave-ended' || change.kind === 'rehired')\n onLeave = false;\n }\n return onLeave;\n}\n\n/**\n * The value of a dated attribute on `on`: the latest change on or before it,\n * else the value before the first change, else the current value.\n */\nfunction valueOn(\n history: EmploymentChange[],\n kind: EmploymentChangeKind,\n on: IsoDate,\n current: string | null,\n): string | null {\n const ofKind = history.filter((change) => change.kind === kind);\n if (ofKind.length === 0) return current;\n let value = ofKind[0].fromValue;\n for (const change of ofKind) {\n if (change.effectiveOn > on) break;\n value = change.toValue;\n }\n return value;\n}\n\n/**\n * The only writer of employment records. Construct it from a trusted\n * `{ tenantId, profileId }` actor the application has already authorized.\n * Every mutation runs in one transaction, appends a dated\n * {@link EmploymentChange} attributed to the actor, and delivers its event\n * after the transaction commits. Dates are calendar dates (`YYYY-MM-DD`).\n */\nexport class EmploymentService extends HrService {\n private async load(db: DatabaseInterface, id: string): Promise<Employment> {\n const employments = await EmploymentCollection.create({ db });\n const [employment] = await employments.list({\n where: { tenantId: this.actor.tenantId, id: this.id('employmentId', id) },\n limit: 1,\n });\n if (!employment)\n throw new HrError('HR_NOT_FOUND', 'Employment not found in this tenant.');\n return employment;\n }\n\n private async one(\n where: Record<string, unknown>,\n db: DatabaseInterface = this.db,\n ): Promise<Employment | null> {\n const employments = await EmploymentCollection.create({ db });\n const [employment] = await employments.list({\n where: { tenantId: this.actor.tenantId, ...where },\n limit: 1,\n });\n return employment ?? null;\n }\n\n private async termsOf(\n db: DatabaseInterface,\n employmentId: string,\n ): Promise<EmploymentTerm[]> {\n const terms = await EmploymentTermCollection.create({ db });\n const rows = await terms.list({\n where: { tenantId: this.actor.tenantId, employmentId },\n });\n return rows.sort((a, b) =>\n a.startedOn < b.startedOn ? -1 : a.startedOn > b.startedOn ? 1 : 0,\n );\n }\n\n private async changesOf(\n db: DatabaseInterface,\n employmentId: string,\n ): Promise<EmploymentChange[]> {\n const changes = await EmploymentChangeCollection.create({ db });\n return inHistoryOrder(\n await changes.list({\n where: { tenantId: this.actor.tenantId, employmentId },\n }),\n );\n }\n\n /**\n * Append a change. Creation instants are strictly increasing within one\n * employment (writes are serialized per tenant), so same-day changes replay\n * in the order they were recorded.\n */\n private async record(\n db: DatabaseInterface,\n employmentId: string,\n change: {\n kind: EmploymentChangeKind;\n effectiveOn: IsoDate;\n fromValue?: string | null;\n toValue?: string | null;\n note?: string;\n },\n ): Promise<void> {\n const changes = await EmploymentChangeCollection.create({ db });\n const latest = (\n await changes.list({\n where: { tenantId: this.actor.tenantId, employmentId },\n })\n ).reduce((max, row) => Math.max(max, createdTime(row)), 0);\n await insertHr(EmploymentChange, db, {\n tenantId: this.actor.tenantId,\n employmentId,\n kind: change.kind,\n effectiveOn: change.effectiveOn,\n fromValue: change.fromValue ?? null,\n toValue: change.toValue ?? null,\n note: change.note ?? '',\n actorProfileId: this.actor.profileId,\n created_at: new Date(Math.max(Date.now(), latest + 1)),\n });\n }\n\n /**\n * The term a dated change belongs to, with `effectiveOn` inside it: the\n * latest term, which is the open one until an end is recorded and the final\n * one (up to its last day) afterwards. The stored status plays no part, so\n * a change dated inside a notice period is accepted.\n *\n * @throws HrError `HR_INVALID` before the term started, `HR_STATUS_TRANSITION` after its last day\n */\n private async latestTerm(\n db: DatabaseInterface,\n employment: Employment,\n effectiveOn: IsoDate,\n ): Promise<EmploymentTerm> {\n const term = (await this.termsOf(db, employment.id as string)).at(-1);\n if (!term)\n throw new HrError('HR_STATUS_TRANSITION', 'The employment has no term.');\n if (effectiveOn < term.startedOn)\n throw new HrError(\n 'HR_INVALID',\n `${effectiveOn} is before the current term started (${term.startedOn}).`,\n );\n if (term.endedOn !== null && effectiveOn > term.endedOn)\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n `The employment ended on ${term.endedOn}; rehire it first.`,\n );\n return term;\n }\n\n private async notBeforeLatest(\n db: DatabaseInterface,\n employmentId: string,\n kinds: readonly EmploymentChangeKind[],\n effectiveOn: IsoDate,\n ): Promise<void> {\n const latest = (await this.changesOf(db, employmentId))\n .filter((change) => kinds.includes(change.kind))\n .at(-1);\n if (latest && effectiveOn < latest.effectiveOn)\n throw new HrError(\n 'HR_INVALID',\n `${effectiveOn} is earlier than the latest recorded change (${latest.effectiveOn}).`,\n );\n }\n\n /**\n * A login is exclusive to the employment that stores it until it is\n * explicitly unlinked there, whatever that employment's stored status. An\n * ended employment keeps its login through its notice period and after, so\n * two employments never share one and `findByUser(userId, on)` cannot be\n * ambiguous through the service.\n */\n private async loginFree(\n db: DatabaseInterface,\n userId: string,\n employmentId: string | null,\n ): Promise<void> {\n const employments = await EmploymentCollection.create({ db });\n const linked = await employments.list({\n where: { tenantId: this.actor.tenantId, userId },\n });\n if (linked.some((other) => other.id !== employmentId))\n throw new HrError(\n 'HR_INVALID',\n 'This login is already linked to another employment; unlink it there first.',\n );\n }\n\n private transition(employment: Employment, to: EmploymentStatus): void {\n if (!EMPLOYMENT_STATUS_TRANSITIONS[employment.status]?.includes(to))\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n `An employment cannot go from '${employment.status}' to '${to}'.`,\n );\n employment.status = to;\n }\n\n /**\n * Employ a person: creates the employment, its first term and a `hired`\n * change. A profile has one employment per tenant, so a person who worked\n * here before is rehired instead.\n *\n * @throws HrError `HR_ALREADY_EMPLOYED`, `HR_EMPLOYEE_NUMBER_TAKEN`, `HR_INVALID` (including a login already linked to another employment, ended or not: unlink it there first)\n */\n async hire(input: HireInput): Promise<Employment> {\n const profileId = this.id('profileId', input?.profileId);\n const employeeNumber =\n typeof input.employeeNumber === 'string'\n ? input.employeeNumber.trim()\n : '';\n if (!employeeNumber)\n throw new HrError('HR_INVALID', 'employeeNumber is required.');\n const startedOn = assertIsoDate('startedOn', input.startedOn);\n const workerType = workerTypeOf(input.workerType ?? 'employee');\n const position = positionOf(input.position);\n const userId = this.optionalId('userId', input.userId);\n const employerProfileId = this.optionalId(\n 'employerProfileId',\n input.employerProfileId,\n );\n const id = await this.transact(async (db, queue) => {\n if (await this.one({ profileId }, db))\n throw new HrError(\n 'HR_ALREADY_EMPLOYED',\n 'This profile already has an employment in this tenant; rehire it instead.',\n );\n if (await this.one({ employeeNumber }, db))\n throw new HrError(\n 'HR_EMPLOYEE_NUMBER_TAKEN',\n `Employee number '${employeeNumber}' is already used in this tenant.`,\n );\n if (userId) await this.loginFree(db, userId, null);\n const employment = await insertHr(Employment, db, {\n tenantId: this.actor.tenantId,\n profileId,\n userId,\n employerProfileId,\n employeeNumber,\n workerType,\n position,\n status: 'active',\n });\n const employmentId = employment.id as string;\n await insertHr(EmploymentTerm, db, {\n tenantId: this.actor.tenantId,\n employmentId,\n startedOn,\n });\n await this.record(db, employmentId, {\n kind: 'hired',\n effectiveOn: startedOn,\n });\n queue({\n type: 'employment.hired',\n employment,\n effectiveOn: startedOn,\n at: new Date(),\n byProfileId: this.actor.profileId,\n });\n return employmentId;\n });\n return this.get(id);\n }\n\n /**\n * End an employment: closes the open term on `endedOn` (the last day\n * employed, inclusive) and, in the same transaction, revokes its\n * employment-scoped qualifications from the day after, so they are still\n * good on the last day. `endedOn` cannot be earlier than a position,\n * worker-type or leave change already recorded in the term.\n *\n * `endedOn` may be ahead of the day the end is recorded (a notice period).\n * The stored status becomes `ended` at once, meaning \"an end has been\n * recorded\": the person is still employed up to and including `endedOn`,\n * so ask by date with {@link check}, {@link employedOn}, {@link asOf} or\n * `findByUser(userId, on)`. Position, worker-type and leave changes dated\n * inside the final term are still accepted.\n *\n * @throws HrError `HR_STATUS_TRANSITION` when already ended, `HR_INVALID`, `HR_NOT_FOUND`\n */\n async end(\n employmentId: string,\n input: EndEmploymentInput,\n ): Promise<Employment> {\n const endedOn = assertIsoDate('endedOn', input?.endedOn);\n const reason = noteOf(input.reason);\n const id = await this.transact(async (db, queue) => {\n const employment = await this.load(db, employmentId);\n this.transition(employment, 'ended');\n const term = await this.latestTerm(db, employment, endedOn);\n if (term.endedOn !== null)\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n 'The employment has no open term.',\n );\n await this.notBeforeLatest(\n db,\n employment.id as string,\n TERM_CHANGE_KINDS,\n endedOn,\n );\n term.endedOn = endedOn;\n term.endReason = reason;\n await persistHr(term);\n await persistHr(employment);\n await this.record(db, employment.id as string, {\n kind: 'ended',\n effectiveOn: endedOn,\n note: reason,\n });\n await revokeEmploymentQualifications(\n db,\n this.actor,\n employment.id as string,\n addDays(endedOn, 1),\n queue,\n );\n queue({\n type: 'employment.ended',\n employment,\n effectiveOn: endedOn,\n at: new Date(),\n byProfileId: this.actor.profileId,\n });\n return employment.id as string;\n });\n return this.get(id);\n }\n\n /**\n * Rehire an ended employment: adds a term to the same row, so the\n * employment id stays stable. The new term cannot start before a position,\n * worker-type or leave change already recorded. The employment keeps the\n * login it stored, which no other employment can have taken meanwhile.\n *\n * @throws HrError `HR_STATUS_TRANSITION` unless ended, `HR_TERM_OVERLAP`, `HR_INVALID`, `HR_NOT_FOUND`\n */\n async rehire(employmentId: string, input: RehireInput): Promise<Employment> {\n const startedOn = assertIsoDate('startedOn', input?.startedOn);\n const workerType =\n input.workerType === undefined\n ? undefined\n : workerTypeOf(input.workerType);\n const position =\n input.position === undefined ? undefined : positionOf(input.position);\n const id = await this.transact(async (db, queue) => {\n const employment = await this.load(db, employmentId);\n if (employment.status !== 'ended')\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n `Only an ended employment can be rehired; this one is '${employment.status}'.`,\n );\n const id = employment.id as string;\n const previous = await this.termsOf(db, id);\n const overlapping = previous.find(\n (term) => term.endedOn === null || startedOn <= term.endedOn,\n );\n if (overlapping)\n throw new HrError(\n 'HR_TERM_OVERLAP',\n `A rehire must start after the previous term ended (${overlapping.endedOn ?? 'still open'}).`,\n );\n await this.notBeforeLatest(db, id, TERM_CHANGE_KINDS, startedOn);\n // Backstop: the service never lets a stored login be linked elsewhere.\n if (employment.userId) await this.loginFree(db, employment.userId, id);\n const from = {\n position: employment.position,\n workerType: employment.workerType,\n };\n this.transition(employment, 'active');\n if (position !== undefined) employment.position = position;\n if (workerType !== undefined) employment.workerType = workerType;\n await persistHr(employment);\n await insertHr(EmploymentTerm, db, {\n tenantId: this.actor.tenantId,\n employmentId: id,\n startedOn,\n });\n await this.record(db, id, { kind: 'rehired', effectiveOn: startedOn });\n if (employment.position !== from.position)\n await this.record(db, id, {\n kind: 'position-changed',\n effectiveOn: startedOn,\n fromValue: from.position,\n toValue: employment.position,\n });\n if (employment.workerType !== from.workerType)\n await this.record(db, id, {\n kind: 'worker-type-changed',\n effectiveOn: startedOn,\n fromValue: from.workerType,\n toValue: employment.workerType,\n });\n queue({\n type: 'employment.rehired',\n employment,\n effectiveOn: startedOn,\n at: new Date(),\n byProfileId: this.actor.profileId,\n });\n return id;\n });\n return this.get(id);\n }\n\n /**\n * Record a new position from `effectiveOn`. The employment row holds the\n * latest value; the change row carries the old and new ones.\n *\n * `effectiveOn` must fall inside the latest term. Once an end has been\n * recorded that is the final term, up to and including its `endedOn`, so a\n * change during a notice period is accepted; the stored status stays\n * `ended`.\n *\n * @throws HrError `HR_STATUS_TRANSITION` when `effectiveOn` is after the last day of an ended employment; `HR_INVALID` for a no-op, a date before the term started or before the latest position change; `HR_NOT_FOUND`\n */\n async changePosition(\n employmentId: string,\n input: ChangePositionInput,\n ): Promise<Employment> {\n const effectiveOn = assertIsoDate('effectiveOn', input?.effectiveOn);\n const position = positionOf(input.position);\n const note = noteOf(input.note);\n const id = await this.transact(async (db, queue) => {\n const employment = await this.load(db, employmentId);\n const id = employment.id as string;\n await this.latestTerm(db, employment, effectiveOn);\n if (employment.position === position)\n throw new HrError(\n 'HR_INVALID',\n 'The employment already has this position.',\n );\n await this.notBeforeLatest(db, id, ['position-changed'], effectiveOn);\n const fromValue = employment.position;\n employment.position = position;\n await persistHr(employment);\n await this.record(db, id, {\n kind: 'position-changed',\n effectiveOn,\n fromValue,\n toValue: position,\n note,\n });\n queue({\n type: 'employment.position-changed',\n employment,\n effectiveOn,\n at: new Date(),\n byProfileId: this.actor.profileId,\n });\n return id;\n });\n return this.get(id);\n }\n\n /**\n * Record a new worker type from `effectiveOn`. The employment row holds the\n * latest value; the change row carries the old and new ones.\n *\n * `effectiveOn` must fall inside the latest term. Once an end has been\n * recorded that is the final term, up to and including its `endedOn`, so a\n * change during a notice period is accepted; the stored status stays\n * `ended`.\n *\n * @throws HrError `HR_STATUS_TRANSITION` when `effectiveOn` is after the last day of an ended employment; `HR_INVALID` for a no-op, a bad value, a date before the term started or before the latest worker-type change; `HR_NOT_FOUND`\n */\n async changeWorkerType(\n employmentId: string,\n input: ChangeWorkerTypeInput,\n ): Promise<Employment> {\n const effectiveOn = assertIsoDate('effectiveOn', input?.effectiveOn);\n const workerType = workerTypeOf(input.workerType);\n const note = noteOf(input.note);\n const id = await this.transact(async (db, queue) => {\n const employment = await this.load(db, employmentId);\n const id = employment.id as string;\n await this.latestTerm(db, employment, effectiveOn);\n if (employment.workerType === workerType)\n throw new HrError(\n 'HR_INVALID',\n 'The employment already has this worker type.',\n );\n await this.notBeforeLatest(db, id, ['worker-type-changed'], effectiveOn);\n const fromValue = employment.workerType;\n employment.workerType = workerType;\n await persistHr(employment);\n await this.record(db, id, {\n kind: 'worker-type-changed',\n effectiveOn,\n fromValue,\n toValue: workerType,\n note,\n });\n queue({\n type: 'employment.worker-type-changed',\n employment,\n effectiveOn,\n at: new Date(),\n byProfileId: this.actor.profileId,\n });\n return id;\n });\n return this.get(id);\n }\n\n private async leave(\n employmentId: string,\n input: LeaveInput,\n to: 'active' | 'on-leave',\n ): Promise<Employment> {\n const effectiveOn = assertIsoDate('effectiveOn', input?.effectiveOn);\n const note = noteOf(input.note);\n const kind = to === 'on-leave' ? 'leave-started' : 'leave-ended';\n const id = await this.transact(async (db, queue) => {\n const employment = await this.load(db, employmentId);\n const id = employment.id as string;\n if (employment.status === 'ended') {\n // An end is recorded: the stored status no longer says whether the\n // person is on leave, so the final term's replayed history decides,\n // and the status stays `ended` (`ended → active` is only a rehire).\n await this.latestTerm(db, employment, effectiveOn);\n await this.notBeforeLatest(db, id, LEAVE_CHANGE_KINDS, effectiveOn);\n const onLeave = onLeaveOn(await this.changesOf(db, id), effectiveOn);\n if (onLeave === (to === 'on-leave'))\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n onLeave\n ? `The employment is already on leave on ${effectiveOn}.`\n : `The employment is not on leave on ${effectiveOn}.`,\n );\n } else {\n this.transition(employment, to);\n await this.latestTerm(db, employment, effectiveOn);\n await this.notBeforeLatest(db, id, LEAVE_CHANGE_KINDS, effectiveOn);\n await persistHr(employment);\n }\n await this.record(db, id, { kind, effectiveOn, note });\n queue({\n type: `employment.${kind}`,\n employment,\n effectiveOn,\n at: new Date(),\n byProfileId: this.actor.profileId,\n });\n return id;\n });\n return this.get(id);\n }\n\n /**\n * Place an employment on leave from `effectiveOn`. A person on leave is\n * still employed.\n *\n * Once an end has been recorded, leave dated inside the final term (up to\n * and including its `endedOn`) is still accepted: it is recorded as a\n * change row, the stored status stays `ended`, and whether the person is\n * already on leave is decided by replaying the history as of `effectiveOn`.\n *\n * @throws HrError `HR_STATUS_TRANSITION` when already on leave, or when `effectiveOn` is after the last day of an ended employment; `HR_INVALID` for a date before the term started or before the latest leave change; `HR_NOT_FOUND`\n */\n async placeOnLeave(\n employmentId: string,\n input: LeaveInput,\n ): Promise<Employment> {\n return this.leave(employmentId, input, 'on-leave');\n }\n\n /**\n * Return an employment from leave; `effectiveOn` is the first day back.\n *\n * Once an end has been recorded, a return dated inside the final term (up\n * to and including its `endedOn`) is still accepted: it is recorded as a\n * change row, the stored status stays `ended`, and whether the person is\n * on leave is decided by replaying the history as of `effectiveOn`.\n *\n * @throws HrError `HR_STATUS_TRANSITION` unless on leave, or when `effectiveOn` is after the last day of an ended employment; `HR_INVALID` for a date before the term started or before the latest leave change; `HR_NOT_FOUND`\n */\n async returnFromLeave(\n employmentId: string,\n input: LeaveInput,\n ): Promise<Employment> {\n return this.leave(employmentId, input, 'active');\n }\n\n private async login(\n employmentId: string,\n userId: string | null,\n input: LoginLinkInput,\n ): Promise<Employment> {\n const effectiveOn = assertIsoDate('effectiveOn', input?.effectiveOn);\n const id = await this.transact(async (db) => {\n const employment = await this.load(db, employmentId);\n const id = employment.id as string;\n if (employment.userId === userId)\n throw new HrError(\n 'HR_INVALID',\n userId\n ? 'This login is already linked to the employment.'\n : 'The employment has no linked login.',\n );\n if (userId) await this.loginFree(db, userId, id);\n const fromValue = employment.userId;\n employment.userId = userId;\n await persistHr(employment);\n await this.record(db, id, {\n kind: userId ? 'login-linked' : 'login-unlinked',\n effectiveOn,\n fromValue,\n toValue: userId,\n });\n return id;\n });\n return this.get(id);\n }\n\n /**\n * Link a login (`smrt-users:User` id) to an employment, replacing any\n * previous one. Grants nothing by itself: permissions stay with smrt-users.\n * A login belongs to one employment at a time and stays with it, ended or\n * not, until {@link unlinkLogin} clears it there.\n *\n * The link takes effect at once, whatever `effectiveOn` says: the date is\n * only recorded on the change row, and login links are not replayed by\n * {@link asOf} or `findByUser(userId, on)`.\n *\n * @throws HrError `HR_INVALID` when already linked to this login, or to another employment (unlink it there first); `HR_NOT_FOUND`\n */\n async linkLogin(\n employmentId: string,\n userId: string,\n input: LoginLinkInput,\n ): Promise<Employment> {\n return this.login(employmentId, this.id('userId', userId), input);\n }\n\n /**\n * Clear an employment's login, which frees it for another employment. Works\n * on an ended employment too. The login is cleared at once, whatever\n * `effectiveOn` says; a future date does not keep it linked until then.\n *\n * @throws HrError `HR_INVALID` when no login is linked, `HR_NOT_FOUND`\n */\n async unlinkLogin(\n employmentId: string,\n input: LoginLinkInput,\n ): Promise<Employment> {\n return this.login(employmentId, null, input);\n }\n\n /**\n * One employment in the actor's tenant.\n *\n * @throws HrError `HR_NOT_FOUND` when it does not exist in this tenant\n */\n async get(employmentId: string): Promise<Employment> {\n return this.scope(() => this.load(this.db, employmentId));\n }\n\n /** The profile's employment in this tenant (ended or not), or null. */\n async findByProfile(profileId: string): Promise<Employment | null> {\n if (typeof profileId !== 'string' || !profileId.trim()) return null;\n const id = this.identity(profileId.trim());\n return this.scope(() => this.one({ profileId: id }));\n }\n\n /** The employment with this employee number in this tenant, or null. */\n async findByEmployeeNumber(\n employeeNumber: string,\n ): Promise<Employment | null> {\n if (typeof employeeNumber !== 'string' || !employeeNumber.trim())\n return null;\n return this.scope(() =>\n this.one({ employeeNumber: employeeNumber.trim() }),\n );\n }\n\n /**\n * The employment linked to a login. Fails closed: null when there is none,\n * or when more than one matches (the service keeps a login on one\n * employment, so that takes rows written some other way).\n *\n * With `on`, the answer comes from the dated terms: the employment linked\n * to the login that has a term covering `on`, whatever its stored status.\n * Without `on`, it is the linked employment whose stored status is not\n * `ended`, which stops resolving as soon as an end is recorded. A host that\n * records end dates ahead of time (a notice period) should pass the date,\n * or the worker is locked out before their last day.\n *\n * @throws HrError `HR_INVALID` when `on` is given and is not a calendar date\n */\n async findByUser(userId: string, on?: IsoDate): Promise<Employment | null> {\n const date = on === undefined ? null : assertIsoDate('on', on);\n if (typeof userId !== 'string' || !userId.trim()) return null;\n const id = this.identity(userId.trim());\n return this.scope(async () => {\n const employments = await EmploymentCollection.create({ db: this.db });\n const linked = await employments.list({\n where: { tenantId: this.actor.tenantId, userId: id },\n });\n const matching: Employment[] = [];\n for (const employment of linked) {\n const matches =\n date === null\n ? employment.status !== 'ended'\n : (await this.termsOf(this.db, employment.id as string)).some(\n (term) => dateWithin(date, term.startedOn, term.endedOn),\n );\n if (matches) matching.push(employment);\n }\n return matching.length === 1 ? matching[0] : null;\n });\n }\n\n /** Everyone with a term covering `on`, including people on leave. */\n async employedOn(on: IsoDate): Promise<Employment[]> {\n const date = assertIsoDate('on', on);\n return this.scope(() => employmentsOn(this.db, this.actor.tenantId, date));\n }\n\n /**\n * Whether a profile is employed on a date. Answered from terms, never from\n * the stored status; `onLeave` is computed as of `on`, so past dates answer\n * correctly.\n */\n async check(profileId: string, on: IsoDate): Promise<EmploymentCheck> {\n const date = assertIsoDate('on', on);\n if (typeof profileId !== 'string' || !profileId.trim())\n return { ok: false, reason: 'not-employed' };\n const id = this.identity(profileId.trim());\n return this.scope(async () => {\n const employment = await this.one({ profileId: id });\n if (!employment) return { ok: false, reason: 'not-employed' };\n const employmentId = employment.id as string;\n const terms = await this.termsOf(this.db, employmentId);\n if (terms.length === 0) return { ok: false, reason: 'not-employed' };\n if (!terms.some((term) => dateWithin(date, term.startedOn, term.endedOn)))\n return {\n ok: false,\n reason: date < terms[0].startedOn ? 'not-started' : 'ended',\n };\n return {\n ok: true,\n employmentId,\n onLeave: onLeaveOn(await this.changesOf(this.db, employmentId), date),\n };\n });\n }\n\n /**\n * An employment's terms, oldest first.\n *\n * @throws HrError `HR_NOT_FOUND` when the employment is not in this tenant\n */\n async terms(employmentId: string): Promise<EmploymentTerm[]> {\n return this.scope(async () => {\n const employment = await this.load(this.db, employmentId);\n return this.termsOf(this.db, employment.id as string);\n });\n }\n\n /**\n * An employment's dated changes, by effective date and then in the order\n * they were recorded.\n *\n * @throws HrError `HR_NOT_FOUND` when the employment is not in this tenant\n */\n async history(employmentId: string): Promise<EmploymentChange[]> {\n return this.scope(async () => {\n const employment = await this.load(this.db, employmentId);\n return this.changesOf(this.db, employment.id as string);\n });\n }\n\n /**\n * An employment's position, worker type and standing on one date, replayed\n * from the change history. Before the first term the values are those at\n * hire and `employed` is false.\n *\n * @throws HrError `HR_NOT_FOUND` when the employment is not in this tenant\n */\n async asOf(employmentId: string, on: IsoDate): Promise<EmploymentAsOf> {\n const date = assertIsoDate('on', on);\n return this.scope(async () => {\n const employment = await this.load(this.db, employmentId);\n const id = employment.id as string;\n const history = await this.changesOf(this.db, id);\n const employed = (await this.termsOf(this.db, id)).some((term) =>\n dateWithin(date, term.startedOn, term.endedOn),\n );\n return {\n position: valueOn(\n history,\n 'position-changed',\n date,\n employment.position,\n ),\n workerType:\n valueOn(\n history,\n 'worker-type-changed',\n date,\n employment.workerType,\n ) ?? employment.workerType,\n employed,\n onLeave: employed && onLeaveOn(history, date),\n };\n });\n }\n}\n","import type { DatabaseInterface } from '@happyvertical/sql';\nimport { addDays, addMonths, assertIsoDate, dateWithin } from '../dates.js';\nimport {\n EmploymentCollection,\n EmploymentTermCollection,\n} from '../employment/models.js';\nimport { employmentsOn } from '../employment/queries.js';\nimport { type HrEventQueue, HrService } from '../service-base.js';\nimport {\n type HeldQualificationChangeKind,\n type HeldQualificationEventType,\n type HeldQualificationStatus,\n HrError,\n type IsoDate,\n type QualificationCheck,\n type QualificationKind,\n type QualificationScope,\n} from '../types.js';\nimport { insertHr, persistHr } from '../write.js';\nimport { EMPLOYMENT_ENDED_REASON } from './internal.js';\nimport {\n HeldQualification,\n HeldQualificationChange,\n HeldQualificationChangeCollection,\n HeldQualificationCollection,\n Qualification,\n QualificationCollection,\n} from './models.js';\n\n/** Input for {@link QualificationService.define} and {@link QualificationService.seed}. */\nexport interface DefineQualificationInput {\n /** Lowercase kebab-case, unique per tenant: `first-aid`. */\n key: string;\n /** Display name. */\n name: string;\n /** What the qualification is. */\n kind: QualificationKind;\n /** Who issues it; free text. */\n issuingBody?: string;\n /** Whether held qualifications of this kind expire. Defaults to false. */\n expires?: boolean;\n /** Default validity in months; only meaningful when `expires` is true. */\n validityMonths?: number | null;\n /** Defaults to `employment` for kind `authorization`, else `person`. */\n scope?: QualificationScope;\n}\n\n/** Input for {@link QualificationService.update}. Key and scope never change. */\nexport interface UpdateQualificationInput {\n /** New display name. */\n name?: string;\n /** New issuing body. */\n issuingBody?: string;\n /** Whether new grants expire; turning it off clears `validityMonths`. */\n expires?: boolean;\n /** New default validity in months, or null for none. */\n validityMonths?: number | null;\n /** Deactivate (or reactivate) the definition; inactive ones cannot be granted. */\n isActive?: boolean;\n}\n\n/** Options for {@link QualificationService.list}. */\nexport interface ListQualificationsOptions {\n /** Include deactivated definitions. Defaults to false. */\n includeInactive?: boolean;\n}\n\n/** Input for {@link QualificationService.grant}. */\nexport interface GrantQualificationInput {\n /** The definition being granted. */\n qualificationId: string;\n /** The person receiving it. */\n profileId: string;\n /** First day it is valid. */\n issuedOn: IsoDate;\n /** Last day it is valid (inclusive). Defaults from the definition's validity. */\n expiresOn?: IsoDate | null;\n /** Certificate or ticket number; free text. */\n certificateNumber?: string;\n /** Optional `smrt-assets:Asset` id of the scanned document. */\n documentAssetId?: string | null;\n /** Record the actor as having verified the document, now. */\n verified?: boolean;\n /** For an employment-scoped definition; resolved from the profile when omitted. */\n employmentId?: string | null;\n}\n\n/** Input for {@link QualificationService.renew}. */\nexport interface RenewQualificationInput {\n /** First day the renewal is valid. */\n issuedOn: IsoDate;\n /** Last day it is valid (inclusive). Defaults from the definition's validity. */\n expiresOn?: IsoDate | null;\n /** Certificate or ticket number of the renewal. */\n certificateNumber?: string;\n /** Optional `smrt-assets:Asset` id of the renewal's document. */\n documentAssetId?: string | null;\n /** Record the actor as having verified the document, now. */\n verified?: boolean;\n}\n\n/** Input for suspending or revoking a held qualification. */\nexport interface HeldQualificationReasonInput {\n /** The calendar date the change takes effect (that day included). */\n effectiveOn: IsoDate;\n /** Why; required. */\n reason: string;\n}\n\n/** Input for {@link QualificationService.reinstate}. */\nexport interface ReinstateQualificationInput {\n /** The calendar date the qualification is good again (that day included). */\n effectiveOn: IsoDate;\n /** Optional note. */\n reason?: string;\n}\n\n/** Input for {@link QualificationService.verify}. */\nexport interface VerifyQualificationInput {\n /**\n * `smrt-assets:Asset` id of the document to attach or replace; null removes\n * it, and omitting it keeps the current one.\n */\n documentAssetId?: string | null;\n}\n\n/** Options for {@link QualificationService.holders}. */\nexport interface QualificationHoldersOptions {\n /** Only people with an employment term covering the date. */\n employedOnly?: boolean;\n}\n\n/** Options for {@link QualificationService.sweepExpired}. */\nexport interface SweepExpiredOptions {\n /**\n * Only look at qualifications whose last day is on or after this date.\n * Bounds the work of a scheduled sweep; omit it to look at all history.\n */\n since?: IsoDate | null;\n}\n\n/** Options for {@link QualificationService.expiringWithin}. */\nexport interface ExpiringQualificationsOptions {\n /** Only people with an employment term covering `today`. */\n employedOnly?: boolean;\n}\n\n/** Status of a held qualification computed for one calendar date. */\nexport type HeldQualificationStatusOn =\n | HeldQualificationStatus\n | 'not-yet-issued';\n\n/** One row of {@link QualificationService.listForProfile}. */\nexport interface ProfileQualification {\n /** What the person holds. */\n held: HeldQualification;\n /** Its definition. */\n qualification: Qualification;\n /** Status computed for the requested date. */\n status: HeldQualificationStatusOn;\n}\n\n/**\n * A small, generic starter list an application may pass to\n * {@link QualificationService.seed}. Illustrative only: validity periods and\n * what a restriction means are the application's and its jurisdiction's call.\n */\nexport const SUGGESTED_QUALIFICATIONS: readonly DefineQualificationInput[] =\n Object.freeze([\n Object.freeze({\n key: 'first-aid',\n name: 'First aid',\n kind: 'certification',\n expires: true,\n validityMonths: 36,\n } as const),\n Object.freeze({\n key: 'site-orientation',\n name: 'Site orientation',\n kind: 'training',\n } as const),\n Object.freeze({\n key: 'youth-worker',\n name: 'Youth worker',\n kind: 'restriction',\n } as const),\n ]);\n\n/** The cutoff an already closed employment term puts on a qualification. */\nfunction employmentEnd(endsOn: IsoDate | null): Revocation | null {\n return endsOn === null\n ? null\n : { on: addDays(endsOn, 1), reason: EMPLOYMENT_ENDED_REASON };\n}\n\nconst KEY = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst KINDS: readonly QualificationKind[] = [\n 'ticket',\n 'certification',\n 'authorization',\n 'training',\n 'restriction',\n];\nconst SCOPES: readonly QualificationScope[] = ['person', 'employment'];\nconst IN_CHUNK = 500;\n\ntype Definition = Required<DefineQualificationInput>;\n\nfunction invalid(message: string): HrError {\n return new HrError('HR_INVALID', message);\n}\n\nfunction text(fieldName: string, value: unknown, required = false): string {\n if (value == null && !required) return '';\n if (typeof value !== 'string') throw invalid(`${fieldName} must be text.`);\n const trimmed = value.trim();\n if (required && !trimmed) throw invalid(`${fieldName} is required.`);\n return trimmed;\n}\n\nfunction flag(fieldName: string, value: unknown, fallback: boolean): boolean {\n if (value === undefined) return fallback;\n if (typeof value !== 'boolean')\n throw invalid(`${fieldName} must be true or false.`);\n return value;\n}\n\nfunction validity(expires: boolean, value: unknown): number | null {\n if (value == null) return null;\n if (!Number.isInteger(value) || (value as number) <= 0)\n throw invalid('validityMonths must be a positive whole number or null.');\n if (!expires)\n throw invalid(\n 'validityMonths only applies to a qualification that expires.',\n );\n return value as number;\n}\n\nfunction normalizeDefinition(input: DefineQualificationInput): Definition {\n const key = text('key', input?.key, true);\n if (!KEY.test(key))\n throw invalid(`key must be lowercase kebab-case, got '${key}'.`);\n if (!KINDS.includes(input.kind))\n throw invalid(`kind must be one of ${KINDS.join(', ')}.`);\n const scope =\n input.scope ?? (input.kind === 'authorization' ? 'employment' : 'person');\n if (!SCOPES.includes(scope))\n throw invalid(`scope must be one of ${SCOPES.join(', ')}.`);\n const expires = flag('expires', input.expires, false);\n return {\n key,\n name: text('name', input.name, true),\n kind: input.kind,\n issuingBody: text('issuingBody', input.issuingBody),\n expires,\n validityMonths: validity(expires, input.validityMonths),\n scope,\n };\n}\n\n/** The last day a grant is valid, from the input or the definition's validity. */\nfunction resolveExpiry(\n definition: Qualification,\n issuedOn: IsoDate,\n given: IsoDate | null | undefined,\n): IsoDate | null {\n if (!definition.expires) {\n if (given != null)\n throw invalid(`'${definition.key}' does not expire; omit expiresOn.`);\n return null;\n }\n let expiresOn: IsoDate;\n if (given != null) expiresOn = assertIsoDate('expiresOn', given);\n else if (definition.validityMonths != null)\n expiresOn = addMonths(issuedOn, definition.validityMonths);\n else\n throw invalid(\n `'${definition.key}' expires and has no default validity; give expiresOn.`,\n );\n if (expiresOn < issuedOn)\n throw invalid('expiresOn must not be before issuedOn.');\n return expiresOn;\n}\n\n/**\n * Whether a row is revoked or suspended on a date, replayed from its dated\n * changes. Does not depend on the order of same-day changes: revocation is\n * terminal, and suspensions and reinstatements alternate.\n */\nfunction standingOn(\n changes: readonly HeldQualificationChange[],\n on: IsoDate,\n): 'revoked' | 'suspended' | null {\n let suspensions = 0;\n for (const change of changes) {\n if (change.effectiveOn > on) continue;\n if (change.kind === 'revoked') return 'revoked';\n if (change.kind === 'suspended') suspensions += 1;\n else if (change.kind === 'reinstated') suspensions -= 1;\n }\n return suspensions > 0 ? 'suspended' : null;\n}\n\n/**\n * Status of one row on a date, from its own dates and changes. Never trusts\n * the stored status.\n */\nfunction statusOn(\n row: HeldQualification,\n changes: readonly HeldQualificationChange[],\n on: IsoDate,\n): HeldQualificationStatusOn {\n if (row.issuedOn > on) return 'not-yet-issued';\n const standing = standingOn(changes, on);\n if (standing) return standing;\n if (row.expiresOn != null && on > row.expiresOn) return 'expired';\n return 'valid';\n}\n\n/**\n * Status of every row on a date, with a renewal chain sharing its standing:\n * a row that would be valid is revoked or suspended when a later row in its\n * chain (a renewal of it, or of that renewal) is revoked or suspended on the\n * date. Without this, revoking a renewal would be bypassed by the unexpired\n * row it renewed. `rows` must hold whole chains.\n */\nfunction chainStatusesOn(\n rows: readonly HeldQualification[],\n changes: ReadonlyMap<string, readonly HeldQualificationChange[]>,\n on: IsoDate,\n): Map<string, HeldQualificationStatusOn> {\n const renewalOf = new Map<string, HeldQualification>();\n for (const row of rows)\n if (row.renewalOfId) renewalOf.set(row.renewalOfId, row);\n const statuses = new Map<string, HeldQualificationStatusOn>();\n for (const row of rows) {\n const id = row.id as string;\n let status = statusOn(row, changes.get(id) ?? [], on);\n const seen = new Set([id]);\n let later = renewalOf.get(id);\n while (status !== 'revoked' && later && !seen.has(later.id as string)) {\n seen.add(later.id as string);\n const standing = standingOn(changes.get(later.id as string) ?? [], on);\n if (standing && (status === 'valid' || status === 'suspended'))\n status = standing;\n later = renewalOf.get(later.id as string);\n }\n statuses.set(id, status);\n }\n return statuses;\n}\n\n/**\n * Changes that suspend or lift a suspension. They are recorded in date order;\n * a revocation is terminal and may be recorded for any date.\n */\nconst SUSPENSION_KINDS: readonly HeldQualificationChangeKind[] = [\n 'suspended',\n 'reinstated',\n];\n\n/** A revocation: the day it takes effect and why. */\ninterface Revocation {\n /** First day revoked. */\n on: IsoDate;\n /** The reason recorded with it. */\n reason: string;\n}\n\n/** The revocation in force: the earliest one recorded, or null. */\nfunction revocationOf(\n changes: readonly HeldQualificationChange[],\n): Revocation | null {\n let earliest: HeldQualificationChange | null = null;\n for (const change of changes)\n if (\n change.kind === 'revoked' &&\n (earliest === null || change.effectiveOn < earliest.effectiveOn)\n )\n earliest = change;\n return earliest && { on: earliest.effectiveOn, reason: earliest.reason };\n}\n\n/** The date a revocation takes effect: the earliest one recorded, or null. */\nfunction revokedFrom(\n changes: readonly HeldQualificationChange[],\n): IsoDate | null {\n return revocationOf(changes)?.on ?? null;\n}\n\n/** The latest effective date among changes of the given kinds, or null. */\nfunction latestOf(\n changes: readonly HeldQualificationChange[],\n kinds: readonly HeldQualificationChangeKind[],\n): IsoDate | null {\n let latest: IsoDate | null = null;\n for (const change of changes)\n if (\n kinds.includes(change.kind) &&\n (latest === null || change.effectiveOn > latest)\n )\n latest = change.effectiveOn;\n return latest;\n}\n\nfunction createdTime(row: { created_at?: Date | string | null }): number {\n const time = row.created_at ? new Date(row.created_at).getTime() : 0;\n return Number.isNaN(time) ? 0 : time;\n}\n\n/** Among rows that are good on a date, the one that lasts longest. */\nfunction longestLasting(rows: readonly HeldQualification[]): HeldQualification {\n return rows.reduce((best, row) => {\n if (best.expiresOn === row.expiresOn)\n return row.issuedOn > best.issuedOn ? row : best;\n if (best.expiresOn == null) return best;\n if (row.expiresOn == null) return row;\n return row.expiresOn > best.expiresOn ? row : best;\n });\n}\n\n/**\n * The only writer of qualification definitions and held qualifications, and\n * the gate ({@link QualificationService.check}) applications call before\n * letting a person do qualified work. Constructed from a trusted\n * tenant/profile actor the application has already authorized.\n *\n * Whether a qualification is good on a date is computed from `issuedOn`,\n * `expiresOn` and the dated change history, so past dates keep answering\n * correctly and expiry needs no scheduler. The stored status records the\n * last change made (a revocation or suspension stores at once, even when it\n * takes effect later), so every rule that asks about standing replays the\n * dated history instead, including the rules that decide whether a\n * qualification may be suspended, reinstated, revoked or renewed. A stored\n * `revoked` is final: later changes to such a row are change rows only. The\n * stored `expired` status is written by\n * {@link QualificationService.sweepExpired}, and by a reinstatement of a row\n * whose lapse the sweep has recorded.\n */\nexport class QualificationService extends HrService {\n // ── Definitions ──────────────────────────────────────────────────────────\n\n /** Add a qualification definition; its key is unique per tenant. */\n async define(input: DefineQualificationInput): Promise<Qualification> {\n const definition = normalizeDefinition(input);\n const id = await this.transact(async (db) => {\n if (await this.byKey(db, definition.key))\n throw new HrError(\n 'HR_QUALIFICATION_KEY_TAKEN',\n `A qualification with key '${definition.key}' already exists.`,\n );\n return (await this.insertDefinition(db, definition)).id as string;\n });\n return this.get(id);\n }\n\n /** Change a definition's descriptive fields; key and scope are immutable. */\n async update(\n qualificationId: string,\n input: UpdateQualificationInput,\n ): Promise<Qualification> {\n const id = this.id('qualificationId', qualificationId);\n if (input == null || typeof input !== 'object')\n throw invalid('update needs the fields to change.');\n await this.transact(async (db) => {\n const row = await this.definition(db, id);\n if (input.name !== undefined) row.name = text('name', input.name, true);\n if (input.issuingBody !== undefined)\n row.issuingBody = text('issuingBody', input.issuingBody);\n row.expires = flag('expires', input.expires, row.expires);\n row.isActive = flag('isActive', input.isActive, row.isActive);\n if (input.validityMonths !== undefined)\n row.validityMonths = validity(row.expires, input.validityMonths);\n else if (!row.expires) row.validityMonths = null;\n await persistHr(row);\n });\n return this.get(id);\n }\n\n /**\n * Create the definitions whose key is missing and leave existing rows\n * untouched. Returns one definition per input, in input order.\n */\n async seed(\n definitions: readonly DefineQualificationInput[],\n ): Promise<Qualification[]> {\n const wanted = definitions.map(normalizeDefinition);\n if (new Set(wanted.map((d) => d.key)).size !== wanted.length)\n throw invalid('Seed definitions must have distinct keys.');\n const ids = await this.transact(async (db) => {\n const result: string[] = [];\n for (const definition of wanted) {\n const row =\n (await this.byKey(db, definition.key)) ??\n (await this.insertDefinition(db, definition));\n result.push(row.id as string);\n }\n return result;\n });\n return this.scope(async () => {\n const collection = await QualificationCollection.create({ db: this.db });\n const rows = await collection.list({\n where: { tenantId: this.actor.tenantId },\n });\n const byId = new Map(rows.map((row) => [row.id as string, row]));\n return ids.map((id) => byId.get(id) as Qualification);\n });\n }\n\n /** The tenant's definitions ordered by key; active ones unless asked. */\n async list(\n options: ListQualificationsOptions = {},\n ): Promise<Qualification[]> {\n return this.scope(async () => {\n const collection = await QualificationCollection.create({ db: this.db });\n const rows = await collection.list({\n where: { tenantId: this.actor.tenantId },\n orderBy: 'key ASC',\n });\n return options.includeInactive ? rows : rows.filter((r) => r.isActive);\n });\n }\n\n /**\n * One definition by id.\n *\n * @throws HrError `HR_NOT_FOUND` outside the actor's tenant, `HR_INVALID` for a blank id\n */\n async get(qualificationId: string): Promise<Qualification> {\n const id = this.id('qualificationId', qualificationId);\n return this.scope(() => this.definition(this.db, id));\n }\n\n /** One definition by key, or null. */\n async findByKey(key: string): Promise<Qualification | null> {\n if (typeof key !== 'string' || !key.trim()) return null;\n return this.scope(() => this.byKey(this.db, key.trim()));\n }\n\n // ── Held qualifications ──────────────────────────────────────────────────\n\n /**\n * Record that a person holds a qualification from `issuedOn`.\n *\n * A person has one live grant of a qualification at a time, decided by\n * date: the grant is refused unless everything they already hold of it is\n * revoked effective on or before `issuedOn`. A lapsed ticket (swept or not)\n * is renewed, not granted again, and a revocation scheduled for a later\n * date does not free an earlier issue date.\n *\n * An employment-scoped grant dated inside a term whose end is already\n * recorded ends with that employment: the `employment-ended` revocation,\n * effective the day after the term's last day, is recorded in the same\n * transaction and the row is stored `revoked`.\n *\n * @throws HrError `HR_ALREADY_HELD` unless every earlier grant is revoked effective on or before `issuedOn` (renew the existing one instead), `HR_QUALIFICATION_SCOPE`, `HR_INVALID`, `HR_NOT_FOUND`\n */\n async grant(input: GrantQualificationInput): Promise<HeldQualification> {\n const qualificationId = this.id('qualificationId', input?.qualificationId);\n const profileId = this.id('profileId', input.profileId);\n const issuedOn = assertIsoDate('issuedOn', input.issuedOn);\n const givenEmploymentId = this.optionalId(\n 'employmentId',\n input.employmentId,\n );\n const id = await this.transact(async (db, queue) => {\n const definition = await this.grantable(db, qualificationId);\n const expiresOn = resolveExpiry(definition, issuedOn, input.expiresOn);\n const { employmentId, endsOn } = await this.employmentFor(\n db,\n definition,\n profileId,\n givenEmploymentId,\n issuedOn,\n );\n const rows = await this.rows(db, { profileId, qualificationId });\n const [live] = await this.liveChains(db, rows, issuedOn, null);\n if (live)\n throw new HrError(\n 'HR_ALREADY_HELD',\n live.revokedOn\n ? `This person holds '${definition.key}' until its revocation takes effect on ${live.revokedOn}; renew the existing one instead.`\n : `This person already holds '${definition.key}'; renew it instead.`,\n );\n const row = await this.insertHeld(db, queue, 'granted', {\n qualificationId,\n profileId,\n employmentId,\n revocation: employmentEnd(endsOn),\n issuedOn,\n expiresOn,\n renewalOfId: null,\n input,\n });\n return row.id as string;\n });\n return this.held(this.db, id);\n }\n\n /**\n * Renew a held qualification: a new row pointing at the old one. The old row\n * is not modified, so checks on past dates still answer from it. Only the\n * latest row of a chain can be renewed.\n *\n * The renewal is validated against the old row's dated history, not its\n * stored status: `issuedOn` cannot be earlier than the row's latest\n * suspension or reinstatement (the renewal would otherwise answer for dates\n * the history says were suspended), the row must not be suspended on\n * `issuedOn`, and it must not be revoked on or before `issuedOn`.\n *\n * A revocation recorded for a later date (a scheduled one, or the end of an\n * employment whose last day is ahead) does not stop a renewal: the\n * qualification is still in force, so a ticket that lapses first can be\n * renewed. The renewal carries the cutoff forward. It is stored `revoked`\n * with a revocation at the earlier of the old row's pending revocation\n * (with its reason) and, for an employment-scoped one inside a term whose\n * end is recorded, the day after the last day employed\n * (`employment-ended`); `held-qualification.renewed` and\n * `held-qualification.revoked` are both raised.\n *\n * @throws HrError `HR_STATUS_TRANSITION` when suspended on `issuedOn` (reinstate it first); `HR_ALREADY_HELD` when the person holds another grant outside this chain that is not revoked on or before `issuedOn` and starts before the day this renewal is cut off; `HR_INVALID` for a row revoked on or before `issuedOn`, an already renewed row, an `issuedOn` before the row's own or before its latest suspension or reinstatement, or an expiry earlier than the row's own (revoke it and grant a new one instead); `HR_NOT_FOUND`\n */\n async renew(\n heldQualificationId: string,\n input: RenewQualificationInput,\n ): Promise<HeldQualification> {\n const priorId = this.id('heldQualificationId', heldQualificationId);\n const issuedOn = assertIsoDate('issuedOn', input?.issuedOn);\n const id = await this.transact(async (db, queue) => {\n const prior = await this.held(db, priorId);\n const history =\n (await this.changesByHeld(db, [prior])).get(priorId) ?? [];\n // Revocation is terminal from the day it takes effect; until then the\n // qualification is in force and can be renewed. A stored `revoked` with\n // no dated revocation (a row written outside the service) has no day to\n // go by and is treated as revoked throughout.\n const pending = revocationOf(history);\n if (\n (pending !== null && pending.on <= issuedOn) ||\n (pending === null && prior.status === 'revoked')\n )\n throw invalid(\n 'A qualification revoked on or before the renewal date cannot be renewed; grant a new one.',\n );\n const siblings = await this.rows(db, {\n profileId: prior.profileId,\n qualificationId: prior.qualificationId,\n });\n if (siblings.some((row) => row.renewalOfId === priorId))\n throw invalid(\n 'This qualification was already renewed; renew the latest one.',\n );\n if (issuedOn < prior.issuedOn)\n throw invalid('A renewal cannot be issued before what it renews.');\n // A renewal starts out in good standing from its issue date, so it must\n // not reach back over a suspension the history records.\n const changedOn = latestOf(history, SUSPENSION_KINDS);\n if (changedOn !== null && issuedOn < changedOn)\n throw invalid(\n `A renewal cannot be issued before the latest suspension or reinstatement on ${changedOn}.`,\n );\n if (standingOn(history, issuedOn) === 'suspended')\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n 'A suspended qualification cannot be renewed; reinstate it first.',\n );\n const definition = await this.grantable(db, prior.qualificationId);\n const expiresOn = resolveExpiry(definition, issuedOn, input.expiresOn);\n // The row being renewed keeps answering for its own period, so a\n // renewal that ended sooner would lapse while the person still held the\n // qualification through the old row.\n if (\n expiresOn !== null &&\n (prior.expiresOn === null || expiresOn < prior.expiresOn)\n )\n throw invalid(\n `A renewal cannot end before what it renews (${prior.expiresOn ?? 'no expiry'}). To replace it with a shorter one, revoke it from ${issuedOn} and grant a new one from that day.`,\n );\n const { employmentId, endsOn } = await this.employmentFor(\n db,\n definition,\n prior.profileId,\n prior.employmentId,\n issuedOn,\n );\n // The earlier cutoff wins; on the same day the employment end is the\n // reason kept.\n const ended = employmentEnd(endsOn);\n const revocation =\n ended !== null && (pending === null || ended.on <= pending.on)\n ? ended\n : pending;\n // Earlier rows of this chain are all renewed, so any other latest row\n // belongs to a different chain. One that only starts on or after the\n // day this renewal is cut off never overlaps it, whichever of the two\n // was recorded first.\n const byId = new Map(siblings.map((row) => [row.id as string, row]));\n const startsOn = (row: HeldQualification): IsoDate => {\n let first = row.issuedOn;\n const seen = new Set<string>();\n for (\n let member: HeldQualification | undefined = row;\n member && !seen.has(member.id as string);\n member = member.renewalOfId ? byId.get(member.renewalOfId) : undefined\n ) {\n seen.add(member.id as string);\n if (member.issuedOn < first) first = member.issuedOn;\n }\n return first;\n };\n const overlapping = (\n await this.liveChains(db, siblings, issuedOn, priorId)\n ).filter(\n (other) => revocation === null || startsOn(other.row) < revocation.on,\n );\n if (overlapping.length > 0)\n throw new HrError(\n 'HR_ALREADY_HELD',\n 'This person holds another grant of this qualification outside this renewal chain that overlaps the renewal; renew that one, or revoke it first.',\n );\n const row = await this.insertHeld(db, queue, 'renewed', {\n qualificationId: prior.qualificationId,\n profileId: prior.profileId,\n employmentId,\n revocation,\n issuedOn,\n expiresOn,\n renewalOfId: priorId,\n input,\n });\n return row.id as string;\n });\n return this.held(this.db, id);\n }\n\n /**\n * Suspend a qualification from `effectiveOn` until it is reinstated. Act on\n * the latest row of a renewal chain: the whole chain shares its standing,\n * so the rows it renewed stop passing checks too.\n *\n * Whether it can be suspended is decided from the chain's dated history as\n * of `effectiveOn`, never from the stored status: it must be neither\n * suspended nor revoked on that day. A revocation recorded for a later date\n * (scheduled, or the end of an employment whose last day is ahead) does not\n * prevent it, and neither does a stored `expired`.\n *\n * `effectiveOn` may be any date from the chain's first issue date onward,\n * not before the chain's latest suspension or reinstatement. It may precede\n * the latest row's own issue date: with a renewal recorded ahead of time,\n * that is how the ticket in force today is suspended, and the renewal is\n * then suspended from its issue date until reinstated.\n *\n * The stored status becomes `suspended`, except on a row already stored\n * `revoked`, which keeps it: only the change row is added.\n *\n * @throws HrError `HR_INVALID` for a row that was renewed, a date outside that range or a bad input; `HR_STATUS_TRANSITION` when suspended or revoked on `effectiveOn`; `HR_NOT_FOUND`\n */\n async suspend(\n heldQualificationId: string,\n input: HeldQualificationReasonInput,\n ): Promise<HeldQualification> {\n return this.transition(heldQualificationId, 'suspended', {\n effectiveOn: input?.effectiveOn,\n reason: text('reason', input?.reason, true),\n });\n }\n\n /**\n * Lift a suspension from `effectiveOn`, which follows the same date rule as\n * {@link suspend}. The chain must be suspended on that day by its dated\n * history, and not yet revoked. The stored status becomes `valid`, or\n * `expired` when the sweep has recorded the row's lapse; a stored `revoked`\n * stays.\n *\n * @throws HrError `HR_INVALID` for a row that was renewed, a date before the suspension or a bad input; `HR_STATUS_TRANSITION` unless suspended on `effectiveOn`; `HR_NOT_FOUND`\n */\n async reinstate(\n heldQualificationId: string,\n input: ReinstateQualificationInput,\n ): Promise<HeldQualification> {\n return this.transition(heldQualificationId, 'reinstated', {\n effectiveOn: input?.effectiveOn,\n reason: text('reason', input?.reason),\n });\n }\n\n /**\n * Revoke a qualification from `effectiveOn`. Terminal: grant a new one\n * dated on or after it. Act on the latest row of a renewal chain: the rows\n * it renewed stop passing checks from the same date; dated before a renewal\n * recorded ahead of time, the renewal is revoked from its issue date.\n *\n * `effectiveOn` may be any date from the chain's first issue date onward,\n * whatever else is recorded: an urgent revocation may be dated before a\n * suspension recorded ahead of time, or before a revocation that has not\n * taken effect yet (a scheduled one, or the end of an employment whose last\n * day is ahead). The earliest revocation is the one in force. It is refused\n * only when the chain is already revoked on or before `effectiveOn`.\n *\n * The stored status becomes `revoked` at once, meaning a revocation has\n * been recorded; until `effectiveOn` the qualification still passes\n * {@link check}. A row already stored `revoked` is not saved again: the\n * earlier revocation is one more change row, and the event carries its\n * date.\n *\n * @throws HrError `HR_INVALID` for a row that was renewed, a date before the chain was issued or a bad input; `HR_STATUS_TRANSITION` when already revoked on or before `effectiveOn`; `HR_NOT_FOUND`\n */\n async revoke(\n heldQualificationId: string,\n input: HeldQualificationReasonInput,\n ): Promise<HeldQualification> {\n return this.transition(heldQualificationId, 'revoked', {\n effectiveOn: input?.effectiveOn,\n reason: text('reason', input?.reason, true),\n });\n }\n\n /**\n * Record that the actor has verified a held qualification's document, now.\n * Optionally attaches, replaces or removes the document. The status and the\n * dated history do not change, and no event is raised. Verification is not\n * a status, so it works whatever the standing, including on a row stored\n * `revoked` (which may still be in force until its revocation takes effect).\n *\n * @throws HrError `HR_INVALID`, `HR_NOT_FOUND`\n */\n async verify(\n heldQualificationId: string,\n input: VerifyQualificationInput = {},\n ): Promise<HeldQualification> {\n const id = this.id('heldQualificationId', heldQualificationId);\n if (input == null || typeof input !== 'object')\n throw invalid('verify takes an options object.');\n const documentAssetId =\n input.documentAssetId === undefined\n ? undefined\n : this.optionalId('documentAssetId', input.documentAssetId);\n await this.transact(async (db) => {\n const row = await this.held(db, id);\n if (documentAssetId !== undefined) row.documentAssetId = documentAssetId;\n row.verifiedByProfileId = this.actor.profileId;\n row.verifiedAt = new Date();\n await persistHr(row);\n });\n return this.held(this.db, id);\n }\n\n /**\n * Record the lapse of every qualification whose last day is before `today`\n * and that was still in force on it: an `expired` change dated the day\n * after `expiresOn`. The application calls this from its own scheduler;\n * checks never depend on it. Idempotent: a row is recorded once.\n *\n * Which rows lapsed is decided by date, not by the stored status, so a\n * ticket that runs out while a suspension is in force, or while a\n * revocation is recorded for a later day (a notice period), is recorded\n * too. A row revoked on or before the day after `expiresOn` did not lapse:\n * it was revoked. The stored status becomes `expired` only on a row stored\n * `valid`; a stored `suspended` or `revoked` stays as it is.\n *\n * Every run re-reads the lapsed rows stored `suspended` or `revoked`, a set\n * that only grows with a tenant's history. Pass `since` (a day on or before\n * the last day the previous run covered) to look only at rows whose last\n * day is on or after it.\n *\n * Emits `held-qualification.expired`, and returns the row, only for\n * qualifications that were not renewed.\n *\n * @throws HrError `HR_INVALID` for a bad `today` or `since`\n */\n async sweepExpired(\n today: IsoDate,\n options: SweepExpiredOptions = {},\n ): Promise<HeldQualification[]> {\n assertIsoDate('today', today);\n const since =\n options?.since == null ? null : assertIsoDate('since', options.since);\n const ids = await this.transact(async (db, queue) => {\n const candidates = (\n await this.rows(db, {\n status: ['valid', 'suspended', 'revoked'],\n 'expiresOn <': today,\n ...(since === null ? {} : { 'expiresOn >=': since }),\n })\n ).filter((row) => row.expiresOn != null);\n const changes = await this.changesByHeld(db, candidates);\n const due = candidates.filter((row) => {\n const history = changes.get(row.id as string) ?? [];\n if (history.some((change) => change.kind === 'expired')) return false;\n const revokedOn = revokedFrom(history);\n return (\n revokedOn === null || revokedOn > addDays(row.expiresOn as IsoDate, 1)\n );\n });\n const superseded = await this.supersededIds(db, due);\n const emitted: string[] = [];\n for (const row of due) {\n const effectiveOn = addDays(row.expiresOn as IsoDate, 1);\n if (row.status === 'valid') {\n row.status = 'expired';\n await persistHr(row);\n }\n await this.recordChange(db, row, 'expired', effectiveOn, '');\n if (superseded.has(row.id as string)) continue;\n this.announce(queue, 'held-qualification.expired', row, effectiveOn);\n emitted.push(row.id as string);\n }\n return emitted;\n });\n return this.scope(async () => {\n const rows = await this.rowsById(this.db, ids);\n return rows.sort(\n (a, b) =>\n (a.expiresOn ?? '').localeCompare(b.expiresOn ?? '') ||\n String(a.id).localeCompare(String(b.id)),\n );\n });\n }\n\n // ── Queries ──────────────────────────────────────────────────────────────\n\n /**\n * Whether a person holds a qualification on a calendar date. Computed from\n * issue and expiry dates and the dated change history, never from the stored\n * status alone, so it is correct for past dates and without the sweep. A\n * renewal chain shares its standing: while a renewal is revoked or\n * suspended, the row it renewed does not pass either. A blank profile or\n * qualification id is `not-held`.\n */\n async check(\n profileId: string,\n qualificationId: string,\n on: IsoDate,\n ): Promise<QualificationCheck> {\n assertIsoDate('on', on);\n const blank = (value: unknown) =>\n typeof value !== 'string' || !value.trim();\n if (blank(profileId) || blank(qualificationId))\n return { ok: false, reason: 'not-held' };\n const where = {\n profileId: this.id('profileId', profileId),\n qualificationId: this.id('qualificationId', qualificationId),\n };\n return this.scope(async () => {\n const rows = await this.rows(this.db, where);\n if (rows.length === 0) return { ok: false, reason: 'not-held' };\n const statuses = chainStatusesOn(\n rows,\n await this.changesByHeld(this.db, rows),\n on,\n );\n const good = rows.filter(\n (row) => statuses.get(row.id as string) === 'valid',\n );\n if (good.length > 0) {\n const best = longestLasting(good);\n return {\n ok: true,\n heldQualificationId: best.id as string,\n expiresOn: best.expiresOn,\n };\n }\n const issued = rows.filter((row) => row.issuedOn <= on);\n if (issued.length === 0) return { ok: false, reason: 'not-yet-issued' };\n const latest = issued.reduce((a, b) =>\n b.issuedOn > a.issuedOn ||\n (b.issuedOn === a.issuedOn && createdTime(b) >= createdTime(a))\n ? b\n : a,\n );\n return {\n ok: false,\n reason: statuses.get(latest.id as string) as\n | 'expired'\n | 'suspended'\n | 'revoked',\n };\n });\n }\n\n /**\n * One held qualification per person who passes {@link check} on `on`,\n * ordered by profile id.\n */\n async holders(\n qualificationId: string,\n on: IsoDate,\n options: QualificationHoldersOptions = {},\n ): Promise<HeldQualification[]> {\n assertIsoDate('on', on);\n const id = this.id('qualificationId', qualificationId);\n return this.scope(async () => {\n const rows = await this.rows(this.db, { qualificationId: id });\n const statuses = chainStatusesOn(\n rows,\n await this.changesByHeld(this.db, rows),\n on,\n );\n const employed = options?.employedOnly\n ? new Set(\n (await employmentsOn(this.db, this.actor.tenantId, on)).map(\n (employment) => employment.profileId,\n ),\n )\n : null;\n const good = new Map<string, HeldQualification[]>();\n for (const row of rows) {\n if (employed && !employed.has(row.profileId)) continue;\n if (statuses.get(row.id as string) !== 'valid') continue;\n good.set(row.profileId, [...(good.get(row.profileId) ?? []), row]);\n }\n return [...good.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([, held]) => longestLasting(held));\n });\n }\n\n /**\n * Qualifications good on `today` (issued, and not suspended or revoked by\n * the dated history, exactly as {@link check} decides) whose last day falls\n * in `[today, today + days]`, soonest first. The stored status plays no\n * part. A qualification that was already renewed is left out, and\n * `employedOnly` keeps only people with an employment term covering `today`.\n */\n async expiringWithin(\n days: number,\n today: IsoDate,\n options: ExpiringQualificationsOptions = {},\n ): Promise<HeldQualification[]> {\n assertIsoDate('today', today);\n if (!Number.isInteger(days) || days < 0)\n throw invalid('days must be a whole number, zero or more.');\n return this.scope(async () => {\n const rows = await this.rows(\n this.db,\n {\n 'issuedOn <=': today,\n 'expiresOn >=': today,\n 'expiresOn <=': addDays(today, days),\n },\n 'expiresOn ASC',\n );\n // A row that was renewed is left out, so each remaining row is the\n // latest of its chain and its own history is its chain's standing.\n const superseded = await this.supersededIds(this.db, rows);\n const changes = await this.changesByHeld(this.db, rows);\n const employed = options?.employedOnly\n ? new Set(\n (await employmentsOn(this.db, this.actor.tenantId, today)).map(\n (employment) => employment.profileId,\n ),\n )\n : null;\n return rows.filter(\n (row) =>\n !superseded.has(row.id as string) &&\n statusOn(row, changes.get(row.id as string) ?? [], today) ===\n 'valid' &&\n (!employed || employed.has(row.profileId)),\n );\n });\n }\n\n /**\n * Everything a person holds (renewed rows are replaced by their renewal),\n * each with its definition and its status computed for `on`. The status\n * agrees with {@link check}: it is `valid` when the row, or a row it renews,\n * is good on `on`, and otherwise the status of the latest row of the chain\n * issued by `on`. Ordered by qualification key, then issue date.\n *\n * @throws HrError `HR_INVALID` for a blank profile id or a bad date\n */\n async listForProfile(\n profileId: string,\n on: IsoDate,\n ): Promise<ProfileQualification[]> {\n assertIsoDate('on', on);\n const id = this.id('profileId', profileId);\n return this.scope(async () => {\n const rows = await this.rows(this.db, { profileId: id });\n const superseded = await this.supersededIds(this.db, rows);\n const statuses = chainStatusesOn(\n rows,\n await this.changesByHeld(this.db, rows),\n on,\n );\n const byRowId = new Map(rows.map((row) => [row.id as string, row]));\n // The latest row speaks for its chain: when it is not good on the date\n // but a row it renews is, the person still holds the qualification.\n // Otherwise the latest row already issued answers, as in check(): a\n // renewal recorded ahead of time does not hide a suspended or revoked\n // ticket behind `not-yet-issued`.\n const chainStatus = (\n held: HeldQualification,\n ): HeldQualificationStatusOn => {\n let answer: HeldQualificationStatusOn = 'not-yet-issued';\n const seen = new Set<string>();\n let earlier: HeldQualification | undefined = held;\n while (earlier && !seen.has(earlier.id as string)) {\n seen.add(earlier.id as string);\n const status = statuses.get(\n earlier.id as string,\n ) as HeldQualificationStatusOn;\n if (status === 'valid') return 'valid';\n if (answer === 'not-yet-issued') answer = status;\n earlier = earlier.renewalOfId\n ? byRowId.get(earlier.renewalOfId)\n : undefined;\n }\n return answer;\n };\n const definitions = await QualificationCollection.create({ db: this.db });\n const byId = new Map(\n (\n await definitions.list({ where: { tenantId: this.actor.tenantId } })\n ).map((definition) => [definition.id as string, definition]),\n );\n return rows\n .filter((row) => !superseded.has(row.id as string))\n .map((held) => ({\n held,\n qualification: byId.get(held.qualificationId) as Qualification,\n status: chainStatus(held),\n }))\n .sort(\n (a, b) =>\n a.qualification.key.localeCompare(b.qualification.key) ||\n a.held.issuedOn.localeCompare(b.held.issuedOn) ||\n createdTime(a.held) - createdTime(b.held),\n );\n });\n }\n\n /**\n * The dated changes of one held qualification, oldest first.\n *\n * @throws HrError `HR_NOT_FOUND` outside the actor's tenant, `HR_INVALID` for a blank id\n */\n async history(\n heldQualificationId: string,\n ): Promise<HeldQualificationChange[]> {\n const id = this.id('heldQualificationId', heldQualificationId);\n return this.scope(async () => {\n const row = await this.held(this.db, id);\n const changes = await this.changesByHeld(this.db, [row]);\n return changes.get(id) ?? [];\n });\n }\n\n // ── Internals ────────────────────────────────────────────────────────────\n\n private async byKey(\n db: DatabaseInterface,\n key: string,\n ): Promise<Qualification | null> {\n const collection = await QualificationCollection.create({ db });\n const [row] = await collection.list({\n where: { tenantId: this.actor.tenantId, key },\n limit: 1,\n });\n return row ?? null;\n }\n\n private async definition(\n db: DatabaseInterface,\n id: string,\n ): Promise<Qualification> {\n const collection = await QualificationCollection.create({ db });\n const [row] = await collection.list({\n where: { tenantId: this.actor.tenantId, id },\n limit: 1,\n });\n if (!row) throw new HrError('HR_NOT_FOUND', 'Qualification not found.');\n return row;\n }\n\n private async grantable(\n db: DatabaseInterface,\n id: string,\n ): Promise<Qualification> {\n const definition = await this.definition(db, id);\n if (!definition.isActive)\n throw invalid(`'${definition.key}' is inactive and cannot be granted.`);\n return definition;\n }\n\n private insertDefinition(\n db: DatabaseInterface,\n definition: Definition,\n ): Promise<Qualification> {\n return insertHr(Qualification, db, {\n tenantId: this.actor.tenantId,\n ...definition,\n });\n }\n\n private async held(\n db: DatabaseInterface,\n id: string,\n ): Promise<HeldQualification> {\n const [row] = await this.scope(() => this.rows(db, { id }));\n if (!row)\n throw new HrError('HR_NOT_FOUND', 'Held qualification not found.');\n return row;\n }\n\n /** Held rows in the actor's tenant matching `where`. */\n private async rows(\n db: DatabaseInterface,\n where: Record<string, unknown>,\n orderBy?: string,\n ): Promise<HeldQualification[]> {\n const collection = await HeldQualificationCollection.create({ db });\n return collection.list({\n where: { tenantId: this.actor.tenantId, ...where },\n orderBy,\n });\n }\n\n private async rowsById(\n db: DatabaseInterface,\n ids: readonly string[],\n ): Promise<HeldQualification[]> {\n const rows: HeldQualification[] = [];\n for (let at = 0; at < ids.length; at += IN_CHUNK)\n rows.push(...(await this.rows(db, { id: ids.slice(at, at + IN_CHUNK) })));\n return rows;\n }\n\n /**\n * The chains among `rows` (everything one person holds of one\n * qualification) that are not revoked effective on or before `on`, each\n * represented by its latest row and the date its revocation takes effect,\n * if one is recorded. The chain whose latest row is `exceptId` is skipped.\n * Decided from the dated history: a revocation stored now but effective\n * later still counts as held.\n */\n private async liveChains(\n db: DatabaseInterface,\n rows: readonly HeldQualification[],\n on: IsoDate,\n exceptId: string | null,\n ): Promise<{ row: HeldQualification; revokedOn: IsoDate | null }[]> {\n const renewed = new Set(rows.map((row) => row.renewalOfId));\n const latest = rows.filter(\n (row) => !renewed.has(row.id as string) && row.id !== exceptId,\n );\n const changes = await this.changesByHeld(db, latest);\n return latest\n .map((row) => ({\n row,\n revokedOn: revokedFrom(changes.get(row.id as string) ?? []),\n }))\n .filter(({ revokedOn }) => revokedOn === null || revokedOn > on);\n }\n\n /** Ids, among `rows`, that a later row renews. */\n private async supersededIds(\n db: DatabaseInterface,\n rows: readonly HeldQualification[],\n ): Promise<Set<string>> {\n const ids = rows.map((row) => row.id as string);\n const superseded = new Set<string>();\n for (let at = 0; at < ids.length; at += IN_CHUNK)\n for (const renewal of await this.rows(db, {\n renewalOfId: ids.slice(at, at + IN_CHUNK),\n }))\n if (renewal.renewalOfId) superseded.add(renewal.renewalOfId);\n return superseded;\n }\n\n /** Change rows per held qualification, oldest first. */\n private async changesByHeld(\n db: DatabaseInterface,\n rows: readonly HeldQualification[],\n ): Promise<Map<string, HeldQualificationChange[]>> {\n const ids = rows.map((row) => row.id as string);\n const collection = await HeldQualificationChangeCollection.create({ db });\n const all: HeldQualificationChange[] = [];\n for (let at = 0; at < ids.length; at += IN_CHUNK)\n all.push(\n ...(await collection.list({\n where: {\n tenantId: this.actor.tenantId,\n heldQualificationId: ids.slice(at, at + IN_CHUNK),\n },\n })),\n );\n all.sort(\n (a, b) =>\n a.effectiveOn.localeCompare(b.effectiveOn) ||\n createdTime(a) - createdTime(b),\n );\n const grouped = new Map<string, HeldQualificationChange[]>();\n for (const change of all)\n grouped.set(change.heldQualificationId, [\n ...(grouped.get(change.heldQualificationId) ?? []),\n change,\n ]);\n return grouped;\n }\n\n /**\n * The employment an employment-scoped grant belongs to (null for a\n * person-scoped one) and, when the term covering `issuedOn` already has its\n * end recorded, that term's last day. The person must be employed on\n * `issuedOn`.\n */\n private async employmentFor(\n db: DatabaseInterface,\n definition: Qualification,\n profileId: string,\n given: string | null,\n issuedOn: IsoDate,\n ): Promise<{ employmentId: string | null; endsOn: IsoDate | null }> {\n const mismatch = (message: string) =>\n new HrError('HR_QUALIFICATION_SCOPE', message);\n if (definition.scope !== 'employment') {\n if (given != null)\n throw mismatch(\n `'${definition.key}' belongs to the person; omit employmentId.`,\n );\n return { employmentId: null, endsOn: null };\n }\n const employments = await EmploymentCollection.create({ db });\n const [employment] = await employments.list({\n where: {\n tenantId: this.actor.tenantId,\n profileId,\n ...(given != null ? { id: given } : {}),\n },\n limit: 1,\n });\n if (!employment)\n throw mismatch(\n `'${definition.key}' belongs to an employment, and this person has no matching employment.`,\n );\n const terms = await EmploymentTermCollection.create({ db });\n const term = (\n await terms.list({\n where: {\n tenantId: this.actor.tenantId,\n employmentId: employment.id as string,\n 'startedOn <=': issuedOn,\n },\n })\n ).find((covering) =>\n dateWithin(issuedOn, covering.startedOn, covering.endedOn),\n );\n if (!term)\n throw mismatch(\n `'${definition.key}' belongs to an employment, and this person is not employed on ${issuedOn}.`,\n );\n return { employmentId: employment.id as string, endsOn: term.endedOn };\n }\n\n /**\n * Insert a granted or renewed row with its change and event. With a\n * `revocation` (the end of an employment term that is already closed, or a\n * pending revocation a renewal carries forward) the row starts out cut off,\n * exactly as `EmploymentService.end()` leaves the rows that existed when\n * the end was recorded: stored `revoked`, a revocation on that date, and\n * the revoked event.\n */\n private async insertHeld(\n db: DatabaseInterface,\n queue: HrEventQueue,\n kind: 'granted' | 'renewed',\n values: {\n qualificationId: string;\n profileId: string;\n employmentId: string | null;\n revocation: Revocation | null;\n issuedOn: IsoDate;\n expiresOn: IsoDate | null;\n renewalOfId: string | null;\n input: Pick<\n GrantQualificationInput,\n 'certificateNumber' | 'documentAssetId' | 'verified'\n >;\n },\n ): Promise<HeldQualification> {\n const { input, revocation, ...identityFields } = values;\n const verified = flag('verified', input.verified, false);\n const row = await insertHr(HeldQualification, db, {\n tenantId: this.actor.tenantId,\n ...identityFields,\n certificateNumber: text('certificateNumber', input.certificateNumber),\n documentAssetId: this.optionalId(\n 'documentAssetId',\n input.documentAssetId,\n ),\n status: revocation === null ? 'valid' : 'revoked',\n verifiedByProfileId: verified ? this.actor.profileId : null,\n verifiedAt: verified ? new Date() : null,\n });\n await this.recordChange(db, row, kind, values.issuedOn, '');\n this.announce(queue, `held-qualification.${kind}`, row, values.issuedOn);\n if (revocation !== null) {\n await this.recordChange(\n db,\n row,\n 'revoked',\n revocation.on,\n revocation.reason,\n );\n this.announce(queue, 'held-qualification.revoked', row, revocation.on);\n }\n return row;\n }\n\n /**\n * Suspend, reinstate or revoke the latest row of a chain. The change is\n * validated against the chain's dated history as of `effectiveOn`; the\n * stored status is not consulted, except that a row stored `revoked` is\n * never saved again and gets the change row only.\n */\n private async transition(\n heldQualificationId: string,\n kind: 'suspended' | 'reinstated' | 'revoked',\n input: { effectiveOn: IsoDate; reason: string },\n ): Promise<HeldQualification> {\n const id = this.id('heldQualificationId', heldQualificationId);\n const effectiveOn = assertIsoDate('effectiveOn', input.effectiveOn);\n await this.transact(async (db, queue) => {\n const row = await this.held(db, id);\n const siblings = await this.rows(db, {\n profileId: row.profileId,\n qualificationId: row.qualificationId,\n });\n if (siblings.some((other) => other.renewalOfId === id))\n throw invalid(\n `This qualification was renewed and cannot be ${kind} by itself; act on the latest row in its renewal chain.`,\n );\n // The change is the chain's: it may take effect while an earlier row is\n // the one in force, before this row's own issue date.\n const byId = new Map(\n siblings.map((other) => [other.id as string, other]),\n );\n const chain: HeldQualification[] = [];\n for (\n let member: HeldQualification | undefined = row;\n member && !chain.includes(member);\n member = member.renewalOfId ? byId.get(member.renewalOfId) : undefined\n )\n chain.push(member);\n const firstIssuedOn = chain.reduce(\n (first, member) => (member.issuedOn < first ? member.issuedOn : first),\n row.issuedOn,\n );\n if (effectiveOn < firstIssuedOn)\n throw invalid(\n `effectiveOn must not be before the qualification was issued on ${firstIssuedOn}.`,\n );\n const changes = await this.changesByHeld(db, chain);\n const history = chain.flatMap(\n (member) => changes.get(member.id as string) ?? [],\n );\n if (kind === 'revoked') {\n // Terminal, and the earliest one wins in replay, so it needs no\n // ordering against other changes: only an earlier or same-day\n // revocation makes it pointless.\n const revokedOn = revokedFrom(history);\n if (revokedOn !== null && revokedOn <= effectiveOn)\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n `This qualification is already revoked from ${revokedOn}.`,\n );\n } else {\n // Suspensions and reinstatements are recorded in date order, so the\n // replay never has to reorder them.\n const changedOn = latestOf(history, SUSPENSION_KINDS);\n if (changedOn !== null && effectiveOn < changedOn)\n throw invalid(\n `effectiveOn must not be before the latest suspension or reinstatement on ${changedOn}.`,\n );\n const standing = standingOn(history, effectiveOn) ?? 'valid';\n if (standing !== (kind === 'suspended' ? 'valid' : 'suspended'))\n throw new HrError(\n 'HR_STATUS_TRANSITION',\n standing === 'valid'\n ? `A qualification that is not suspended on ${effectiveOn} cannot be reinstated.`\n : `A qualification that is ${standing} on ${effectiveOn} cannot be ${kind}.`,\n );\n }\n // `revoked` is final in storage: a row that already stores it is never\n // saved again, and the dated change alone carries the new fact.\n if (row.status !== 'revoked') {\n const lapsed = (changes.get(id) ?? []).some(\n (change) => change.kind === 'expired',\n );\n row.status =\n kind === 'reinstated' ? (lapsed ? 'expired' : 'valid') : kind;\n await persistHr(row);\n }\n await this.recordChange(db, row, kind, effectiveOn, input.reason);\n this.announce(queue, `held-qualification.${kind}`, row, effectiveOn);\n });\n return this.held(this.db, id);\n }\n\n private async recordChange(\n db: DatabaseInterface,\n row: HeldQualification,\n kind: HeldQualificationChangeKind,\n effectiveOn: IsoDate,\n reason: string,\n ): Promise<void> {\n await insertHr(HeldQualificationChange, db, {\n tenantId: this.actor.tenantId,\n heldQualificationId: row.id as string,\n kind,\n effectiveOn,\n reason,\n actorProfileId: this.actor.profileId,\n });\n }\n\n private announce(\n queue: HrEventQueue,\n type: HeldQualificationEventType,\n heldQualification: HeldQualification,\n effectiveOn: IsoDate,\n ): void {\n queue({\n type,\n heldQualification,\n effectiveOn,\n at: new Date(),\n byProfileId: this.actor.profileId,\n });\n }\n}\n"],"mappings":"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"}