@thejob/schema 2.6.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -685,7 +685,7 @@ var GroupFilterSchema = rejectUnknownKeys(
685
685
  );
686
686
 
687
687
  // src/job/job.schema.ts
688
- var import_yup20 = require("yup");
688
+ var import_yup21 = require("yup");
689
689
 
690
690
  // src/location/location.schema.ts
691
691
  var import_yup7 = require("yup");
@@ -904,6 +904,349 @@ var SkillSchema = (0, import_yup15.object)({
904
904
  tags: (0, import_yup15.array)().of((0, import_yup15.string)().max(50)).max(20).optional().label("Tags")
905
905
  }).concat(DbDefaultSchema).noUnknown().strict().label("Skill");
906
906
 
907
+ // src/taxonomy/taxonomy.schema.ts
908
+ var import_yup16 = require("yup");
909
+
910
+ // src/taxonomy/taxonomy.constant.ts
911
+ var SupportedConceptSources = ["seed", "manual", "import"];
912
+ var SupportedTaxonomySources = ["seed", "manual"];
913
+ var SupportedTaxonomyNodeKinds = ["category", "subcategory", "group"];
914
+ var SupportedPrefilterRules = [
915
+ "spoken_language",
916
+ "no_letters",
917
+ "url_or_email",
918
+ "fragment"
919
+ ];
920
+ var SupportedUnmatchedResolutions = ["pending", "added", "rejected"];
921
+ var SupportedUnmatchedReasons = ["ambiguous"];
922
+ var SupportedProposalKinds = ["concept", "alias", "placement"];
923
+ var SupportedProposalFilters = [...SupportedProposalKinds, "uncategorized"];
924
+ var SupportedProposalStatuses = ["draft", "approved", "discarded"];
925
+ var SupportedProposalMessageRoles = ["operator", "model"];
926
+ var SupportedResolverVerdicts = [
927
+ "same",
928
+ "narrower",
929
+ "broader",
930
+ "related",
931
+ "new_concept",
932
+ "out_of_scope"
933
+ ];
934
+ var SupportedAuditStatuses = ["pending", "correct", "wrong"];
935
+ var SupportedReleaseKinds = ["seed", "manual", "review", "auto"];
936
+ var SupportedReleaseStatuses = ["open", "rolled_back"];
937
+ var SupportedMatchTypes = ["exact", "canonical", "fuzzy", "none"];
938
+ var SupportedTaxonomyJobKinds = [
939
+ "describe-concepts",
940
+ "retype-concepts",
941
+ "generate-aliases",
942
+ "alias-backfill",
943
+ "close-gaps",
944
+ "propose-placements",
945
+ "place-unplaced",
946
+ "calibrate-placement",
947
+ "reseed",
948
+ "reindex",
949
+ "calibrate-resolver",
950
+ "golden",
951
+ "resolve-backlog"
952
+ ];
953
+ var SupportedTaxonomyJobStates = ["running", "succeeded", "failed", "cancelled"];
954
+ var CONCEPT_ID_PATTERN = /^[a-z][a-z0-9-]*_[a-z0-9-]+$/;
955
+ var VOCABULARY_ID_PATTERN = /^[a-z][a-z0-9-]{0,31}$/;
956
+ var TAXONOMY_NODE_ID_PATTERN = /^[A-Za-z0-9_]{1,64}$/;
957
+ var CONCEPT_TYPE_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
958
+ var SCHEDULE_MIN_HOURS = 1;
959
+ var SCHEDULE_MAX_HOURS = 24 * 30;
960
+
961
+ // src/taxonomy/taxonomy.schema.ts
962
+ var conceptId = () => (0, import_yup16.string)().matches(CONCEPT_ID_PATTERN, "conceptId must look like <vocabulary>_<slug>");
963
+ var vocabularyId = () => (0, import_yup16.string)().matches(VOCABULARY_ID_PATTERN, "vocabulary must match [a-z][a-z0-9-]{0,31}");
964
+ var nodeId = () => (0, import_yup16.string)().matches(TAXONOMY_NODE_ID_PATTERN, "node ids look like CAT_01_TECH");
965
+ var strings = () => (0, import_yup16.array)((0, import_yup16.string)().trim().min(1).required());
966
+ var VocabularyTypeSchema = (0, import_yup16.object)({
967
+ id: (0, import_yup16.string)().matches(CONCEPT_TYPE_PATTERN, "type ids match [a-z][a-z0-9_]*").required().label("Type ID"),
968
+ /** Shown to the model when it classifies a concept; include examples. */
969
+ description: (0, import_yup16.string)().defined().label("Description")
970
+ }).label("Vocabulary Type");
971
+ var VocabularySchema = (0, import_yup16.object)({
972
+ vocabularyId: vocabularyId().required().label("Vocabulary ID"),
973
+ name: (0, import_yup16.string)().trim().min(1).required().label("Name"),
974
+ /** What one concept is called in prompts and messages: "skill", "role". */
975
+ noun: (0, import_yup16.string)().trim().min(1).required().label("Noun"),
976
+ nounPlural: (0, import_yup16.string)().trim().min(1).required().label("Noun (plural)"),
977
+ /** What belongs in this vocabulary, for prompts. */
978
+ description: (0, import_yup16.string)().trim().min(1).required().label("Description"),
979
+ /** Where its terms are written, for the "swap it in" test: "a job posting". */
980
+ appearsIn: (0, import_yup16.string)().trim().min(1).required().label("Appears In"),
981
+ /** Things that are NOT concepts of this vocabulary, for prompts. */
982
+ outOfScope: (0, import_yup16.string)().trim().min(1).required().label("Out of Scope"),
983
+ types: (0, import_yup16.array)(VocabularyTypeSchema.required()).min(1).required().test(
984
+ "unique-type-ids",
985
+ "type ids must be unique",
986
+ (types) => !types || new Set(types.map((t) => t.id)).size === types.length
987
+ ).label("Types"),
988
+ /** The type a concept gets when nothing more specific is known. One of `types`. */
989
+ defaultType: (0, import_yup16.string)().required().test("default-type-listed", "defaultType must be one of the type ids", function(value) {
990
+ const types = this.parent.types ?? [];
991
+ return types.some((t) => t.id === value);
992
+ }).label("Default Type"),
993
+ prefilter: (0, import_yup16.array)((0, import_yup16.string)().oneOf(SupportedPrefilterRules).required()).default([]).label("Prefilter"),
994
+ /**
995
+ * Words that qualify a term without changing what it names ("fluent",
996
+ * "native"), dropped from its canonical lookup form in THIS vocabulary only.
997
+ */
998
+ qualifiers: strings().optional().label("Qualifiers"),
999
+ /** Extra instructions per model task; each is optional. */
1000
+ guidance: (0, import_yup16.object)({
1001
+ describe: (0, import_yup16.string)().optional(),
1002
+ classify: (0, import_yup16.string)().optional(),
1003
+ aliases: (0, import_yup16.string)().optional(),
1004
+ verify: (0, import_yup16.string)().optional(),
1005
+ place: (0, import_yup16.string)().optional()
1006
+ }).label("Guidance"),
1007
+ source: (0, import_yup16.string)().oneOf(SupportedTaxonomySources).required().label("Source"),
1008
+ createdAt: (0, import_yup16.number)().required().label("Created At"),
1009
+ updatedAt: (0, import_yup16.number)().required().label("Updated At")
1010
+ }).label("Vocabulary");
1011
+ var TaxonomyNodeSchema = (0, import_yup16.object)({
1012
+ nodeId: nodeId().required().label("Node ID"),
1013
+ vocabulary: vocabularyId().required().label("Vocabulary"),
1014
+ kind: (0, import_yup16.string)().oneOf(SupportedTaxonomyNodeKinds).required().label("Kind"),
1015
+ name: (0, import_yup16.string)().trim().min(1).required().label("Name"),
1016
+ /** Groups carry a description; categories and subcategories usually do not. */
1017
+ description: (0, import_yup16.string)().optional().label("Description"),
1018
+ aliases: strings().default([]).label("Aliases"),
1019
+ /** null for categories (the roots). */
1020
+ parentId: (0, import_yup16.string)().nullable().defined().label("Parent ID"),
1021
+ /** Ancestor ids from the root down to and including this node. */
1022
+ path: (0, import_yup16.array)((0, import_yup16.string)().required()).default([]).label("Path"),
1023
+ source: (0, import_yup16.string)().oneOf(SupportedTaxonomySources).optional().label("Source"),
1024
+ createdAt: (0, import_yup16.number)().required().label("Created At"),
1025
+ updatedAt: (0, import_yup16.number)().required().label("Updated At")
1026
+ }).label("Taxonomy Node");
1027
+ var ConceptMembershipSchema = (0, import_yup16.object)({
1028
+ categoryId: (0, import_yup16.string)().required().label("Category ID"),
1029
+ categoryName: (0, import_yup16.string)().required().label("Category Name"),
1030
+ subcategoryId: (0, import_yup16.string)().required().label("Subcategory ID"),
1031
+ subcategoryName: (0, import_yup16.string)().required().label("Subcategory Name"),
1032
+ groupId: (0, import_yup16.string)().required().label("Group ID"),
1033
+ groupName: (0, import_yup16.string)().required().label("Group Name"),
1034
+ /** The source record this membership came from; '' when written by hand. */
1035
+ sourceId: (0, import_yup16.string)().default("").label("Source ID")
1036
+ }).label("Concept Membership");
1037
+ var ConceptSchema = (0, import_yup16.object)({
1038
+ /** `<vocabulary>_<slug>`, e.g. `skills_power-bi`. Primary key. */
1039
+ conceptId: conceptId().required().label("Concept ID"),
1040
+ vocabulary: vocabularyId().required().label("Vocabulary"),
1041
+ name: (0, import_yup16.string)().trim().min(1).max(200).required().label("Name"),
1042
+ /** One of the vocabulary's type ids; checked against the vocabulary by the service. */
1043
+ type: (0, import_yup16.string)().trim().matches(CONCEPT_TYPE_PATTERN, "type must be a type id").required().label("Type"),
1044
+ description: (0, import_yup16.string)().trim().max(2e3).default("").label("Description"),
1045
+ aliases: strings().default([]).label("Aliases"),
1046
+ memberships: (0, import_yup16.array)(ConceptMembershipSchema.required()).default([]).label("Memberships"),
1047
+ /** Always concepts of the same vocabulary. */
1048
+ relatedConceptIds: strings().default([]).label("Related Concepts"),
1049
+ source: (0, import_yup16.string)().oneOf(SupportedConceptSources).required().label("Source"),
1050
+ createdAt: (0, import_yup16.number)().required().label("Created At"),
1051
+ updatedAt: (0, import_yup16.number)().required().label("Updated At"),
1052
+ /** Resume marker of `retype-concepts`: set once the type was examined. */
1053
+ retypedAt: (0, import_yup16.number)().optional().label("Retyped At"),
1054
+ /** Resume marker of `generate-aliases`: set even when no alias was added. */
1055
+ aliasedAt: (0, import_yup16.number)().optional().label("Aliased At"),
1056
+ /** Set while `description` is a stand-in; removed once a real one is written. */
1057
+ descriptionPending: (0, import_yup16.boolean)().isTrue().optional().label("Description Pending")
1058
+ }).label("Concept");
1059
+ var UnmatchedTermSchema = (0, import_yup16.object)({
1060
+ vocabulary: vocabularyId().required().label("Vocabulary"),
1061
+ /** The raw input as first received. */
1062
+ term: (0, import_yup16.string)().required().label("Term"),
1063
+ /** The dedupe key, so "React JS" and "react.js" share a row. */
1064
+ normalizedTerm: (0, import_yup16.string)().required().label("Normalized Term"),
1065
+ count: (0, import_yup16.number)().integer().min(0).required().label("Count"),
1066
+ firstSeenAt: (0, import_yup16.number)().required().label("First Seen At"),
1067
+ lastSeenAt: (0, import_yup16.number)().required().label("Last Seen At"),
1068
+ /** Which callers sent it, capped. */
1069
+ sources: (0, import_yup16.array)(
1070
+ (0, import_yup16.object)({
1071
+ service: (0, import_yup16.string)().required(),
1072
+ context: (0, import_yup16.string)().optional()
1073
+ }).required()
1074
+ ).default([]).label("Sources"),
1075
+ /**
1076
+ * Where the term was seen, capped: the caller's title and the terms sent with
1077
+ * it. What the resolver reads to tell "CTS" on a chip-design posting from "CTS"
1078
+ * on an ad-tech one; a bare term cannot carry that.
1079
+ */
1080
+ samples: (0, import_yup16.array)(
1081
+ (0, import_yup16.object)({
1082
+ title: (0, import_yup16.string)().optional(),
1083
+ siblings: (0, import_yup16.array)((0, import_yup16.string)().required()).default([])
1084
+ }).required()
1085
+ ).default([]).label("Samples"),
1086
+ resolution: (0, import_yup16.string)().oneOf(SupportedUnmatchedResolutions).required().label("Resolution"),
1087
+ resolvedConceptId: (0, import_yup16.string)().optional().label("Resolved Concept"),
1088
+ /** `ambiguous`: the concept exists; the caller gave no context that picked one. */
1089
+ reason: (0, import_yup16.string)().oneOf(SupportedUnmatchedReasons).optional().label("Reason"),
1090
+ /** The concepts an ambiguous term could mean. */
1091
+ candidates: (0, import_yup16.array)((0, import_yup16.string)().required()).optional().label("Candidates"),
1092
+ /** Who rejected it when it was not a human: `prefilter:<rule>` or `model`. */
1093
+ rejectedBy: (0, import_yup16.string)().optional().label("Rejected By")
1094
+ }).label("Unmatched Term");
1095
+ var ProposalCandidateSchema = (0, import_yup16.object)({
1096
+ groupId: (0, import_yup16.string)().required(),
1097
+ groupName: (0, import_yup16.string)().required(),
1098
+ subcategoryId: (0, import_yup16.string)().required(),
1099
+ subcategoryName: (0, import_yup16.string)().required(),
1100
+ categoryId: (0, import_yup16.string)().required(),
1101
+ categoryName: (0, import_yup16.string)().required(),
1102
+ score: (0, import_yup16.number)().required(),
1103
+ nearestMembers: (0, import_yup16.array)((0, import_yup16.string)().required()).default([])
1104
+ }).label("Proposal Candidate");
1105
+ var ProposalMessageSchema = (0, import_yup16.object)({
1106
+ role: (0, import_yup16.string)().oneOf(SupportedProposalMessageRoles).required(),
1107
+ /** The operator's instruction, or the model's note on what it changed and why. */
1108
+ content: (0, import_yup16.string)().defined(),
1109
+ /** Which model answered. Absent on operator turns. */
1110
+ model: (0, import_yup16.string)().optional(),
1111
+ at: (0, import_yup16.number)().required()
1112
+ }).label("Proposal Message");
1113
+ var SuggestedHomeSchema = (0, import_yup16.object)({
1114
+ categoryName: (0, import_yup16.string)().defined(),
1115
+ subcategoryName: (0, import_yup16.string)().defined(),
1116
+ groupName: (0, import_yup16.string)().defined()
1117
+ }).label("Suggested Home");
1118
+ var ResolverVerificationSchema = (0, import_yup16.object)({
1119
+ verdict: (0, import_yup16.string)().oneOf(SupportedResolverVerdicts).required(),
1120
+ /** The shown concept the verdict is about; null for `new_concept` / `out_of_scope`. */
1121
+ conceptId: (0, import_yup16.string)().nullable().defined(),
1122
+ evidence: (0, import_yup16.string)().defined(),
1123
+ /** What the judge took the term to mean, abbreviations expanded. */
1124
+ means: (0, import_yup16.string)().optional(),
1125
+ /** For `same`: did the independent confirm step agree? */
1126
+ consistent: (0, import_yup16.boolean)().optional(),
1127
+ confirm: (0, import_yup16.object)({
1128
+ answer: (0, import_yup16.string)().oneOf(["yes", "no"]).required(),
1129
+ evidence: (0, import_yup16.string)().defined()
1130
+ }).optional().default(void 0)
1131
+ }).label("Resolver Verification");
1132
+ var ConceptProposalSchema = (0, import_yup16.object)({
1133
+ proposalId: (0, import_yup16.string)().required().label("Proposal ID"),
1134
+ vocabulary: vocabularyId().required().label("Vocabulary"),
1135
+ /** The miss this closes; `normalizedTerm` links back to its unmatched row. */
1136
+ term: (0, import_yup16.string)().required().label("Term"),
1137
+ normalizedTerm: (0, import_yup16.string)().required().label("Normalized Term"),
1138
+ /** Demand at proposal time, so the queue ranks without a join. */
1139
+ count: (0, import_yup16.number)().integer().min(0).required().label("Count"),
1140
+ /** Other misses folded into this draft because they name the same new concept. */
1141
+ mergedTerms: (0, import_yup16.array)(
1142
+ (0, import_yup16.object)({
1143
+ term: (0, import_yup16.string)().required(),
1144
+ normalizedTerm: (0, import_yup16.string)().required(),
1145
+ count: (0, import_yup16.number)().integer().min(0).required()
1146
+ }).required()
1147
+ ).optional().label("Merged Terms"),
1148
+ /** Absent on drafts that predate the field: read as `concept`. */
1149
+ kind: (0, import_yup16.string)().oneOf(SupportedProposalKinds).optional().label("Kind"),
1150
+ /** For `alias`: the concept the term names. For `placement`: the concept to place. */
1151
+ targetConceptId: (0, import_yup16.string)().optional().label("Target Concept"),
1152
+ targetName: (0, import_yup16.string)().optional().label("Target Name"),
1153
+ // The draft concept, editable until approved. Empty strings are valid here:
1154
+ // an alias draft carries no draft concept of its own.
1155
+ name: (0, import_yup16.string)().defined().label("Name"),
1156
+ type: (0, import_yup16.string)().defined().label("Type"),
1157
+ description: (0, import_yup16.string)().defined().label("Description"),
1158
+ aliases: (0, import_yup16.array)((0, import_yup16.string)().required()).default([]).label("Aliases"),
1159
+ /** Empty when no group fits: approved that way, the concept goes live uncategorized. */
1160
+ memberships: (0, import_yup16.array)(ConceptMembershipSchema.required()).default([]).label("Memberships"),
1161
+ suggestedHome: SuggestedHomeSchema.nullable().optional().default(void 0).label("Suggested Home"),
1162
+ /** Resolved to concepts that exist; names that matched nothing are dropped. */
1163
+ relatedConceptIds: (0, import_yup16.array)((0, import_yup16.string)().required()).default([]).label("Related Concepts"),
1164
+ /** Every placement candidate considered, best first. */
1165
+ candidates: (0, import_yup16.array)(ProposalCandidateSchema.required()).default([]).label("Candidates"),
1166
+ needsReview: (0, import_yup16.boolean)().required().label("Needs Review"),
1167
+ reason: (0, import_yup16.string)().optional().label("Reason"),
1168
+ verification: ResolverVerificationSchema.optional().default(void 0).label("Verification"),
1169
+ /** What the auto-accept gate decided for an alias verdict. */
1170
+ autoAccept: (0, import_yup16.object)({
1171
+ accepted: (0, import_yup16.boolean)().required(),
1172
+ reason: (0, import_yup16.string)().optional(),
1173
+ tier: (0, import_yup16.string)().optional(),
1174
+ calibrationId: (0, import_yup16.string)().optional(),
1175
+ releaseId: (0, import_yup16.string)().optional()
1176
+ }).optional().default(void 0).label("Auto Accept"),
1177
+ /** Which model drafted it. */
1178
+ model: (0, import_yup16.string)().defined().label("Model"),
1179
+ /** The refinement conversation, oldest first. */
1180
+ messages: (0, import_yup16.array)(ProposalMessageSchema.required()).default([]).label("Messages"),
1181
+ status: (0, import_yup16.string)().oneOf(SupportedProposalStatuses).required().label("Status"),
1182
+ /** Set when approved: the concept this became. */
1183
+ resolvedConceptId: (0, import_yup16.string)().optional().label("Resolved Concept"),
1184
+ createdAt: (0, import_yup16.number)().required().label("Created At"),
1185
+ updatedAt: (0, import_yup16.number)().required().label("Updated At")
1186
+ }).label("Concept Proposal");
1187
+ var ReleaseSchema = (0, import_yup16.object)({
1188
+ releaseId: (0, import_yup16.string)().required().label("Release ID"),
1189
+ kind: (0, import_yup16.string)().oneOf(SupportedReleaseKinds).required().label("Kind"),
1190
+ status: (0, import_yup16.string)().oneOf(SupportedReleaseStatuses).required().label("Status"),
1191
+ createdBy: (0, import_yup16.string)().required().label("Created By"),
1192
+ createdAt: (0, import_yup16.number)().required().label("Created At"),
1193
+ note: (0, import_yup16.string)().optional().label("Note"),
1194
+ rolledBackAt: (0, import_yup16.number)().optional().label("Rolled Back At"),
1195
+ rolledBackBy: (0, import_yup16.string)().optional().label("Rolled Back By")
1196
+ }).label("Release");
1197
+ var AuditSchema = (0, import_yup16.object)({
1198
+ auditId: (0, import_yup16.string)().required().label("Audit ID"),
1199
+ releaseId: (0, import_yup16.string)().required().label("Release ID"),
1200
+ key: (0, import_yup16.string)().required().label("Key"),
1201
+ term: (0, import_yup16.string)().required().label("Term"),
1202
+ conceptId: (0, import_yup16.string)().required().label("Concept ID"),
1203
+ tier: (0, import_yup16.string)().optional().label("Tier"),
1204
+ status: (0, import_yup16.string)().oneOf(SupportedAuditStatuses).required().label("Status"),
1205
+ createdAt: (0, import_yup16.number)().required().label("Created At"),
1206
+ resolvedAt: (0, import_yup16.number)().optional().label("Resolved At"),
1207
+ resolvedBy: (0, import_yup16.string)().optional().label("Resolved By")
1208
+ }).label("Audit");
1209
+ var ResolverSettingsSchema = (0, import_yup16.object)({
1210
+ autoAccept: (0, import_yup16.object)({
1211
+ /** Stop switch for WRITING auto keys. Off until a calibration passes. */
1212
+ enabled: (0, import_yup16.boolean)().required(),
1213
+ /** Minimum calibrated precision (Wilson 95% lower bound) for a tier to auto-accept. */
1214
+ target: (0, import_yup16.number)().min(0.5).lessThan(1).required(),
1215
+ maxPerRun: (0, import_yup16.number)().integer().min(0).required(),
1216
+ maxPerDay: (0, import_yup16.number)().integer().min(0).required(),
1217
+ /** Share of each auto release sampled into the audit queue (0..1). */
1218
+ auditRate: (0, import_yup16.number)().min(0).max(1).required()
1219
+ }).label("Auto Accept"),
1220
+ /** Stop switch for SERVING auto keys: false hides them from lookups at once. */
1221
+ serveAutoKeys: (0, import_yup16.boolean)().required().label("Serve Auto Keys"),
1222
+ /** Minimum Meilisearch ranking score (0..1) for a fuzzy hit to be considered. */
1223
+ fuzzyMinScore: (0, import_yup16.number)().min(0).max(1).required().label("Fuzzy Min Score"),
1224
+ /** Per-client limit on public search, per minute per replica. 0 disables it. */
1225
+ searchRateLimitPerMin: (0, import_yup16.number)().integer().min(0).required().label("Search Rate Limit"),
1226
+ /** Master switch for scheduled maintenance jobs. */
1227
+ scheduledJobs: (0, import_yup16.boolean)().required().label("Scheduled Jobs"),
1228
+ updatedAt: (0, import_yup16.number)().optional().label("Updated At"),
1229
+ updatedBy: (0, import_yup16.string)().optional().label("Updated By"),
1230
+ /** Why auto-accept was last switched off automatically. */
1231
+ trippedReason: (0, import_yup16.string)().optional().label("Tripped Reason")
1232
+ }).label("Resolver Settings");
1233
+ var TaxonomyScheduleSchema = (0, import_yup16.object)({
1234
+ kind: (0, import_yup16.string)().oneOf(SupportedTaxonomyJobKinds).required().label("Job"),
1235
+ enabled: (0, import_yup16.boolean)().required().label("Enabled"),
1236
+ everyHours: (0, import_yup16.number)().min(SCHEDULE_MIN_HOURS).max(SCHEDULE_MAX_HOURS).required().label("Every (hours)"),
1237
+ /** Last time the scheduler fired this job. */
1238
+ lastRunAt: (0, import_yup16.number)().optional().label("Last Run At"),
1239
+ updatedAt: (0, import_yup16.number)().required().label("Updated At")
1240
+ }).label("Taxonomy Schedule");
1241
+ var NormalizeContextSchema = (0, import_yup16.object)({
1242
+ /** The title of the record the terms come from. */
1243
+ title: (0, import_yup16.string)().max(200).optional(),
1244
+ /** Backbone node ids (category, subcategory or group). */
1245
+ categoryIds: (0, import_yup16.array)(nodeId().required()).max(10).optional(),
1246
+ /** Other terms from the same record, in the same vocabulary. */
1247
+ siblings: (0, import_yup16.array)((0, import_yup16.string)().max(200).required()).max(50).optional()
1248
+ }).label("Normalize Context");
1249
+
907
1250
  // src/question/question.constant.ts
908
1251
  var QuestionType = /* @__PURE__ */ ((QuestionType2) => {
909
1252
  QuestionType2["Input"] = "input";
@@ -920,24 +1263,24 @@ var AnswerChoiceType = /* @__PURE__ */ ((AnswerChoiceType2) => {
920
1263
  var SupportedAnswerChoiceTypes = Object.values(AnswerChoiceType);
921
1264
 
922
1265
  // src/question/question.schema.ts
923
- var import_yup16 = require("yup");
924
- var QuestionSchema = (0, import_yup16.object)().shape({
925
- label: (0, import_yup16.string)().max(50).optional().label("Label"),
926
- default: (0, import_yup16.boolean)().optional().default(false).label("Default"),
927
- title: (0, import_yup16.string)().required().max(200).label("Title"),
928
- type: (0, import_yup16.string)().oneOf(SupportedQuestionTypes).required().label("Type"),
929
- isOptional: (0, import_yup16.boolean)().optional().label("Mark if the question is optional"),
930
- readonly: (0, import_yup16.boolean)().optional().label("Readonly"),
931
- isNew: (0, import_yup16.boolean)().optional().label("Is New")
1266
+ var import_yup17 = require("yup");
1267
+ var QuestionSchema = (0, import_yup17.object)().shape({
1268
+ label: (0, import_yup17.string)().max(50).optional().label("Label"),
1269
+ default: (0, import_yup17.boolean)().optional().default(false).label("Default"),
1270
+ title: (0, import_yup17.string)().required().max(200).label("Title"),
1271
+ type: (0, import_yup17.string)().oneOf(SupportedQuestionTypes).required().label("Type"),
1272
+ isOptional: (0, import_yup17.boolean)().optional().label("Mark if the question is optional"),
1273
+ readonly: (0, import_yup17.boolean)().optional().label("Readonly"),
1274
+ isNew: (0, import_yup17.boolean)().optional().label("Is New")
932
1275
  });
933
1276
 
934
1277
  // src/question/input-question.schema.ts
935
- var import_yup17 = require("yup");
1278
+ var import_yup18 = require("yup");
936
1279
  var InputQuestionSchema = QuestionSchema.concat(
937
- (0, import_yup17.object)().shape({
938
- type: (0, import_yup17.string)().oneOf(["input" /* Input */]).required().label("Question type"),
939
- preferredAnswer: (0, import_yup17.string)().max(100).optional().label("Preferred Answer"),
940
- answers: (0, import_yup17.string)().max(500).when("isOptional", {
1280
+ (0, import_yup18.object)().shape({
1281
+ type: (0, import_yup18.string)().oneOf(["input" /* Input */]).required().label("Question type"),
1282
+ preferredAnswer: (0, import_yup18.string)().max(100).optional().label("Preferred Answer"),
1283
+ answers: (0, import_yup18.string)().max(500).when("isOptional", {
941
1284
  is: (isOptional) => Boolean(isOptional),
942
1285
  then: (schema) => schema.optional(),
943
1286
  otherwise: (schema) => schema.required()
@@ -946,10 +1289,10 @@ var InputQuestionSchema = QuestionSchema.concat(
946
1289
  );
947
1290
 
948
1291
  // src/question/choice-question.schema.ts
949
- var import_yup18 = require("yup");
950
- var ChoicePreferredAnswerSchema = (label = "Preferred answer") => (0, import_yup18.mixed)().when("answerChoiceType", {
1292
+ var import_yup19 = require("yup");
1293
+ var ChoicePreferredAnswerSchema = (label = "Preferred answer") => (0, import_yup19.mixed)().when("answerChoiceType", {
951
1294
  is: (value) => value === "multiple" /* Multiple */,
952
- then: () => (0, import_yup18.array)((0, import_yup18.string)()).test(
1295
+ then: () => (0, import_yup19.array)((0, import_yup19.string)()).test(
953
1296
  "is-valid-option",
954
1297
  `${label} must be from the provided options`,
955
1298
  function(answer) {
@@ -960,7 +1303,7 @@ var ChoicePreferredAnswerSchema = (label = "Preferred answer") => (0, import_yup
960
1303
  return true;
961
1304
  }
962
1305
  ).optional().label(label),
963
- otherwise: () => (0, import_yup18.string)().test(
1306
+ otherwise: () => (0, import_yup19.string)().test(
964
1307
  "is-valid-option",
965
1308
  `${label} must be from the provided options`,
966
1309
  function(answer) {
@@ -971,20 +1314,20 @@ var ChoicePreferredAnswerSchema = (label = "Preferred answer") => (0, import_yup
971
1314
  });
972
1315
  var getChoiceAnswerSchema = (answerChoiceType, isOptional) => {
973
1316
  if (answerChoiceType === "multiple" /* Multiple */) {
974
- const multiAnswerSchema = (0, import_yup18.array)((0, import_yup18.string)().max(500));
1317
+ const multiAnswerSchema = (0, import_yup19.array)((0, import_yup19.string)().max(500));
975
1318
  return isOptional ? multiAnswerSchema.optional().default([]) : multiAnswerSchema.min(1, "Select at least one option.").required("Select at least one option.").default([]);
976
1319
  }
977
- const singleAnswerSchema = (0, import_yup18.string)().max(500);
1320
+ const singleAnswerSchema = (0, import_yup19.string)().max(500);
978
1321
  return isOptional ? singleAnswerSchema.optional() : singleAnswerSchema.required("Select an option.");
979
1322
  };
980
1323
  var ChoiceQuestionSchema = QuestionSchema.concat(
981
- (0, import_yup18.object)().shape({
982
- type: (0, import_yup18.string)().oneOf(["choice" /* Choice */]).required().label("Question type"),
983
- optionsFrom: (0, import_yup18.string)().oneOf(["skill" /* Skill */, "education_level" /* EducationLevel */, "experience_level" /* ExperienceLevel */]).optional().label("Option field"),
984
- options: (0, import_yup18.array)((0, import_yup18.string)().required()).min(1).required().label("Options"),
985
- answerChoiceType: (0, import_yup18.string)().oneOf(SupportedAnswerChoiceTypes).required().default("single" /* Single */).label("Type"),
1324
+ (0, import_yup19.object)().shape({
1325
+ type: (0, import_yup19.string)().oneOf(["choice" /* Choice */]).required().label("Question type"),
1326
+ optionsFrom: (0, import_yup19.string)().oneOf(["skill" /* Skill */, "education_level" /* EducationLevel */, "experience_level" /* ExperienceLevel */]).optional().label("Option field"),
1327
+ options: (0, import_yup19.array)((0, import_yup19.string)().required()).min(1).required().label("Options"),
1328
+ answerChoiceType: (0, import_yup19.string)().oneOf(SupportedAnswerChoiceTypes).required().default("single" /* Single */).label("Type"),
986
1329
  preferredAnswer: ChoicePreferredAnswerSchema("Preferred answer"),
987
- answers: (0, import_yup18.mixed)().when(
1330
+ answers: (0, import_yup19.mixed)().when(
988
1331
  ["answerChoiceType", "isOptional"],
989
1332
  ([answerChoiceType, isOptional]) => getChoiceAnswerSchema(answerChoiceType, isOptional)
990
1333
  )
@@ -992,12 +1335,12 @@ var ChoiceQuestionSchema = QuestionSchema.concat(
992
1335
  );
993
1336
 
994
1337
  // src/question/read-and-acknowledge.schema.ts
995
- var import_yup19 = require("yup");
996
- var AcknowledgeAnswerSchema = (0, import_yup19.string)().oneOf(["yes" /* Yes */, "no" /* No */]).required("This question must be acknowledged.");
1338
+ var import_yup20 = require("yup");
1339
+ var AcknowledgeAnswerSchema = (0, import_yup20.string)().oneOf(["yes" /* Yes */, "no" /* No */]).required("This question must be acknowledged.");
997
1340
  var ReadAndAcknowledgeQuestionSchema = QuestionSchema.concat(
998
- (0, import_yup19.object)().shape({
999
- type: (0, import_yup19.string)().oneOf(["readAndAcknowledge" /* ReadAndAcknowledge */]).required().label("Question type"),
1000
- description: (0, import_yup19.string)().max(1e4).required().label("Content"),
1341
+ (0, import_yup20.object)().shape({
1342
+ type: (0, import_yup20.string)().oneOf(["readAndAcknowledge" /* ReadAndAcknowledge */]).required().label("Question type"),
1343
+ description: (0, import_yup20.string)().max(1e4).required().label("Content"),
1001
1344
  preferredAnswer: AcknowledgeAnswerSchema.label("Preferred Answer"),
1002
1345
  answers: AcknowledgeAnswerSchema.label("Answer")
1003
1346
  })
@@ -1078,20 +1421,20 @@ var SupportedApplicationReceivePreferences = Object.values(
1078
1421
  );
1079
1422
 
1080
1423
  // src/job/job.schema.ts
1081
- var JobSchema = (0, import_yup20.object)({
1424
+ var JobSchema = (0, import_yup21.object)({
1082
1425
  /** Unique id usable by external systems to reference the job. */
1083
- uniqueId: (0, import_yup20.string)().optional().label("Unique ID"),
1426
+ uniqueId: (0, import_yup21.string)().optional().label("Unique ID"),
1084
1427
  /** Job title shown in listings and search. */
1085
- headline: (0, import_yup20.string)().required().min(5).max(150).label("Headline"),
1428
+ headline: (0, import_yup21.string)().required().min(5).max(150).label("Headline"),
1086
1429
  /** User-friendly, unique URL identifier. */
1087
- slug: (0, import_yup20.string)().required().max(250).label("Slug"),
1430
+ slug: (0, import_yup21.string)().required().max(250).label("Slug"),
1088
1431
  /** How applications are received (inbox vs external website). */
1089
- applicationReceivePreference: (0, import_yup20.mixed)().oneOf([
1432
+ applicationReceivePreference: (0, import_yup21.mixed)().oneOf([
1090
1433
  "inbox" /* Inbox */,
1091
1434
  "external_website" /* ExternalWebsite */
1092
1435
  ]).required().label("Preference"),
1093
1436
  /** Required when applicationReceivePreference is ExternalWebsite. */
1094
- externalApplyLink: (0, import_yup20.string)().when("applicationReceivePreference", {
1437
+ externalApplyLink: (0, import_yup21.string)().when("applicationReceivePreference", {
1095
1438
  is: (value) => value === "external_website" /* ExternalWebsite */,
1096
1439
  then: (schema) => schema.url().required().label("Apply for Job link"),
1097
1440
  otherwise: (schema) => schema.optional()
@@ -1099,35 +1442,44 @@ var JobSchema = (0, import_yup20.object)({
1099
1442
  /** Company the job belongs to (subset of PageSchema). */
1100
1443
  company: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Company"),
1101
1444
  /** @deprecated kept for backwards compatibility. */
1102
- designation: (0, import_yup20.string)().optional().nullable().label("Designation"),
1103
- employmentType: (0, import_yup20.string)().required().label("Employment type"),
1104
- workMode: (0, import_yup20.string)().required().label("Work mode"),
1445
+ designation: (0, import_yup21.string)().optional().nullable().label("Designation"),
1446
+ employmentType: (0, import_yup21.string)().required().label("Employment type"),
1447
+ workMode: (0, import_yup21.string)().required().label("Work mode"),
1105
1448
  /**
1106
- * `conceptId`/`conceptName` carry the taxonomy-service normalization. They are
1107
- * picked here (not just `name`/`logo`) because SkillSchema is `.noUnknown()`:
1108
- * an unpicked field that is actually PRESENT fails validation, so omitting them
1109
- * would reject every normalized job.
1449
+ * `name` is the skill exactly as posted. `concept` is what thejob-taxonomy-service
1450
+ * resolved it to (`vocabulary: skills`), written by jobs-service, never by the
1451
+ * poster: enough of the concept to filter by id and display the canonical name
1452
+ * without a lookup. A copy, so it can go stale after a taxonomy release; see
1453
+ * `taxonomyVersion`.
1454
+ *
1455
+ * Additive, never authoritative: the raw `name` is always kept, so a wrong
1456
+ * match, a taxonomy release or an outage is recoverable by re-normalizing.
1457
+ * `concept` is absent when the term did not resolve (a normal outcome, logged
1458
+ * as a miss on the taxonomy side) or the job predates normalization, so read
1459
+ * `concept?.name ?? name`.
1110
1460
  */
1111
- skills: (0, import_yup20.array)().of(
1112
- SkillSchema.pick(["name", "logo", "conceptId", "conceptName"]).required().label("Skill")
1461
+ skills: (0, import_yup21.array)().of(
1462
+ SkillSchema.pick(["name", "logo"]).shape({
1463
+ concept: ConceptSchema.pick(["conceptId", "vocabulary", "name", "type"]).nullable().optional().default(void 0)
1464
+ }).required().label("Skill")
1113
1465
  ).nullable().optional().label("Skills"),
1114
- bookmarks: (0, import_yup20.array)().of(UserIdAndCreatedAtSchema).optional().label("Bookmarks"),
1115
- embedding: (0, import_yup20.object)({
1116
- vector: (0, import_yup20.array)((0, import_yup20.number)().required()).optional().label("Embedding vector"),
1117
- model: (0, import_yup20.string)().optional().label("Embedding model")
1466
+ bookmarks: (0, import_yup21.array)().of(UserIdAndCreatedAtSchema).optional().label("Bookmarks"),
1467
+ embedding: (0, import_yup21.object)({
1468
+ vector: (0, import_yup21.array)((0, import_yup21.number)().required()).optional().label("Embedding vector"),
1469
+ model: (0, import_yup21.string)().optional().label("Embedding model")
1118
1470
  }).nullable().optional().label("Embedding"),
1119
- applicants: (0, import_yup20.array)().of(UserIdAndCreatedAtSchema).optional().label("Applicants"),
1120
- applicantCount: (0, import_yup20.number)().optional().label("Applicant count"),
1121
- externalApplicationLinkClickCount: (0, import_yup20.number)().optional().label("External application link click count"),
1122
- description: (0, import_yup20.string)().required().min(100).max(5e4).label("Description"),
1123
- descriptionMarkdown: (0, import_yup20.string)().required().label("Description Markdown"),
1124
- descriptionHTML: (0, import_yup20.string)().optional().label("Description HTML"),
1471
+ applicants: (0, import_yup21.array)().of(UserIdAndCreatedAtSchema).optional().label("Applicants"),
1472
+ applicantCount: (0, import_yup21.number)().optional().label("Applicant count"),
1473
+ externalApplicationLinkClickCount: (0, import_yup21.number)().optional().label("External application link click count"),
1474
+ description: (0, import_yup21.string)().required().min(100).max(5e4).label("Description"),
1475
+ descriptionMarkdown: (0, import_yup21.string)().required().label("Description Markdown"),
1476
+ descriptionHTML: (0, import_yup21.string)().optional().label("Description HTML"),
1125
1477
  /**
1126
1478
  * Job locations. Required even for remote roles (compliance/legal). Defaults
1127
1479
  * to an empty array; min(1) is intentionally not enforced yet.
1128
1480
  */
1129
- locations: (0, import_yup20.array)().of(LocationSchema).default([]).label("Location"),
1130
- educationLevel: (0, import_yup20.array)((0, import_yup20.string)().oneOf(SupportedEducationLevels)).required().min(1).label("Educational level"),
1481
+ locations: (0, import_yup21.array)().of(LocationSchema).default([]).label("Location"),
1482
+ educationLevel: (0, import_yup21.array)((0, import_yup21.string)().oneOf(SupportedEducationLevels)).required().min(1).label("Educational level"),
1131
1483
  /** Posting validity window (start/expiry). */
1132
1484
  validity: DurationSchema({
1133
1485
  format: SYSTEM_DATE_FORMAT,
@@ -1140,22 +1492,22 @@ var JobSchema = (0, import_yup20.object)({
1140
1492
  * has no `validity.endDate` and must be verified by crawling its page. Guards
1141
1493
  * re-check cadence so the same job is not re-crawled on every sweep.
1142
1494
  */
1143
- lastExpiryCheckAt: (0, import_yup20.number)().optional().nullable().label("Last expiry check at"),
1495
+ lastExpiryCheckAt: (0, import_yup21.number)().optional().nullable().label("Last expiry check at"),
1144
1496
  /**
1145
1497
  * When this job transitioned to `status: closed` (epoch ms). Server-owned
1146
1498
  * operational field: stamped by every jobs-service close path (expiry
1147
1499
  * auto-close and the dated lapse sweep). Distinct from `updatedAt` so a later
1148
1500
  * edit to a closed job does not move it.
1149
1501
  */
1150
- closedAt: (0, import_yup20.number)().optional().nullable().label("Closed at"),
1151
- experienceLevel: (0, import_yup20.string)().oneOf(SupportedExperienceLevels).nullable().optional().label("Experience level"),
1152
- contactEmail: (0, import_yup20.string)().email().nullable().optional().label("Contact email"),
1153
- workingHoursPerWeek: (0, import_yup20.number)().nullable().optional().min(1).max(24 * 7).label("Working hours per week"),
1154
- tags: (0, import_yup20.array)().max(50).optional().label("Tags"),
1502
+ closedAt: (0, import_yup21.number)().optional().nullable().label("Closed at"),
1503
+ experienceLevel: (0, import_yup21.string)().oneOf(SupportedExperienceLevels).nullable().optional().label("Experience level"),
1504
+ contactEmail: (0, import_yup21.string)().email().nullable().optional().label("Contact email"),
1505
+ workingHoursPerWeek: (0, import_yup21.number)().nullable().optional().min(1).max(24 * 7).label("Working hours per week"),
1506
+ tags: (0, import_yup21.array)().max(50).optional().label("Tags"),
1155
1507
  /** Direct/Quick-apply questionnaire. Each item is validated against the
1156
1508
  * concrete question schema for its `type`, with the stored `answers` omitted. */
1157
- questionnaire: (0, import_yup20.array)().of(
1158
- (0, import_yup20.lazy)((question) => {
1509
+ questionnaire: (0, import_yup21.array)().of(
1510
+ (0, import_yup21.lazy)((question) => {
1159
1511
  const schema = getSchemaByQuestion(question.type);
1160
1512
  if (question.type === "input" /* Input */) {
1161
1513
  return schema.omit([
@@ -1171,12 +1523,12 @@ var JobSchema = (0, import_yup20.object)({
1171
1523
  return schema;
1172
1524
  })
1173
1525
  ).label("Questionnaire"),
1174
- status: (0, import_yup20.string)().oneOf(SupportedJobStatuses).default("draft" /* Draft */).required().label("Status"),
1175
- seoTags: (0, import_yup20.object)({
1176
- description: (0, import_yup20.string)().optional().label("Description"),
1177
- keywords: (0, import_yup20.array)().of((0, import_yup20.string)()).optional().label("Keywords")
1526
+ status: (0, import_yup21.string)().oneOf(SupportedJobStatuses).default("draft" /* Draft */).required().label("Status"),
1527
+ seoTags: (0, import_yup21.object)({
1528
+ description: (0, import_yup21.string)().optional().label("Description"),
1529
+ keywords: (0, import_yup21.array)().of((0, import_yup21.string)()).optional().label("Keywords")
1178
1530
  }).nullable().optional().label("SEO Tags"),
1179
- jdSummary: (0, import_yup20.string)().optional().label("JD Summary"),
1531
+ jdSummary: (0, import_yup21.string)().optional().label("JD Summary"),
1180
1532
  /**
1181
1533
  * Structured, model-extracted sections of the posting, re-organized into clean
1182
1534
  * bullet points. This is our OWN unique presentation of the job (rendered as the
@@ -1185,28 +1537,28 @@ var JobSchema = (0, import_yup20.object)({
1185
1537
  * optional: absent on jobs not yet (re-)enriched, and any section is `[]`/omitted
1186
1538
  * when the posting genuinely has none.
1187
1539
  */
1188
- jobHighlights: (0, import_yup20.object)({
1189
- responsibilities: (0, import_yup20.array)().of((0, import_yup20.string)()).optional().label("Responsibilities"),
1190
- requirements: (0, import_yup20.array)().of((0, import_yup20.string)()).optional().label("Requirements"),
1191
- benefits: (0, import_yup20.array)().of((0, import_yup20.string)()).optional().label("Benefits"),
1192
- qualifications: (0, import_yup20.array)().of((0, import_yup20.string)()).optional().label("Qualifications")
1540
+ jobHighlights: (0, import_yup21.object)({
1541
+ responsibilities: (0, import_yup21.array)().of((0, import_yup21.string)()).optional().label("Responsibilities"),
1542
+ requirements: (0, import_yup21.array)().of((0, import_yup21.string)()).optional().label("Requirements"),
1543
+ benefits: (0, import_yup21.array)().of((0, import_yup21.string)()).optional().label("Benefits"),
1544
+ qualifications: (0, import_yup21.array)().of((0, import_yup21.string)()).optional().label("Qualifications")
1193
1545
  }).nullable().optional().label("Job Highlights"),
1194
- canCreateAlert: (0, import_yup20.boolean)().optional().label("Can create alert"),
1195
- canDirectApply: (0, import_yup20.boolean)().optional().label("Can direct apply"),
1196
- hasApplied: (0, import_yup20.boolean)().optional().label("Has applied"),
1197
- isOwner: (0, import_yup20.boolean)().optional().label("Is owner"),
1198
- hasBookmarked: (0, import_yup20.boolean)().optional().label("Has bookmarked"),
1199
- hasReported: (0, import_yup20.boolean)().optional().label("Has reported"),
1200
- ratePerHour: (0, import_yup20.number)().optional().min(0).label("Rate per hour"),
1201
- isPromoted: (0, import_yup20.boolean)().optional().label("Is promoted"),
1202
- salaryRange: (0, import_yup20.object)({
1203
- currency: (0, import_yup20.string)().required().label("Currency").default("USD"),
1204
- min: (0, import_yup20.number)().nullable().optional().min(0).label("Minimum Salary"),
1205
- max: (0, import_yup20.number)().nullable().optional().min(0).label("Maximum Salary"),
1206
- compensationType: (0, import_yup20.string)().oneOf(SupportedCompensationTypes).nullable().optional().label("Compensation Type"),
1207
- isNegotiable: (0, import_yup20.boolean)().nullable().optional().default(true).label("Is Negotiable")
1546
+ canCreateAlert: (0, import_yup21.boolean)().optional().label("Can create alert"),
1547
+ canDirectApply: (0, import_yup21.boolean)().optional().label("Can direct apply"),
1548
+ hasApplied: (0, import_yup21.boolean)().optional().label("Has applied"),
1549
+ isOwner: (0, import_yup21.boolean)().optional().label("Is owner"),
1550
+ hasBookmarked: (0, import_yup21.boolean)().optional().label("Has bookmarked"),
1551
+ hasReported: (0, import_yup21.boolean)().optional().label("Has reported"),
1552
+ ratePerHour: (0, import_yup21.number)().optional().min(0).label("Rate per hour"),
1553
+ isPromoted: (0, import_yup21.boolean)().optional().label("Is promoted"),
1554
+ salaryRange: (0, import_yup21.object)({
1555
+ currency: (0, import_yup21.string)().required().label("Currency").default("USD"),
1556
+ min: (0, import_yup21.number)().nullable().optional().min(0).label("Minimum Salary"),
1557
+ max: (0, import_yup21.number)().nullable().optional().min(0).label("Maximum Salary"),
1558
+ compensationType: (0, import_yup21.string)().oneOf(SupportedCompensationTypes).nullable().optional().label("Compensation Type"),
1559
+ isNegotiable: (0, import_yup21.boolean)().nullable().optional().default(true).label("Is Negotiable")
1208
1560
  }).nullable().optional().label("Salary Range"),
1209
- reports: (0, import_yup20.array)().of(UserIdAndCreatedAtSchema).optional().label("Reports"),
1561
+ reports: (0, import_yup21.array)().of(UserIdAndCreatedAtSchema).optional().label("Reports"),
1210
1562
  // Taxonomy fields are constants (see job-taxonomy.constant.ts), stored as slugs and
1211
1563
  // translated for display. They are deliberately NOT `oneOf`-constrained here: a value
1212
1564
  // outside the taxonomy must be DROPPED, never a reason to reject the posting. Yup
@@ -1219,17 +1571,25 @@ var JobSchema = (0, import_yup20.object)({
1219
1571
  // time (values get renamed, retired, added), so stored jobs will always outlive the
1220
1572
  // enum — being lenient here is what lets the taxonomy evolve without a migration
1221
1573
  // gate on every write.
1222
- category: (0, import_yup20.string)().nullable().optional().label("Category"),
1223
- industry: (0, import_yup20.string)().nullable().optional().label("Industry"),
1224
- subCategories: (0, import_yup20.array)().of((0, import_yup20.string)().trim().required()).nullable().optional().label("Sub Categories"),
1225
- termsAccepted: (0, import_yup20.boolean)().oneOf([true], "Accept terms before proceeding").required("Accept terms before proceeding").label("Terms accepted"),
1226
- isFeatured: (0, import_yup20.boolean)().optional().default(false).label("Is featured"),
1574
+ category: (0, import_yup21.string)().nullable().optional().label("Category"),
1575
+ industry: (0, import_yup21.string)().nullable().optional().label("Industry"),
1576
+ subCategories: (0, import_yup21.array)().of((0, import_yup21.string)().trim().required()).nullable().optional().label("Sub Categories"),
1577
+ /**
1578
+ * The taxonomy state `skills[].concept` was resolved against (the service's
1579
+ * `taxonomyVersion`, bumped by every release and rollback); behind the current
1580
+ * version means the concepts may be stale. Written by jobs-service with every
1581
+ * skills write: `null` when that write could not be normalized (taxonomy down
1582
+ * or unconfigured), absent on jobs that predate normalization.
1583
+ */
1584
+ taxonomyVersion: (0, import_yup21.number)().integer().min(0).nullable().optional().label("Taxonomy Version"),
1585
+ termsAccepted: (0, import_yup21.boolean)().oneOf([true], "Accept terms before proceeding").required("Accept terms before proceeding").label("Terms accepted"),
1586
+ isFeatured: (0, import_yup21.boolean)().optional().default(false).label("Is featured"),
1227
1587
  /** Market country codes to feature this job in; empty = global. */
1228
- featuredMarkets: (0, import_yup20.array)().of((0, import_yup20.string)().required()).optional().default([]).label("Featured markets")
1588
+ featuredMarkets: (0, import_yup21.array)().of((0, import_yup21.string)().required()).optional().default([]).label("Featured markets")
1229
1589
  }).concat(DbDefaultSchema).noUnknown().label("Job");
1230
1590
 
1231
1591
  // src/job/job-filter.schema.ts
1232
- var import_yup21 = require("yup");
1592
+ var import_yup22 = require("yup");
1233
1593
 
1234
1594
  // src/user/user.constant.ts
1235
1595
  var UserRole = /* @__PURE__ */ ((UserRole2) => {
@@ -2417,7 +2777,7 @@ var TAXONOMY_LABELS = {
2417
2777
 
2418
2778
  // src/job/job-filter.schema.ts
2419
2779
  var GEO_GROUP = "Geo";
2420
- var JobFilterShape = (0, import_yup21.object)({
2780
+ var JobFilterShape = (0, import_yup22.object)({
2421
2781
  // ── Classification ──────────────────────────────────────────────────────────
2422
2782
  // Closed vocabularies, sent verbatim and matched exactly: a partial match
2423
2783
  // would make "remote" also match a hypothetical "remote_first".
@@ -2560,8 +2920,8 @@ var JobFilterShape = (0, import_yup21.object)({
2560
2920
  // ── Flags ───────────────────────────────────────────────────────────────────
2561
2921
  // No `.default(false)`: an unchecked box must mean "no constraint", not "only
2562
2922
  // the non-featured ones", so absent simply omits the clause.
2563
- isFeatured: (0, import_yup21.boolean)().optional().label("Featured Only"),
2564
- isPromoted: (0, import_yup21.boolean)().optional().label("Promoted Only"),
2923
+ isFeatured: (0, import_yup22.boolean)().optional().label("Featured Only"),
2924
+ isPromoted: (0, import_yup22.boolean)().optional().label("Promoted Only"),
2565
2925
  featuredMarkets: tokens("Featured Markets"),
2566
2926
  // ── Ranges ──────────────────────────────────────────────────────────────────
2567
2927
  /**
@@ -2572,10 +2932,10 @@ var JobFilterShape = (0, import_yup21.object)({
2572
2932
  // can be selected and a bound is meaningless across them. Here the currency is
2573
2933
  // a single sibling field, so the bound is plain and the currency simply
2574
2934
  // narrows which jobs are compared.
2575
- salary: (0, import_yup21.object)({
2576
- min: (0, import_yup21.number)().integer().min(0).optional().default(void 0).label("Salary \u2265"),
2577
- max: (0, import_yup21.number)().integer().min(0).optional().default(void 0).label("Salary \u2264"),
2578
- currency: (0, import_yup21.string)().oneOf(SupportedSalaryCurrencies).optional().label("Currency")
2935
+ salary: (0, import_yup22.object)({
2936
+ min: (0, import_yup22.number)().integer().min(0).optional().default(void 0).label("Salary \u2265"),
2937
+ max: (0, import_yup22.number)().integer().min(0).optional().default(void 0).label("Salary \u2264"),
2938
+ currency: (0, import_yup22.string)().oneOf(SupportedSalaryCurrencies).optional().label("Currency")
2579
2939
  }).optional().default(void 0).label("Salary"),
2580
2940
  /** Posted within this many days. Relative, so a saved filter does not rot. */
2581
2941
  postedWithinDays: days("Posted Within (days)"),
@@ -2584,7 +2944,7 @@ var JobFilterShape = (0, import_yup21.object)({
2584
2944
  * relative `postedWithinDays`. EPOCH MS, matching how the field is stored — a
2585
2945
  * coerced date string would not compare.
2586
2946
  */
2587
- createdAt: (0, import_yup21.object)({
2947
+ createdAt: (0, import_yup22.object)({
2588
2948
  min: epoch("Posted After"),
2589
2949
  max: epoch("Posted Before")
2590
2950
  }).optional().default(void 0).label("Posted Between")
@@ -2638,7 +2998,7 @@ function deriveIndustry(category, subCategories) {
2638
2998
  }
2639
2999
 
2640
3000
  // src/post/post.schema.ts
2641
- var import_yup22 = require("yup");
3001
+ var import_yup23 = require("yup");
2642
3002
 
2643
3003
  // src/post/post.constant.ts
2644
3004
  var PostStatus = /* @__PURE__ */ ((PostStatus2) => {
@@ -2650,41 +3010,41 @@ var PostStatus = /* @__PURE__ */ ((PostStatus2) => {
2650
3010
  var SupportedPostStatuses = Object.values(PostStatus);
2651
3011
 
2652
3012
  // src/post/post.schema.ts
2653
- var PostSchema = (0, import_yup22.object)({
3013
+ var PostSchema = (0, import_yup23.object)({
2654
3014
  /** Headline shown in listings and at the top of the post. */
2655
- title: (0, import_yup22.string)().required().min(5).max(200).label("Title"),
3015
+ title: (0, import_yup23.string)().required().min(5).max(200).label("Title"),
2656
3016
  /** User-friendly, unique URL identifier. */
2657
- slug: (0, import_yup22.string)().required().max(250).label("Slug"),
3017
+ slug: (0, import_yup23.string)().required().max(250).label("Slug"),
2658
3018
  /** Short summary used in listings and meta description fallback. */
2659
- excerpt: (0, import_yup22.string)().max(500).optional().label("Excerpt"),
3019
+ excerpt: (0, import_yup23.string)().max(500).optional().label("Excerpt"),
2660
3020
  /** Rendered HTML source of truth for the post body (sanitized on write/render).
2661
3021
  * Supports rich formatting markdown can't express (image size/alignment, etc). */
2662
- bodyHTML: (0, import_yup22.string)().required().min(1).label("Body (HTML)"),
3022
+ bodyHTML: (0, import_yup23.string)().required().min(1).label("Body (HTML)"),
2663
3023
  /** Optional Markdown mirror of the body (portable text / search fallback).
2664
3024
  * Kept for back-compat and plain-text derivation; bodyHTML is authoritative. */
2665
- bodyMarkdown: (0, import_yup22.string)().optional().label("Body (Markdown)"),
3025
+ bodyMarkdown: (0, import_yup23.string)().optional().label("Body (Markdown)"),
2666
3026
  /** Cover/hero image URL. Not `.url()`-validated: the value is either an
2667
3027
  * uploaded file URL served by this platform (which may be a bare host like
2668
3028
  * `http://localhost:3082/...` in dev, that yup's `.url()` wrongly rejects) or
2669
3029
  * an admin-pasted link. Kept as a plain string; empty ⇒ null. */
2670
- coverImage: (0, import_yup22.string)().nullable().optional().label("Cover image"),
3030
+ coverImage: (0, import_yup23.string)().nullable().optional().label("Cover image"),
2671
3031
  /** Author (user id). */
2672
- authorId: (0, import_yup22.string)().required().label("Author ID"),
3032
+ authorId: (0, import_yup23.string)().required().label("Author ID"),
2673
3033
  /** Free-form tags for filtering and related-posts. */
2674
- tags: (0, import_yup22.array)().of((0, import_yup22.string)().trim().required()).default([]).label("Tags"),
3034
+ tags: (0, import_yup23.array)().of((0, import_yup23.string)().trim().required()).default([]).label("Tags"),
2675
3035
  /** Estimated reading time in minutes (server-derived from body length). */
2676
- readingTimeMinutes: (0, import_yup22.number)().min(0).optional().label("Reading time (minutes)"),
3036
+ readingTimeMinutes: (0, import_yup23.number)().min(0).optional().label("Reading time (minutes)"),
2677
3037
  /** SEO overrides. */
2678
- seoTags: (0, import_yup22.object)({
2679
- description: (0, import_yup22.string)().optional().label("Description"),
2680
- keywords: (0, import_yup22.array)().of((0, import_yup22.string)().required()).optional().label("Keywords")
3038
+ seoTags: (0, import_yup23.object)({
3039
+ description: (0, import_yup23.string)().optional().label("Description"),
3040
+ keywords: (0, import_yup23.array)().of((0, import_yup23.string)().required()).optional().label("Keywords")
2681
3041
  }).nullable().optional().label("SEO Tags"),
2682
3042
  /** When the post was first published (epoch ms). Server-owned. */
2683
- publishedAt: (0, import_yup22.number)().nullable().optional().label("Published at"),
3043
+ publishedAt: (0, import_yup23.number)().nullable().optional().label("Published at"),
2684
3044
  /** Whether the post is featured in the blog listing. */
2685
- isFeatured: (0, import_yup22.boolean)().default(false).optional().label("Is featured"),
3045
+ isFeatured: (0, import_yup23.boolean)().default(false).optional().label("Is featured"),
2686
3046
  /** Publication state. */
2687
- status: (0, import_yup22.string)().oneOf(SupportedPostStatuses).default("draft" /* Draft */).required().label("Status")
3047
+ status: (0, import_yup23.string)().oneOf(SupportedPostStatuses).default("draft" /* Draft */).required().label("Status")
2688
3048
  }).concat(DbDefaultSchema).noUnknown().label("Post");
2689
3049
 
2690
3050
  // src/geo/geo.constant.ts
@@ -2734,11 +3094,11 @@ var GEO_COLLECTIONS = {
2734
3094
  var GEO_SEARCH_MAX = 64;
2735
3095
 
2736
3096
  // src/geo/country.schema.ts
2737
- var import_yup23 = require("yup");
2738
- var CountrySchema = (0, import_yup23.object)({
3097
+ var import_yup24 = require("yup");
3098
+ var CountrySchema = (0, import_yup24.object)({
2739
3099
  /** The upstream dr5hn id. Stable, and the key every upsert matches on. */
2740
- id: (0, import_yup23.number)().integer().required().label("ID"),
2741
- name: (0, import_yup23.string)().trim().required().label("Name"),
3100
+ id: (0, import_yup24.number)().integer().required().label("ID"),
3101
+ name: (0, import_yup24.string)().trim().required().label("Name"),
2742
3102
  /**
2743
3103
  * ISO 3166-1 alpha-2, UPPERCASE (`IN`, `SE`).
2744
3104
  *
@@ -2747,34 +3107,34 @@ var CountrySchema = (0, import_yup23.object)({
2747
3107
  * already clean uppercase alpha-2. A lowercase value here would compare
2748
3108
  * unequal to all of them and silently match nothing.
2749
3109
  */
2750
- countryCode: (0, import_yup23.string)().trim().uppercase().length(2).required().label("Country Code"),
2751
- iso3: (0, import_yup23.string)().trim().uppercase().length(3).optional().nullable().label("ISO3"),
2752
- numericCode: (0, import_yup23.string)().trim().optional().nullable().label("Numeric Code"),
2753
- phonecode: (0, import_yup23.string)().trim().optional().nullable().label("Phone Code"),
2754
- capital: (0, import_yup23.string)().trim().optional().nullable().label("Capital"),
2755
- currency: (0, import_yup23.string)().trim().optional().nullable().label("Currency"),
2756
- currencyName: (0, import_yup23.string)().trim().optional().nullable().label("Currency Name"),
2757
- currencySymbol: (0, import_yup23.string)().trim().optional().nullable().label("Currency Symbol"),
2758
- tld: (0, import_yup23.string)().trim().optional().nullable().label("TLD"),
3110
+ countryCode: (0, import_yup24.string)().trim().uppercase().length(2).required().label("Country Code"),
3111
+ iso3: (0, import_yup24.string)().trim().uppercase().length(3).optional().nullable().label("ISO3"),
3112
+ numericCode: (0, import_yup24.string)().trim().optional().nullable().label("Numeric Code"),
3113
+ phonecode: (0, import_yup24.string)().trim().optional().nullable().label("Phone Code"),
3114
+ capital: (0, import_yup24.string)().trim().optional().nullable().label("Capital"),
3115
+ currency: (0, import_yup24.string)().trim().optional().nullable().label("Currency"),
3116
+ currencyName: (0, import_yup24.string)().trim().optional().nullable().label("Currency Name"),
3117
+ currencySymbol: (0, import_yup24.string)().trim().optional().nullable().label("Currency Symbol"),
3118
+ tld: (0, import_yup24.string)().trim().optional().nullable().label("TLD"),
2759
3119
  /** The name in its own script, e.g. "भारत". Stored, but NOT searchable; see `searchByLocale`. */
2760
- native: (0, import_yup23.string)().trim().optional().nullable().label("Native Name"),
2761
- population: (0, import_yup23.number)().optional().nullable().label("Population"),
2762
- gdp: (0, import_yup23.number)().optional().nullable().label("GDP"),
2763
- region: (0, import_yup23.string)().trim().optional().nullable().label("Region"),
2764
- regionId: (0, import_yup23.number)().integer().optional().nullable().label("Region ID"),
2765
- subregion: (0, import_yup23.string)().trim().optional().nullable().label("Subregion"),
2766
- subregionId: (0, import_yup23.number)().integer().optional().nullable().label("Subregion ID"),
2767
- nationality: (0, import_yup23.string)().trim().optional().nullable().label("Nationality"),
2768
- areaSqKm: (0, import_yup23.number)().optional().nullable().label("Area (sq km)"),
2769
- postalCodeFormat: (0, import_yup23.string)().trim().optional().nullable().label("Postal Code Format"),
2770
- postalCodeRegex: (0, import_yup23.string)().optional().nullable().label("Postal Code Regex"),
2771
- timezones: (0, import_yup23.array)().of(
2772
- (0, import_yup23.object)({
2773
- zoneName: (0, import_yup23.string)().optional().nullable().label("Zone Name"),
2774
- gmtOffset: (0, import_yup23.number)().optional().nullable().label("GMT Offset"),
2775
- gmtOffsetName: (0, import_yup23.string)().optional().nullable().label("GMT Offset Name"),
2776
- abbreviation: (0, import_yup23.string)().optional().nullable().label("Abbreviation"),
2777
- tzName: (0, import_yup23.string)().optional().nullable().label("TZ Name")
3120
+ native: (0, import_yup24.string)().trim().optional().nullable().label("Native Name"),
3121
+ population: (0, import_yup24.number)().optional().nullable().label("Population"),
3122
+ gdp: (0, import_yup24.number)().optional().nullable().label("GDP"),
3123
+ region: (0, import_yup24.string)().trim().optional().nullable().label("Region"),
3124
+ regionId: (0, import_yup24.number)().integer().optional().nullable().label("Region ID"),
3125
+ subregion: (0, import_yup24.string)().trim().optional().nullable().label("Subregion"),
3126
+ subregionId: (0, import_yup24.number)().integer().optional().nullable().label("Subregion ID"),
3127
+ nationality: (0, import_yup24.string)().trim().optional().nullable().label("Nationality"),
3128
+ areaSqKm: (0, import_yup24.number)().optional().nullable().label("Area (sq km)"),
3129
+ postalCodeFormat: (0, import_yup24.string)().trim().optional().nullable().label("Postal Code Format"),
3130
+ postalCodeRegex: (0, import_yup24.string)().optional().nullable().label("Postal Code Regex"),
3131
+ timezones: (0, import_yup24.array)().of(
3132
+ (0, import_yup24.object)({
3133
+ zoneName: (0, import_yup24.string)().optional().nullable().label("Zone Name"),
3134
+ gmtOffset: (0, import_yup24.number)().optional().nullable().label("GMT Offset"),
3135
+ gmtOffsetName: (0, import_yup24.string)().optional().nullable().label("GMT Offset Name"),
3136
+ abbreviation: (0, import_yup24.string)().optional().nullable().label("Abbreviation"),
3137
+ tzName: (0, import_yup24.string)().optional().nullable().label("TZ Name")
2778
3138
  })
2779
3139
  ).optional().nullable().label("Timezones"),
2780
3140
  /**
@@ -2783,7 +3143,7 @@ var CountrySchema = (0, import_yup23.object)({
2783
3143
  * declaring it here would turn a new locale into a validation failure. The
2784
3144
  * VALUES are split per-locale into `searchByLocale`.
2785
3145
  */
2786
- translations: (0, import_yup23.object)().optional().nullable().label("Translations"),
3146
+ translations: (0, import_yup24.object)().optional().nullable().label("Translations"),
2787
3147
  /**
2788
3148
  * Coordinates as NUMBERS.
2789
3149
  *
@@ -2792,10 +3152,10 @@ var CountrySchema = (0, import_yup23.object)({
2792
3152
  * `"9" > "10"` is true, so a bounding-box filter would be quietly wrong rather
2793
3153
  * than failing loudly.
2794
3154
  */
2795
- latitude: (0, import_yup23.number)().optional().nullable().label("Latitude"),
2796
- longitude: (0, import_yup23.number)().optional().nullable().label("Longitude"),
2797
- emoji: (0, import_yup23.string)().optional().nullable().label("Emoji"),
2798
- emojiU: (0, import_yup23.string)().optional().nullable().label("Emoji Unicode"),
3155
+ latitude: (0, import_yup24.number)().optional().nullable().label("Latitude"),
3156
+ longitude: (0, import_yup24.number)().optional().nullable().label("Longitude"),
3157
+ emoji: (0, import_yup24.string)().optional().nullable().label("Emoji"),
3158
+ emojiU: (0, import_yup24.string)().optional().nullable().label("Emoji Unicode"),
2799
3159
  /**
2800
3160
  * The name field searched for the DEFAULT (English) locale: the canonical
2801
3161
  * `name`, and nothing else.
@@ -2805,7 +3165,7 @@ var CountrySchema = (0, import_yup23.object)({
2805
3165
  * field would make every locale's search implicitly a search of all 19
2806
3166
  * languages: a French UI searching "Inde" would also match German "Indien".
2807
3167
  */
2808
- searchDefault: (0, import_yup23.array)().of((0, import_yup23.string)().required()).optional().default([]).label("Search Default"),
3168
+ searchDefault: (0, import_yup24.array)().of((0, import_yup24.string)().required()).optional().default([]).label("Search Default"),
2809
3169
  /**
2810
3170
  * Per-locale name fields, keyed by lowercased BCP-47 tag (`de`, `pt-br`).
2811
3171
  * Each carries ONLY that locale's translation, which is what makes
@@ -2823,21 +3183,21 @@ var CountrySchema = (0, import_yup23.object)({
2823
3183
  * it, zh-CN, tr, ru, uk, pl, hi, ar. **`sv` is absent**, so a Swedish search
2824
3184
  * matches nothing. That is the accepted cost of strict scoping.
2825
3185
  */
2826
- searchByLocale: (0, import_yup23.object)().optional().nullable().label("Search By Locale"),
3186
+ searchByLocale: (0, import_yup24.object)().optional().nullable().label("Search By Locale"),
2827
3187
  /** Epoch ms of the run that last wrote this row. Drives the stale-row prune. */
2828
- syncedAt: (0, import_yup23.number)().optional().nullable().label("Synced At")
3188
+ syncedAt: (0, import_yup24.number)().optional().nullable().label("Synced At")
2829
3189
  }).label("Country");
2830
3190
 
2831
3191
  // src/geo/state.schema.ts
2832
- var import_yup24 = require("yup");
2833
- var StateSchema = (0, import_yup24.object)({
3192
+ var import_yup25 = require("yup");
3193
+ var StateSchema = (0, import_yup25.object)({
2834
3194
  /** The upstream dr5hn id. Stable, and the key every upsert matches on. */
2835
- id: (0, import_yup24.number)().integer().required().label("ID"),
2836
- name: (0, import_yup24.string)().trim().required().label("Name"),
2837
- countryId: (0, import_yup24.number)().integer().optional().nullable().label("Country ID"),
3195
+ id: (0, import_yup25.number)().integer().required().label("ID"),
3196
+ name: (0, import_yup25.string)().trim().required().label("Name"),
3197
+ countryId: (0, import_yup25.number)().integer().optional().nullable().label("Country ID"),
2838
3198
  /** ISO 3166-1 alpha-2 of the parent, UPPERCASE. See `CountrySchema.countryCode`. */
2839
- countryCode: (0, import_yup24.string)().trim().uppercase().length(2).required().label("Country Code"),
2840
- countryName: (0, import_yup24.string)().trim().optional().nullable().label("Country Name"),
3199
+ countryCode: (0, import_yup25.string)().trim().uppercase().length(2).required().label("Country Code"),
3200
+ countryName: (0, import_yup25.string)().trim().optional().nullable().label("Country Name"),
2841
3201
  /**
2842
3202
  * The BARE subdivision code, e.g. `KA` (upstream `iso2`).
2843
3203
  *
@@ -2849,22 +3209,22 @@ var StateSchema = (0, import_yup24.object)({
2849
3209
  * both are stored so the migration has its destination present before any
2850
3210
  * consumer moves.
2851
3211
  */
2852
- stateCode: (0, import_yup24.string)().trim().uppercase().optional().nullable().label("State Code"),
3212
+ stateCode: (0, import_yup25.string)().trim().uppercase().optional().nullable().label("State Code"),
2853
3213
  /** Fully-qualified ISO 3166-2, e.g. `IN-KA` (upstream `iso3166_2`). */
2854
- stateCodeIso: (0, import_yup24.string)().trim().uppercase().optional().nullable().label("ISO 3166-2 Code"),
2855
- fipsCode: (0, import_yup24.string)().trim().optional().nullable().label("FIPS Code"),
3214
+ stateCodeIso: (0, import_yup25.string)().trim().uppercase().optional().nullable().label("ISO 3166-2 Code"),
3215
+ fipsCode: (0, import_yup25.string)().trim().optional().nullable().label("FIPS Code"),
2856
3216
  /** Upstream's own word for the subdivision: "province", "state", "district", … */
2857
- type: (0, import_yup24.string)().trim().optional().nullable().label("Type"),
2858
- level: (0, import_yup24.number)().integer().optional().nullable().label("Level"),
2859
- parentId: (0, import_yup24.number)().integer().optional().nullable().label("Parent ID"),
3217
+ type: (0, import_yup25.string)().trim().optional().nullable().label("Type"),
3218
+ level: (0, import_yup25.number)().integer().optional().nullable().label("Level"),
3219
+ parentId: (0, import_yup25.number)().integer().optional().nullable().label("Parent ID"),
2860
3220
  /** The name in its own script. Stored, but NOT searchable; see `searchByLocale`. */
2861
- native: (0, import_yup24.string)().trim().optional().nullable().label("Native Name"),
3221
+ native: (0, import_yup25.string)().trim().optional().nullable().label("Native Name"),
2862
3222
  /** Numbers, not the strings upstream ships. See `CountrySchema.latitude`. */
2863
- latitude: (0, import_yup24.number)().optional().nullable().label("Latitude"),
2864
- longitude: (0, import_yup24.number)().optional().nullable().label("Longitude"),
2865
- timezone: (0, import_yup24.string)().trim().optional().nullable().label("Timezone"),
3223
+ latitude: (0, import_yup25.number)().optional().nullable().label("Latitude"),
3224
+ longitude: (0, import_yup25.number)().optional().nullable().label("Longitude"),
3225
+ timezone: (0, import_yup25.string)().trim().optional().nullable().label("Timezone"),
2866
3226
  /** Localized names keyed by locale. Unshaped; see `CountrySchema.translations`. */
2867
- translations: (0, import_yup24.object)().optional().nullable().label("Translations"),
3227
+ translations: (0, import_yup25.object)().optional().nullable().label("Translations"),
2868
3228
  /**
2869
3229
  * The name field searched for the DEFAULT (English) locale: the canonical
2870
3230
  * `name`, and nothing else.
@@ -2874,7 +3234,7 @@ var StateSchema = (0, import_yup24.object)({
2874
3234
  * field would make every locale's search implicitly a search of all 19
2875
3235
  * languages: a French UI searching "Inde" would also match German "Indien".
2876
3236
  */
2877
- searchDefault: (0, import_yup24.array)().of((0, import_yup24.string)().required()).optional().default([]).label("Search Default"),
3237
+ searchDefault: (0, import_yup25.array)().of((0, import_yup25.string)().required()).optional().default([]).label("Search Default"),
2878
3238
  /**
2879
3239
  * Per-locale name fields, keyed by lowercased BCP-47 tag (`de`, `pt-br`).
2880
3240
  * Each carries ONLY that locale's translation, which is what makes
@@ -2892,20 +3252,20 @@ var StateSchema = (0, import_yup24.object)({
2892
3252
  * it, zh-CN, tr, ru, uk, pl, hi, ar. **`sv` is absent**, so a Swedish search
2893
3253
  * matches nothing. That is the accepted cost of strict scoping.
2894
3254
  */
2895
- searchByLocale: (0, import_yup24.object)().optional().nullable().label("Search By Locale"),
3255
+ searchByLocale: (0, import_yup25.object)().optional().nullable().label("Search By Locale"),
2896
3256
  /** Epoch ms of the run that last wrote this row. Drives the stale-row prune. */
2897
- syncedAt: (0, import_yup24.number)().optional().nullable().label("Synced At")
3257
+ syncedAt: (0, import_yup25.number)().optional().nullable().label("Synced At")
2898
3258
  }).label("State");
2899
3259
 
2900
3260
  // src/geo/city.schema.ts
2901
- var import_yup25 = require("yup");
2902
- var CitySchema = (0, import_yup25.object)({
3261
+ var import_yup26 = require("yup");
3262
+ var CitySchema = (0, import_yup26.object)({
2903
3263
  /** The upstream dr5hn id. Stable, and the key every upsert matches on. */
2904
- id: (0, import_yup25.number)().integer().required().label("ID"),
2905
- name: (0, import_yup25.string)().trim().required().label("Name"),
2906
- stateId: (0, import_yup25.number)().integer().optional().nullable().label("State ID"),
3264
+ id: (0, import_yup26.number)().integer().required().label("ID"),
3265
+ name: (0, import_yup26.string)().trim().required().label("Name"),
3266
+ stateId: (0, import_yup26.number)().integer().optional().nullable().label("State ID"),
2907
3267
  /** The BARE subdivision code. Only meaningful paired with `countryCode`; see `StateSchema`. */
2908
- stateCode: (0, import_yup25.string)().trim().uppercase().optional().nullable().label("State Code"),
3268
+ stateCode: (0, import_yup26.string)().trim().uppercase().optional().nullable().label("State Code"),
2909
3269
  /**
2910
3270
  * Fully-qualified ISO 3166-2, e.g. `IN-KA`.
2911
3271
  *
@@ -2915,29 +3275,29 @@ var CitySchema = (0, import_yup25.object)({
2915
3275
  * migration can re-point the cities route without a 153k-row re-ingest.
2916
3276
  * Null when either half is missing.
2917
3277
  */
2918
- stateCodeIso: (0, import_yup25.string)().trim().uppercase().optional().nullable().label("ISO 3166-2 Code"),
2919
- stateName: (0, import_yup25.string)().trim().optional().nullable().label("State Name"),
2920
- countryId: (0, import_yup25.number)().integer().optional().nullable().label("Country ID"),
3278
+ stateCodeIso: (0, import_yup26.string)().trim().uppercase().optional().nullable().label("ISO 3166-2 Code"),
3279
+ stateName: (0, import_yup26.string)().trim().optional().nullable().label("State Name"),
3280
+ countryId: (0, import_yup26.number)().integer().optional().nullable().label("Country ID"),
2921
3281
  /** ISO 3166-1 alpha-2 of the parent, UPPERCASE. See `CountrySchema.countryCode`. */
2922
- countryCode: (0, import_yup25.string)().trim().uppercase().length(2).required().label("Country Code"),
2923
- countryName: (0, import_yup25.string)().trim().optional().nullable().label("Country Name"),
3282
+ countryCode: (0, import_yup26.string)().trim().uppercase().length(2).required().label("Country Code"),
3283
+ countryName: (0, import_yup26.string)().trim().optional().nullable().label("Country Name"),
2924
3284
  /** Numbers, not the strings upstream ships. See `CountrySchema.latitude`. */
2925
- latitude: (0, import_yup25.number)().optional().nullable().label("Latitude"),
2926
- longitude: (0, import_yup25.number)().optional().nullable().label("Longitude"),
3285
+ latitude: (0, import_yup26.number)().optional().nullable().label("Latitude"),
3286
+ longitude: (0, import_yup26.number)().optional().nullable().label("Longitude"),
2927
3287
  /** The name in its own script. Stored, but NOT searchable; see `searchByLocale`. */
2928
- native: (0, import_yup25.string)().trim().optional().nullable().label("Native Name"),
2929
- type: (0, import_yup25.string)().trim().optional().nullable().label("Type"),
2930
- level: (0, import_yup25.number)().integer().optional().nullable().label("Level"),
2931
- parentId: (0, import_yup25.number)().integer().optional().nullable().label("Parent ID"),
3288
+ native: (0, import_yup26.string)().trim().optional().nullable().label("Native Name"),
3289
+ type: (0, import_yup26.string)().trim().optional().nullable().label("Type"),
3290
+ level: (0, import_yup26.number)().integer().optional().nullable().label("Level"),
3291
+ parentId: (0, import_yup26.number)().integer().optional().nullable().label("Parent ID"),
2932
3292
  /**
2933
3293
  * Null on most rows. Worth keeping (it is a good relevance signal when
2934
3294
  * present) but never worth SORTING by alone, since a null-heavy sort buries
2935
3295
  * every city upstream has no figure for.
2936
3296
  */
2937
- population: (0, import_yup25.number)().optional().nullable().label("Population"),
2938
- timezone: (0, import_yup25.string)().trim().optional().nullable().label("Timezone"),
3297
+ population: (0, import_yup26.number)().optional().nullable().label("Population"),
3298
+ timezone: (0, import_yup26.string)().trim().optional().nullable().label("Timezone"),
2939
3299
  /** Localized names keyed by locale. Unshaped; see `CountrySchema.translations`. */
2940
- translations: (0, import_yup25.object)().optional().nullable().label("Translations"),
3300
+ translations: (0, import_yup26.object)().optional().nullable().label("Translations"),
2941
3301
  /**
2942
3302
  * The name field searched for the DEFAULT (English) locale: the canonical
2943
3303
  * `name`, and nothing else.
@@ -2947,7 +3307,7 @@ var CitySchema = (0, import_yup25.object)({
2947
3307
  * field would make every locale's search implicitly a search of all 19
2948
3308
  * languages: a French UI searching "Inde" would also match German "Indien".
2949
3309
  */
2950
- searchDefault: (0, import_yup25.array)().of((0, import_yup25.string)().required()).optional().default([]).label("Search Default"),
3310
+ searchDefault: (0, import_yup26.array)().of((0, import_yup26.string)().required()).optional().default([]).label("Search Default"),
2951
3311
  /**
2952
3312
  * Per-locale name fields, keyed by lowercased BCP-47 tag (`de`, `pt-br`).
2953
3313
  * Each carries ONLY that locale's translation, which is what makes
@@ -2965,14 +3325,14 @@ var CitySchema = (0, import_yup25.object)({
2965
3325
  * it, zh-CN, tr, ru, uk, pl, hi, ar. **`sv` is absent**, so a Swedish search
2966
3326
  * matches nothing. That is the accepted cost of strict scoping.
2967
3327
  */
2968
- searchByLocale: (0, import_yup25.object)().optional().nullable().label("Search By Locale"),
3328
+ searchByLocale: (0, import_yup26.object)().optional().nullable().label("Search By Locale"),
2969
3329
  /** Epoch ms of the run that last wrote this row. Drives the stale-row prune. */
2970
- syncedAt: (0, import_yup25.number)().optional().nullable().label("Synced At")
3330
+ syncedAt: (0, import_yup26.number)().optional().nullable().label("Synced At")
2971
3331
  }).label("City");
2972
3332
 
2973
3333
  // src/geo/country-filter.schema.ts
2974
- var import_yup26 = require("yup");
2975
- var CountryFilterShape = (0, import_yup26.object)({
3334
+ var import_yup27 = require("yup");
3335
+ var CountryFilterShape = (0, import_yup27.object)({
2976
3336
  /** ISO 3166-1 alpha-2 codes, e.g. `IN`. Uppercased, matching how they are stored. */
2977
3337
  countryCodes: codeTokens("Country Codes"),
2978
3338
  /** ISO 3166-1 alpha-3 codes, e.g. `IND`. */
@@ -2992,8 +3352,8 @@ var CountryFilterSchema = rejectUnknownKeys(
2992
3352
  );
2993
3353
 
2994
3354
  // src/geo/state-filter.schema.ts
2995
- var import_yup27 = require("yup");
2996
- var StateFilterShape = (0, import_yup27.object)({
3355
+ var import_yup28 = require("yup");
3356
+ var StateFilterShape = (0, import_yup28.object)({
2997
3357
  /** ISO 3166-1 alpha-2 of the parent country, e.g. `IN`. */
2998
3358
  countryCodes: codeTokens("Country Codes"),
2999
3359
  /**
@@ -3023,8 +3383,8 @@ var StateFilterSchema = rejectUnknownKeys(
3023
3383
  );
3024
3384
 
3025
3385
  // src/geo/city-filter.schema.ts
3026
- var import_yup28 = require("yup");
3027
- var CityFilterShape = (0, import_yup28.object)({
3386
+ var import_yup29 = require("yup");
3387
+ var CityFilterShape = (0, import_yup29.object)({
3028
3388
  /** ISO 3166-1 alpha-2 of the parent country, e.g. `IN`. */
3029
3389
  countryCodes: codeTokens("Country Codes"),
3030
3390
  /**
@@ -3041,28 +3401,28 @@ var CityFilterSchema = rejectUnknownKeys(
3041
3401
  );
3042
3402
 
3043
3403
  // src/geo/geo-sync.schema.ts
3044
- var import_yup29 = require("yup");
3045
- var GeoSyncCountsSchema = (0, import_yup29.object)({
3046
- read: (0, import_yup29.number)().integer().min(0).default(0).label("Read"),
3047
- upserted: (0, import_yup29.number)().integer().min(0).default(0).label("Upserted"),
3048
- modified: (0, import_yup29.number)().integer().min(0).default(0).label("Modified"),
3049
- matched: (0, import_yup29.number)().integer().min(0).default(0).label("Matched"),
3404
+ var import_yup30 = require("yup");
3405
+ var GeoSyncCountsSchema = (0, import_yup30.object)({
3406
+ read: (0, import_yup30.number)().integer().min(0).default(0).label("Read"),
3407
+ upserted: (0, import_yup30.number)().integer().min(0).default(0).label("Upserted"),
3408
+ modified: (0, import_yup30.number)().integer().min(0).default(0).label("Modified"),
3409
+ matched: (0, import_yup30.number)().integer().min(0).default(0).label("Matched"),
3050
3410
  /** Rows removed because upstream dropped them. See `prunedSkipped`. */
3051
- pruned: (0, import_yup29.number)().integer().min(0).default(0).label("Pruned"),
3411
+ pruned: (0, import_yup30.number)().integer().min(0).default(0).label("Pruned"),
3052
3412
  /**
3053
3413
  * True when the stale-row prune was REFUSED because it would have removed more
3054
3414
  * than its safety threshold of the collection. A partial fetch that looked
3055
3415
  * like a success must not be able to empty the table, so the run reports the
3056
3416
  * refusal rather than acting on it.
3057
3417
  */
3058
- prunedSkipped: (0, import_yup29.boolean)().default(false).label("Prune Skipped")
3418
+ prunedSkipped: (0, import_yup30.boolean)().default(false).label("Prune Skipped")
3059
3419
  }).label("Sync Counts");
3060
- var GeoSyncRunSchema = (0, import_yup29.object)({
3061
- jobId: (0, import_yup29.string)().required().label("Job ID"),
3062
- status: (0, import_yup29.mixed)().oneOf(SupportedGeoSyncStatuses).required().label("Status"),
3063
- trigger: (0, import_yup29.mixed)().oneOf(SupportedGeoSyncTriggers).optional().label("Trigger"),
3420
+ var GeoSyncRunSchema = (0, import_yup30.object)({
3421
+ jobId: (0, import_yup30.string)().required().label("Job ID"),
3422
+ status: (0, import_yup30.mixed)().oneOf(SupportedGeoSyncStatuses).required().label("Status"),
3423
+ trigger: (0, import_yup30.mixed)().oneOf(SupportedGeoSyncTriggers).optional().label("Trigger"),
3064
3424
  /** Whether the caller asked to run even though the release tag was unchanged. */
3065
- force: (0, import_yup29.boolean)().default(false).label("Force"),
3425
+ force: (0, import_yup30.boolean)().default(false).label("Force"),
3066
3426
  /**
3067
3427
  * The upstream release tag this run fetched, e.g. `v3.2-export.7`.
3068
3428
  *
@@ -3070,13 +3430,13 @@ var GeoSyncRunSchema = (0, import_yup29.object)({
3070
3430
  * cannot tell what version it is fetching is otherwise a failure, not a
3071
3431
  * refresh, so it is not allowed to proceed unattended.
3072
3432
  */
3073
- releaseTag: (0, import_yup29.string)().optional().nullable().label("Release Tag"),
3433
+ releaseTag: (0, import_yup30.string)().optional().nullable().label("Release Tag"),
3074
3434
  /** The tag of the last successful run, i.e. what this one is replacing. */
3075
- previousReleaseTag: (0, import_yup29.string)().optional().nullable().label("Previous Release Tag"),
3076
- startedAt: (0, import_yup29.number)().required().label("Started At"),
3077
- finishedAt: (0, import_yup29.number)().optional().nullable().label("Finished At"),
3078
- durationMs: (0, import_yup29.number)().optional().nullable().label("Duration (ms)"),
3079
- phase: (0, import_yup29.mixed)().oneOf(SupportedGeoSyncPhases).optional().nullable().label("Phase"),
3435
+ previousReleaseTag: (0, import_yup30.string)().optional().nullable().label("Previous Release Tag"),
3436
+ startedAt: (0, import_yup30.number)().required().label("Started At"),
3437
+ finishedAt: (0, import_yup30.number)().optional().nullable().label("Finished At"),
3438
+ durationMs: (0, import_yup30.number)().optional().nullable().label("Duration (ms)"),
3439
+ phase: (0, import_yup30.mixed)().oneOf(SupportedGeoSyncPhases).optional().nullable().label("Phase"),
3080
3440
  /**
3081
3441
  * Live progress for the dashboard bar.
3082
3442
  *
@@ -3084,25 +3444,25 @@ var GeoSyncRunSchema = (0, import_yup29.object)({
3084
3444
  * collection, or a hardcoded fallback on the very first run, since the true
3085
3445
  * total is not known until the stream ends. The UI should render it as one.
3086
3446
  */
3087
- progress: (0, import_yup29.object)({
3088
- collection: (0, import_yup29.string)().optional().nullable().label("Collection"),
3089
- processed: (0, import_yup29.number)().integer().min(0).default(0).label("Processed"),
3090
- total: (0, import_yup29.number)().integer().min(0).optional().nullable().label("Total")
3447
+ progress: (0, import_yup30.object)({
3448
+ collection: (0, import_yup30.string)().optional().nullable().label("Collection"),
3449
+ processed: (0, import_yup30.number)().integer().min(0).default(0).label("Processed"),
3450
+ total: (0, import_yup30.number)().integer().min(0).optional().nullable().label("Total")
3091
3451
  }).optional().nullable().label("Progress"),
3092
- counts: (0, import_yup29.object)({
3452
+ counts: (0, import_yup30.object)({
3093
3453
  countries: GeoSyncCountsSchema.optional().nullable().label("Countries"),
3094
3454
  states: GeoSyncCountsSchema.optional().nullable().label("States"),
3095
3455
  cities: GeoSyncCountsSchema.optional().nullable().label("Cities")
3096
3456
  }).optional().nullable().label("Counts"),
3097
3457
  /** Why a `skipped` run was skipped, in words the dashboard can show verbatim. */
3098
- reason: (0, import_yup29.string)().optional().nullable().label("Reason"),
3458
+ reason: (0, import_yup30.string)().optional().nullable().label("Reason"),
3099
3459
  /** The failure message on a `failed` run. Surfaced verbatim to the admin. */
3100
- error: (0, import_yup29.string)().optional().nullable().label("Error")
3460
+ error: (0, import_yup30.string)().optional().nullable().label("Error")
3101
3461
  }).label("Geo Sync Run");
3102
3462
 
3103
3463
  // src/language/language-filter.schema.ts
3104
- var import_yup30 = require("yup");
3105
- var LanguageFilterShape = (0, import_yup30.object)({
3464
+ var import_yup31 = require("yup");
3465
+ var LanguageFilterShape = (0, import_yup31.object)({
3106
3466
  /** Language names, matched exactly. Free-text matching is `?search=`. */
3107
3467
  names: tokens("Names")
3108
3468
  }).label("Language Filter");
@@ -3112,8 +3472,8 @@ var LanguageFilterSchema = rejectUnknownKeys(
3112
3472
  );
3113
3473
 
3114
3474
  // src/page/page-filter.schema.ts
3115
- var import_yup31 = require("yup");
3116
- var PageFilterShape = (0, import_yup31.object)({
3475
+ var import_yup32 = require("yup");
3476
+ var PageFilterShape = (0, import_yup32.object)({
3117
3477
  /**
3118
3478
  * `type`, NOT `pageType`.
3119
3479
  *
@@ -3133,7 +3493,7 @@ var PageFilterShape = (0, import_yup31.object)({
3133
3493
  // No `.default(false)`: absent means no constraint. Note `isFeatured` is
3134
3494
  // present on only 55 documents, so combining it with `status: ['published']`
3135
3495
  // leaves 11 pages — a legitimate filter that returns almost nothing.
3136
- isFeatured: (0, import_yup31.boolean)().optional().label("Featured Only"),
3496
+ isFeatured: (0, import_yup32.boolean)().optional().label("Featured Only"),
3137
3497
  /** Lowercase (`in`, `se`, `ww`), like groups. See `GroupFilterSchema`. */
3138
3498
  featuredMarkets: tokens("Featured Markets"),
3139
3499
  /** Page SLUGS. A company is matched by slug: a display name breaks on rename. */
@@ -3146,7 +3506,7 @@ var PageFilterSchema = rejectUnknownKeys(
3146
3506
  );
3147
3507
 
3148
3508
  // src/marketing/marketing.schema.ts
3149
- var import_yup33 = require("yup");
3509
+ var import_yup34 = require("yup");
3150
3510
 
3151
3511
  // src/marketing/marketing.constant.ts
3152
3512
  var CampaignStatus = /* @__PURE__ */ ((CampaignStatus2) => {
@@ -3223,8 +3583,8 @@ var SupportedOccurrenceStatuses = Object.values(OccurrenceStatus);
3223
3583
  var CAMPAIGN_MAX_RECIPIENTS_CEILING = 5e3;
3224
3584
 
3225
3585
  // src/user/user-filter.schema.ts
3226
- var import_yup32 = require("yup");
3227
- var UserFilterBranchShape = (0, import_yup32.object)({
3586
+ var import_yup33 = require("yup");
3587
+ var UserFilterBranchShape = (0, import_yup33.object)({
3228
3588
  /**
3229
3589
  * Exact email addresses. An IDENTITY lookup, not a search.
3230
3590
  *
@@ -3244,7 +3604,7 @@ var UserFilterBranchShape = (0, import_yup32.object)({
3244
3604
  * the input keeps the match an indexed equality instead of a regex that would
3245
3605
  * put the scan straight back.
3246
3606
  */
3247
- emails: (0, import_yup32.array)().of((0, import_yup32.string)().required()).optional().default([]).label("Email Addresses"),
3607
+ emails: (0, import_yup33.array)().of((0, import_yup33.string)().required()).optional().default([]).label("Email Addresses"),
3248
3608
  /**
3249
3609
  * Better Auth account ids. An IDENTITY lookup like `emails`, and for the same
3250
3610
  * reason: the caller already knows the exact value.
@@ -3263,9 +3623,9 @@ var UserFilterBranchShape = (0, import_yup32.object)({
3263
3623
  * The admin filter panel builds its controls explicitly rather than from this
3264
3624
  * shape, so declaring it here does not put an account-id box in the UI.
3265
3625
  */
3266
- authAccountIds: (0, import_yup32.array)().of((0, import_yup32.string)().required()).optional().default([]).label("Auth Account IDs"),
3626
+ authAccountIds: (0, import_yup33.array)().of((0, import_yup33.string)().required()).optional().default([]).label("Auth Account IDs"),
3267
3627
  // ── Enum facets (closed vocabularies, chip pickers) ─────────────────────────
3268
- countries: (0, import_yup32.array)().of((0, import_yup32.string)().required()).optional().default([]).label("Countries"),
3628
+ countries: (0, import_yup33.array)().of((0, import_yup33.string)().required()).optional().default([]).label("Countries"),
3269
3629
  roles: enumFacet(SupportedUserRoles, "Roles"),
3270
3630
  // The user's own current seniority (UserSchema.experienceLevel).
3271
3631
  experienceLevels: enumFacet(SupportedExperienceLevels, "Experience Levels"),
@@ -3398,12 +3758,12 @@ var UserFilterBranchShape = (0, import_yup32.object)({
3398
3758
  // it, so the dependency is stated rather than assumed.
3399
3759
  minSalaryFloor: currencyBound("Min Salary \u2265", "salaryCurrencies"),
3400
3760
  minSalaryCeil: currencyBound("Min Salary \u2264", "salaryCurrencies"),
3401
- openToWorkOnly: (0, import_yup32.boolean)().optional().default(false).label("Open to Work Only"),
3402
- remoteExperienceOnly: (0, import_yup32.boolean)().optional().default(false).label("Has Remote Experience"),
3403
- hasResumeOnly: (0, import_yup32.boolean)().optional().default(false).label("Has Resume"),
3404
- onboardingCompletedOnly: (0, import_yup32.boolean)().optional().default(false).label("Onboarding Completed"),
3405
- emailVerifiedOnly: (0, import_yup32.boolean)().optional().default(false).label("Email Verified Only"),
3406
- mobileVerifiedOnly: (0, import_yup32.boolean)().optional().default(false).label("Mobile Verified Only"),
3761
+ openToWorkOnly: (0, import_yup33.boolean)().optional().default(false).label("Open to Work Only"),
3762
+ remoteExperienceOnly: (0, import_yup33.boolean)().optional().default(false).label("Has Remote Experience"),
3763
+ hasResumeOnly: (0, import_yup33.boolean)().optional().default(false).label("Has Resume"),
3764
+ onboardingCompletedOnly: (0, import_yup33.boolean)().optional().default(false).label("Onboarding Completed"),
3765
+ emailVerifiedOnly: (0, import_yup33.boolean)().optional().default(false).label("Email Verified Only"),
3766
+ mobileVerifiedOnly: (0, import_yup33.boolean)().optional().default(false).label("Mobile Verified Only"),
3407
3767
  // ── Lifecycle windows ───────────────────────────────────────────────────────
3408
3768
  // Relative, because a saved list is resolved fresh on every send: a stored
3409
3769
  // absolute epoch would freeze at authoring time and the audience would decay
@@ -3431,10 +3791,10 @@ var UserFilterBranchShape = (0, import_yup32.object)({
3431
3791
  // Radius search around a point, against location.geo. All three required
3432
3792
  // together; validated as a group rather than field-by-field so a half-filled
3433
3793
  // radius cannot silently match everyone.
3434
- withinRadius: (0, import_yup32.object)({
3435
- lat: (0, import_yup32.number)().min(-90).max(90).required().label("Latitude"),
3436
- lng: (0, import_yup32.number)().min(-180).max(180).required().label("Longitude"),
3437
- km: (0, import_yup32.number)().moreThan(0).max(2e4).required().label("Radius (km)")
3794
+ withinRadius: (0, import_yup33.object)({
3795
+ lat: (0, import_yup33.number)().min(-90).max(90).required().label("Latitude"),
3796
+ lng: (0, import_yup33.number)().min(-180).max(180).required().label("Longitude"),
3797
+ km: (0, import_yup33.number)().moreThan(0).max(2e4).required().label("Radius (km)")
3438
3798
  }).optional().default(void 0).label("Within Radius"),
3439
3799
  // ── Signup context (immutable market / locale) ──────────────────────────────
3440
3800
  signupCountries: tokens("Signup Countries"),
@@ -3457,7 +3817,7 @@ var UserFilterBranchShape = (0, import_yup32.object)({
3457
3817
  // materialise the second case on every list that never chose it.
3458
3818
  //
3459
3819
  // @todo CONSENT-FILTER-ENFORCEMENT — nothing enforces consent anywhere now.
3460
- marketingSubscribed: (0, import_yup32.boolean)().optional().label("Marketing Subscribed"),
3820
+ marketingSubscribed: (0, import_yup33.boolean)().optional().label("Marketing Subscribed"),
3461
3821
  jobAlertFrequencies: enumFacet(
3462
3822
  SupportedJobAlertFrequencies,
3463
3823
  "Job Alert Frequency"
@@ -3478,7 +3838,7 @@ var UserFilterBranchShape = (0, import_yup32.object)({
3478
3838
  ),
3479
3839
  // ── Tenure / recency ────────────────────────────────────────────────────────
3480
3840
  // Against workExperiences[].duration and educations[].duration ("YYYY-MM").
3481
- currentlyEmployed: (0, import_yup32.boolean)().optional().default(false).label("Currently Employed"),
3841
+ currentlyEmployed: (0, import_yup33.boolean)().optional().default(false).label("Currently Employed"),
3482
3842
  graduatedWithinMonths: months("Graduated Within (months)"),
3483
3843
  skillUsedWithinMonths: months("Skill Used Within (months)"),
3484
3844
  // The ABSOLUTE bounds the three relative windows above resolve into
@@ -3491,13 +3851,13 @@ var UserFilterBranchShape = (0, import_yup32.object)({
3491
3851
  // meaning that forever, while "within 18 months" keeps meaning what the admin
3492
3852
  // intended. They are declared because the vocabulary must be complete, not
3493
3853
  // because a list should store one.
3494
- graduatedAfter: (0, import_yup32.string)().trim().optional().label("Graduated After"),
3495
- skillUsedAfter: (0, import_yup32.string)().trim().optional().label("Skill Used After"),
3854
+ graduatedAfter: (0, import_yup33.string)().trim().optional().label("Graduated After"),
3855
+ skillUsedAfter: (0, import_yup33.string)().trim().optional().label("Skill Used After"),
3496
3856
  // ── Profile richness ────────────────────────────────────────────────────────
3497
- hasImage: (0, import_yup32.boolean)().optional().default(false).label("Has Photo"),
3498
- hasHeadline: (0, import_yup32.boolean)().optional().default(false).label("Has Headline"),
3499
- hasAboutMe: (0, import_yup32.boolean)().optional().default(false).label("Has About Me"),
3500
- hasMobile: (0, import_yup32.boolean)().optional().default(false).label("Has Mobile Number"),
3857
+ hasImage: (0, import_yup33.boolean)().optional().default(false).label("Has Photo"),
3858
+ hasHeadline: (0, import_yup33.boolean)().optional().default(false).label("Has Headline"),
3859
+ hasAboutMe: (0, import_yup33.boolean)().optional().default(false).label("Has About Me"),
3860
+ hasMobile: (0, import_yup33.boolean)().optional().default(false).label("Has Mobile Number"),
3501
3861
  // Segment by WHICH profile section is missing (profileCompleteness.missing[]),
3502
3862
  // rather than only by the overall percentage.
3503
3863
  missingSections: tokens("Missing Profile Sections"),
@@ -3509,8 +3869,8 @@ var UserFilterBranchShape = (0, import_yup32.object)({
3509
3869
  // data lives in other services' collections. Tri-state: `false` is a real
3510
3870
  // filter ("has NO job alert"), which is the audience an alert nudge wants, so
3511
3871
  // these are `undefined` when unset rather than defaulting to false.
3512
- hasJobAlert: (0, import_yup32.boolean)().optional().default(void 0).label("Has a Job Alert"),
3513
- hasApplied: (0, import_yup32.boolean)().optional().default(void 0).label("Has Applied"),
3872
+ hasJobAlert: (0, import_yup33.boolean)().optional().default(void 0).label("Has a Job Alert"),
3873
+ hasApplied: (0, import_yup33.boolean)().optional().default(void 0).label("Has Applied"),
3514
3874
  appliedWithinDays: days("Applied Within (days)")
3515
3875
  // `search` is NOT here. It is free text over name/email, not a facet: it names
3516
3876
  // no field on a user document, user-service treats it as a RESERVED PARAM
@@ -3523,25 +3883,25 @@ var BRANCH_KEYS = Object.keys(UserFilterBranchShape.fields);
3523
3883
  var UserFilterBranch = rejectUnknownKeys(UserFilterBranchShape, () => BRANCH_KEYS);
3524
3884
  var UserFilterSchema = rejectUnknownKeys(
3525
3885
  UserFilterBranchShape.shape({
3526
- or: (0, import_yup32.array)().of(UserFilterBranch).optional().default([]).label("Or Any Of")
3886
+ or: (0, import_yup33.array)().of(UserFilterBranch).optional().default([]).label("Or Any Of")
3527
3887
  }).label("User Filter"),
3528
3888
  () => [...BRANCH_KEYS, "or"]
3529
3889
  );
3530
3890
 
3531
3891
  // src/marketing/marketing.schema.ts
3532
3892
  var MailingListFilterSchema = UserFilterSchema;
3533
- var MailingListSchema = (0, import_yup33.object)({
3534
- name: (0, import_yup33.string)().required().min(1).max(120).label("Name"),
3535
- description: (0, import_yup33.string)().optional().max(500).label("Description"),
3893
+ var MailingListSchema = (0, import_yup34.object)({
3894
+ name: (0, import_yup34.string)().required().min(1).max(120).label("Name"),
3895
+ description: (0, import_yup34.string)().optional().max(500).label("Description"),
3536
3896
  filter: MailingListFilterSchema.default(
3537
3897
  () => MailingListFilterSchema.getDefault()
3538
3898
  )
3539
3899
  }).label("Mailing List");
3540
- var CampaignTriggerSchema = (0, import_yup33.object)({
3541
- kind: (0, import_yup33.string)().oneOf(SupportedCampaignTriggerKinds).required().default("manual" /* Manual */).label("Trigger"),
3900
+ var CampaignTriggerSchema = (0, import_yup34.object)({
3901
+ kind: (0, import_yup34.string)().oneOf(SupportedCampaignTriggerKinds).required().default("manual" /* Manual */).label("Trigger"),
3542
3902
  // IANA zone, e.g. "Asia/Kolkata". Local wall-clock times are interpreted in it,
3543
3903
  // so occurrences land at the intended hour across DST rather than drifting.
3544
- timezone: (0, import_yup33.string)().optional().default("UTC").label("Timezone").when("kind", {
3904
+ timezone: (0, import_yup34.string)().optional().default("UTC").label("Timezone").when("kind", {
3545
3905
  is: (k) => k !== "manual" /* Manual */,
3546
3906
  then: (s) => s.required()
3547
3907
  }),
@@ -3555,26 +3915,26 @@ var CampaignTriggerSchema = (0, import_yup33.object)({
3555
3915
  is: "schedule" /* Schedule */,
3556
3916
  then: (s) => s.required()
3557
3917
  }),
3558
- interval: (0, import_yup33.object)({
3559
- every: (0, import_yup33.number)().integer().min(1).max(365).required().label("Every"),
3560
- unit: (0, import_yup33.string)().oneOf(SupportedCampaignIntervalUnits).required().label("Unit")
3918
+ interval: (0, import_yup34.object)({
3919
+ every: (0, import_yup34.number)().integer().min(1).max(365).required().label("Every"),
3920
+ unit: (0, import_yup34.string)().oneOf(SupportedCampaignIntervalUnits).required().label("Unit")
3561
3921
  }).optional().default(void 0).label("Repeats").when("kind", {
3562
3922
  is: "schedule" /* Schedule */,
3563
3923
  then: (s) => s.required()
3564
3924
  }),
3565
3925
  /** Local wall-clock send time, "HH:mm" in `timezone`. */
3566
- timeOfDay: (0, import_yup33.string)().matches(/^([01]\d|2[0-3]):[0-5]\d$/, "Use HH:mm").optional().default("09:00").label("Time of Day"),
3926
+ timeOfDay: (0, import_yup34.string)().matches(/^([01]\d|2[0-3]):[0-5]\d$/, "Use HH:mm").optional().default("09:00").label("Time of Day"),
3567
3927
  /** For weekly intervals: ISO weekdays, 1 = Monday .. 7 = Sunday. */
3568
- byWeekday: (0, import_yup33.array)().of((0, import_yup33.number)().integer().min(1).max(7).required()).optional().default([]).label("On Days"),
3928
+ byWeekday: (0, import_yup34.array)().of((0, import_yup34.number)().integer().min(1).max(7).required()).optional().default([]).label("On Days"),
3569
3929
  /**
3570
3930
  * For monthly intervals: 1-31, or -1 meaning "last day of the month". A month
3571
3931
  * shorter than the chosen day is clamped to its last day, so 31 in February
3572
3932
  * fires on the 28th/29th rather than being skipped.
3573
3933
  */
3574
- byMonthday: (0, import_yup33.number)().integer().min(-1).max(31).notOneOf([0]).optional().default(void 0).label("Day of Month"),
3575
- end: (0, import_yup33.object)({
3576
- kind: (0, import_yup33.string)().oneOf(SupportedCampaignEndKinds).required().label("Ends"),
3577
- occurrences: (0, import_yup33.number)().integer().min(1).max(1e4).optional().default(void 0).label("After N Sends").when("kind", {
3934
+ byMonthday: (0, import_yup34.number)().integer().min(-1).max(31).notOneOf([0]).optional().default(void 0).label("Day of Month"),
3935
+ end: (0, import_yup34.object)({
3936
+ kind: (0, import_yup34.string)().oneOf(SupportedCampaignEndKinds).required().label("Ends"),
3937
+ occurrences: (0, import_yup34.number)().integer().min(1).max(1e4).optional().default(void 0).label("After N Sends").when("kind", {
3578
3938
  is: "afterOccurrences" /* AfterOccurrences */,
3579
3939
  then: (s) => s.required()
3580
3940
  }),
@@ -3584,8 +3944,8 @@ var CampaignTriggerSchema = (0, import_yup33.object)({
3584
3944
  })
3585
3945
  }).optional().default(() => ({ kind: "never" /* Never */ })).label("End Condition")
3586
3946
  }).label("Trigger");
3587
- var CampaignDataSourceSchema = (0, import_yup33.object)({
3588
- id: (0, import_yup33.string)().required().label("Data Source"),
3947
+ var CampaignDataSourceSchema = (0, import_yup34.object)({
3948
+ id: (0, import_yup34.string)().required().label("Data Source"),
3589
3949
  /**
3590
3950
  * Derive this source's filter per recipient from their profile, instead of
3591
3951
  * using the admin-entered `params` for everyone.
@@ -3600,7 +3960,7 @@ var CampaignDataSourceSchema = (0, import_yup33.object)({
3600
3960
  * un-personalised. Whether a source can honour it at all is declared by the
3601
3961
  * source itself (`supportsPersonalisation`), which this package cannot see.
3602
3962
  */
3603
- personalised: (0, import_yup33.boolean)().optional().label("Personalised"),
3963
+ personalised: (0, import_yup34.boolean)().optional().label("Personalised"),
3604
3964
  /**
3605
3965
  * A free-form bag whose keys are defined by the chosen source's OWN
3606
3966
  * `paramsSchema`, which this package cannot see. Same `lazy` treatment as
@@ -3613,22 +3973,22 @@ var CampaignDataSourceSchema = (0, import_yup33.object)({
3613
3973
  * them against the real source schema in `checkCampaignConfig`, which is the
3614
3974
  * only place that knows what a given source accepts.
3615
3975
  */
3616
- params: (0, import_yup33.lazy)((value) => {
3976
+ params: (0, import_yup34.lazy)((value) => {
3617
3977
  const keys = value && typeof value === "object" ? Object.keys(value) : [];
3618
- return (0, import_yup33.object)(
3619
- Object.fromEntries(keys.map((k) => [k, (0, import_yup33.mixed)().optional()]))
3978
+ return (0, import_yup34.object)(
3979
+ Object.fromEntries(keys.map((k) => [k, (0, import_yup34.mixed)().optional()]))
3620
3980
  ).optional().default(() => ({})).label("Parameters");
3621
3981
  })
3622
3982
  }).label("Data Source");
3623
- var TemplateVariableBindingSchema = (0, import_yup33.object)({
3624
- kind: (0, import_yup33.string)().oneOf(SupportedTemplateVariableBindingKinds).required().label("Source"),
3625
- value: (0, import_yup33.string)().required().min(1).max(2e3).label("Value")
3983
+ var TemplateVariableBindingSchema = (0, import_yup34.object)({
3984
+ kind: (0, import_yup34.string)().oneOf(SupportedTemplateVariableBindingKinds).required().label("Source"),
3985
+ value: (0, import_yup34.string)().required().min(1).max(2e3).label("Value")
3626
3986
  }).label("Template Variable Binding");
3627
- var CampaignSchema = (0, import_yup33.object)({
3628
- name: (0, import_yup33.string)().required().min(1).max(200).label("Campaign Name"),
3987
+ var CampaignSchema = (0, import_yup34.object)({
3988
+ name: (0, import_yup34.string)().required().min(1).max(200).label("Campaign Name"),
3629
3989
  // The template renders its own subject; an override is only needed for the
3630
3990
  // inline-HTML path, hence not required when a template is chosen.
3631
- subject: (0, import_yup33.string)().optional().max(255).label("Subject").when("templateKey", {
3991
+ subject: (0, import_yup34.string)().optional().max(255).label("Subject").when("templateKey", {
3632
3992
  is: (v) => !v,
3633
3993
  then: (s) => s.required().min(1)
3634
3994
  }),
@@ -3639,12 +3999,12 @@ var CampaignSchema = (0, import_yup33.object)({
3639
3999
  * one with a compliance-checked footer and a render test,
3640
4000
  * and hand-pasted HTML that repeats forever is a mistake that repeats forever.
3641
4001
  */
3642
- html: (0, import_yup33.string)().optional().label("HTML Body").when("templateKey", {
4002
+ html: (0, import_yup34.string)().optional().label("HTML Body").when("templateKey", {
3643
4003
  is: (v) => !v,
3644
4004
  then: (s) => s.required().min(1)
3645
4005
  }),
3646
- text: (0, import_yup33.string)().optional().label("Plain-text Body"),
3647
- listId: (0, import_yup33.string)().required().label("Mailing List"),
4006
+ text: (0, import_yup34.string)().optional().label("Plain-text Body"),
4007
+ listId: (0, import_yup34.string)().required().label("Mailing List"),
3648
4008
  /**
3649
4009
  * Where the campaign is in its life. See `CampaignStatus` for the transitions.
3650
4010
  *
@@ -3653,9 +4013,9 @@ var CampaignSchema = (0, import_yup33.object)({
3653
4013
  * value the enum never contained (`scheduled`) reached production rows and
3654
4014
  * rendered in the admin list as though it were real.
3655
4015
  */
3656
- status: (0, import_yup33.string)().oneOf(SupportedCampaignStatuses).optional().default("draft" /* Draft */).label("Status"),
4016
+ status: (0, import_yup34.string)().oneOf(SupportedCampaignStatuses).optional().default("draft" /* Draft */).label("Status"),
3657
4017
  /** Key from email-service's template registry. Mutually exclusive with `html`. */
3658
- templateKey: (0, import_yup33.string)().optional().label("Email Template"),
4018
+ templateKey: (0, import_yup34.string)().optional().label("Email Template"),
3659
4019
  /**
3660
4020
  * One entry per variable the chosen template declares. Validated at save time
3661
4021
  * against the template's own `variables`, so a missing required binding is a
@@ -3670,9 +4030,9 @@ var CampaignSchema = (0, import_yup33.object)({
3670
4030
  * whose required variables all appear unbound. Shaping each entry also means a
3671
4031
  * malformed binding is rejected here rather than at send time.
3672
4032
  */
3673
- bindings: (0, import_yup33.lazy)((value) => {
4033
+ bindings: (0, import_yup34.lazy)((value) => {
3674
4034
  const keys = value && typeof value === "object" ? Object.keys(value) : [];
3675
- return (0, import_yup33.object)(
4035
+ return (0, import_yup34.object)(
3676
4036
  Object.fromEntries(keys.map((k) => [k, TemplateVariableBindingSchema.required()]))
3677
4037
  ).optional().default(() => ({})).label("Template Data");
3678
4038
  }),
@@ -3684,7 +4044,7 @@ var CampaignSchema = (0, import_yup33.object)({
3684
4044
  * `groups.groupCount`), so two sources providing the same name cannot
3685
4045
  * overwrite each other and a template says which one it means.
3686
4046
  */
3687
- dataSources: (0, import_yup33.array)().of(CampaignDataSourceSchema).optional().default(void 0).label("Data Sources"),
4047
+ dataSources: (0, import_yup34.array)().of(CampaignDataSourceSchema).optional().default(void 0).label("Data Sources"),
3688
4048
  trigger: CampaignTriggerSchema.default(
3689
4049
  () => CampaignTriggerSchema.getDefault()
3690
4050
  ),
@@ -3699,7 +4059,7 @@ var CampaignSchema = (0, import_yup33.object)({
3699
4059
  * representable, and the admin list showed exactly that. Activation is a
3700
4060
  * state transition, so it belongs in the state.
3701
4061
  */
3702
- dryRun: (0, import_yup33.boolean)().optional().default(true).label("Dry Run"),
4062
+ dryRun: (0, import_yup34.boolean)().optional().default(true).label("Dry Run"),
3703
4063
  /**
3704
4064
  * Re-eligibility: when someone this campaign already mailed may get it again.
3705
4065
  *
@@ -3707,23 +4067,23 @@ var CampaignSchema = (0, import_yup33.object)({
3707
4067
  * person. `N`: again only after N days. Opt-in, because most recurring
3708
4068
  * campaigns exist to mail the same people repeatedly.
3709
4069
  */
3710
- resendAfterDays: (0, import_yup33.number)().integer().min(0).max(365).nullable().optional().default(void 0).label("Resend After (days)"),
3711
- maxRecipients: (0, import_yup33.number)().integer().min(1).max(CAMPAIGN_MAX_RECIPIENTS_CEILING).optional().default(void 0).label("Max Recipients"),
3712
- utm: (0, import_yup33.object)({
3713
- campaign: (0, import_yup33.string)().optional().max(120).label("utm_campaign"),
3714
- content: (0, import_yup33.string)().optional().max(120).label("utm_content")
4070
+ resendAfterDays: (0, import_yup34.number)().integer().min(0).max(365).nullable().optional().default(void 0).label("Resend After (days)"),
4071
+ maxRecipients: (0, import_yup34.number)().integer().min(1).max(CAMPAIGN_MAX_RECIPIENTS_CEILING).optional().default(void 0).label("Max Recipients"),
4072
+ utm: (0, import_yup34.object)({
4073
+ campaign: (0, import_yup34.string)().optional().max(120).label("utm_campaign"),
4074
+ content: (0, import_yup34.string)().optional().max(120).label("utm_content")
3715
4075
  }).optional().default(void 0).label("Link Tagging")
3716
4076
  }).label("Campaign");
3717
4077
 
3718
4078
  // src/pagination/pagination.schema.ts
3719
- var import_yup34 = require("yup");
3720
- var PaginationSchema = (0, import_yup34.object)().shape({
3721
- page: (0, import_yup34.number)().required().min(1).label("Page").default(1),
3722
- limit: (0, import_yup34.number)().required().min(1).max(100).label("Limit").default(10),
3723
- sortBy: (0, import_yup34.string)().notOneOf([""]).optional().label("SortBy"),
3724
- nextPage: (0, import_yup34.string)().optional().label("Next Page Token"),
3725
- prevPage: (0, import_yup34.string)().optional().label("Previous Page Token"),
3726
- sortDirection: (0, import_yup34.string)().oneOf(["asc", "desc"]).optional().label("Sort Direction")
4079
+ var import_yup35 = require("yup");
4080
+ var PaginationSchema = (0, import_yup35.object)().shape({
4081
+ page: (0, import_yup35.number)().required().min(1).label("Page").default(1),
4082
+ limit: (0, import_yup35.number)().required().min(1).max(100).label("Limit").default(10),
4083
+ sortBy: (0, import_yup35.string)().notOneOf([""]).optional().label("SortBy"),
4084
+ nextPage: (0, import_yup35.string)().optional().label("Next Page Token"),
4085
+ prevPage: (0, import_yup35.string)().optional().label("Previous Page Token"),
4086
+ sortDirection: (0, import_yup35.string)().oneOf(["asc", "desc"]).optional().label("Sort Direction")
3727
4087
  });
3728
4088
 
3729
4089
  // src/pagination/pagination.constant.ts
@@ -3739,7 +4099,7 @@ var DefaultPaginationOptions = {
3739
4099
  };
3740
4100
 
3741
4101
  // src/saved-search/saved-search.schema.ts
3742
- var import_yup35 = require("yup");
4102
+ var import_yup36 = require("yup");
3743
4103
 
3744
4104
  // src/saved-search/saved-search.constant.ts
3745
4105
  var SavedSearchStatus = /* @__PURE__ */ ((SavedSearchStatus2) => {
@@ -3750,21 +4110,21 @@ var SavedSearchStatus = /* @__PURE__ */ ((SavedSearchStatus2) => {
3750
4110
  var SupportedSavedSearchStatuses = Object.values(SavedSearchStatus);
3751
4111
 
3752
4112
  // src/saved-search/saved-search.schema.ts
3753
- var SavedSearchSchema = (0, import_yup35.object)({
4113
+ var SavedSearchSchema = (0, import_yup36.object)({
3754
4114
  /** Public identifier. Server-assigned. */
3755
- shortId: (0, import_yup35.string)().trim().label("Short Id"),
4115
+ shortId: (0, import_yup36.string)().trim().label("Short Id"),
3756
4116
  /**
3757
4117
  * Owner's Better Auth account id. Server-assigned from the verified token,
3758
4118
  * never accepted from a request body.
3759
4119
  */
3760
- ownerUserId: (0, import_yup35.string)().trim().label("Owner"),
4120
+ ownerUserId: (0, import_yup36.string)().trim().label("Owner"),
3761
4121
  /** What the user calls this search, e.g. "Remote React roles". */
3762
- label: (0, import_yup35.string)().trim().max(120).required().label("Label"),
4122
+ label: (0, import_yup36.string)().trim().max(120).required().label("Label"),
3763
4123
  /**
3764
4124
  * The free-text query. Optional because a search can be filters-only ("all
3765
4125
  * remote jobs in Sweden"), which is a legitimate alert to want.
3766
4126
  */
3767
- search: (0, import_yup35.string)().trim().max(500).optional().label("Search Text"),
4127
+ search: (0, import_yup36.string)().trim().max(500).optional().label("Search Text"),
3768
4128
  /**
3769
4129
  * Field filters, in the same `{ field: values }` shape the search endpoint
3770
4130
  * parses from `filter[field]=a,b`. Held as a free-form object rather than a
@@ -3772,7 +4132,7 @@ var SavedSearchSchema = (0, import_yup35.object)({
3772
4132
  * change independently; validating them here would put this package in the
3773
4133
  * business of tracking that vocabulary.
3774
4134
  */
3775
- filter: (0, import_yup35.object)().optional().default({}).label("Filters"),
4135
+ filter: (0, import_yup36.object)().optional().default({}).label("Filters"),
3776
4136
  /**
3777
4137
  * How often to mail matches. `off` keeps the search saved but silent, which is
3778
4138
  * the difference between "I don't want mail" and "delete my search".
@@ -3781,26 +4141,26 @@ var SavedSearchSchema = (0, import_yup35.object)({
3781
4141
  * than defining a second one: two vocabularies for the same concept is how
3782
4142
  * they drift.
3783
4143
  */
3784
- frequency: (0, import_yup35.string)().oneOf(SupportedJobAlertFrequencies).default("off" /* Off */).label("Alert Frequency"),
3785
- status: (0, import_yup35.string)().oneOf(SupportedSavedSearchStatuses).default("active" /* Active */).label("Status"),
4144
+ frequency: (0, import_yup36.string)().oneOf(SupportedJobAlertFrequencies).default("off" /* Off */).label("Alert Frequency"),
4145
+ status: (0, import_yup36.string)().oneOf(SupportedSavedSearchStatuses).default("active" /* Active */).label("Status"),
3786
4146
  /**
3787
4147
  * Epoch ms of the last alert sent for this search. The matching sweep reads it
3788
4148
  * to decide whether the cadence is due, and writes it only AFTER a successful
3789
4149
  * publish, so a failed run retries next tick rather than silently skipping a
3790
4150
  * period.
3791
4151
  */
3792
- lastNotifiedAt: (0, import_yup35.number)().optional().label("Last Notified At"),
4152
+ lastNotifiedAt: (0, import_yup36.number)().optional().label("Last Notified At"),
3793
4153
  /**
3794
4154
  * Epoch ms high-water mark: only jobs created after this are "new" for the
3795
4155
  * next alert. Kept separate from `lastNotifiedAt` because a run that finds no
3796
4156
  * matches should still advance what counts as new, without implying mail was
3797
4157
  * sent.
3798
4158
  */
3799
- lastMatchedAt: (0, import_yup35.number)().optional().label("Last Matched At")
4159
+ lastMatchedAt: (0, import_yup36.number)().optional().label("Last Matched At")
3800
4160
  }).noUnknown().strict();
3801
4161
 
3802
4162
  // src/report/report.schema.ts
3803
- var import_yup36 = require("yup");
4163
+ var import_yup37 = require("yup");
3804
4164
 
3805
4165
  // src/report/report.constant.ts
3806
4166
  var ResourceType = /* @__PURE__ */ ((ResourceType3) => {
@@ -3824,15 +4184,15 @@ var ReportReason = /* @__PURE__ */ ((ReportReason3) => {
3824
4184
  var SupportedReportReasons = Object.values(ReportReason);
3825
4185
 
3826
4186
  // src/report/report.schema.ts
3827
- var ReportSchema = (0, import_yup36.object)({
3828
- type: (0, import_yup36.mixed)().oneOf(SupportedResourceTypes).required().label("Type"),
3829
- resourceId: (0, import_yup36.string)().required().label("Resource ID"),
3830
- reason: (0, import_yup36.mixed)().oneOf(SupportedReportReasons).required("Please choose a reason").label("Reason"),
3831
- comment: (0, import_yup36.string)().max(1e3).optional().label("Comment")
4187
+ var ReportSchema = (0, import_yup37.object)({
4188
+ type: (0, import_yup37.mixed)().oneOf(SupportedResourceTypes).required().label("Type"),
4189
+ resourceId: (0, import_yup37.string)().required().label("Resource ID"),
4190
+ reason: (0, import_yup37.mixed)().oneOf(SupportedReportReasons).required("Please choose a reason").label("Reason"),
4191
+ comment: (0, import_yup37.string)().max(1e3).optional().label("Comment")
3832
4192
  }).label("Report Schema");
3833
4193
 
3834
4194
  // src/feedback/feedback-filter.schema.ts
3835
- var import_yup37 = require("yup");
4195
+ var import_yup38 = require("yup");
3836
4196
 
3837
4197
  // src/feedback/feedback.constant.ts
3838
4198
  var FeedbackType = /* @__PURE__ */ ((FeedbackType3) => {
@@ -3855,7 +4215,7 @@ var SupportedFeedbackStatuses = Object.values(FeedbackStatus);
3855
4215
  var FEEDBACK_MESSAGE_MAX = 4e3;
3856
4216
 
3857
4217
  // src/feedback/feedback-filter.schema.ts
3858
- var FeedbackFilterShape = (0, import_yup37.object)({
4218
+ var FeedbackFilterShape = (0, import_yup38.object)({
3859
4219
  type: enumFacet(SupportedFeedbackTypes, "Type"),
3860
4220
  status: enumFacet(SupportedFeedbackStatuses, "Status")
3861
4221
  }).label("Feedback Filter");
@@ -3865,18 +4225,18 @@ var FeedbackFilterSchema = rejectUnknownKeys(
3865
4225
  );
3866
4226
 
3867
4227
  // src/feedback/feedback.schema.ts
3868
- var import_yup38 = require("yup");
3869
- var FeedbackSchema = (0, import_yup38.object)({
3870
- type: (0, import_yup38.mixed)().oneOf(SupportedFeedbackTypes).required().label("Type"),
3871
- message: (0, import_yup38.string)().trim().required("Please enter a message").max(FEEDBACK_MESSAGE_MAX).label("Message"),
3872
- email: (0, import_yup38.string)().email().optional().label("Email"),
3873
- pageUrl: (0, import_yup38.string)().optional().label("Page URL"),
3874
- status: (0, import_yup38.mixed)().oneOf(SupportedFeedbackStatuses).optional().label("Status")
4228
+ var import_yup39 = require("yup");
4229
+ var FeedbackSchema = (0, import_yup39.object)({
4230
+ type: (0, import_yup39.mixed)().oneOf(SupportedFeedbackTypes).required().label("Type"),
4231
+ message: (0, import_yup39.string)().trim().required("Please enter a message").max(FEEDBACK_MESSAGE_MAX).label("Message"),
4232
+ email: (0, import_yup39.string)().email().optional().label("Email"),
4233
+ pageUrl: (0, import_yup39.string)().optional().label("Page URL"),
4234
+ status: (0, import_yup39.mixed)().oneOf(SupportedFeedbackStatuses).optional().label("Status")
3875
4235
  }).label("Feedback Schema");
3876
4236
 
3877
4237
  // src/skill/skill-filter.schema.ts
3878
- var import_yup39 = require("yup");
3879
- var SkillFilterShape = (0, import_yup39.object)({
4238
+ var import_yup40 = require("yup");
4239
+ var SkillFilterShape = (0, import_yup40.object)({
3880
4240
  /** Display names, matched exactly. Free-text matching is `?search=`. */
3881
4241
  names: tokens("Names"),
3882
4242
  /**
@@ -3894,7 +4254,7 @@ var SkillFilterSchema = rejectUnknownKeys(
3894
4254
  );
3895
4255
 
3896
4256
  // src/billing/billing.schema.ts
3897
- var import_yup40 = require("yup");
4257
+ var import_yup41 = require("yup");
3898
4258
 
3899
4259
  // src/billing/billing.constant.ts
3900
4260
  var PlanKind = /* @__PURE__ */ ((PlanKind2) => {
@@ -3997,60 +4357,60 @@ var GRANT_MAX_LIMIT = 1e5;
3997
4357
  var CONSUME_MAX_UNITS = 1e3;
3998
4358
 
3999
4359
  // src/billing/billing.schema.ts
4000
- var MoneySchema = (0, import_yup40.object)({
4001
- amountMinor: (0, import_yup40.number)().integer().min(0).required().label("Amount (minor units)"),
4360
+ var MoneySchema = (0, import_yup41.object)({
4361
+ amountMinor: (0, import_yup41.number)().integer().min(0).required().label("Amount (minor units)"),
4002
4362
  /**
4003
4363
  * ISO-4217, as returned by Paddle. Deliberately NOT constrained to
4004
4364
  * `SupportedSalaryCurrencies`: that list is the job-salary vocabulary, and
4005
4365
  * Paddle bills in currencies it does not contain. Constraining it here would
4006
4366
  * reject real payments.
4007
4367
  */
4008
- currencyCode: (0, import_yup40.string)().trim().uppercase().required().label("Currency")
4368
+ currencyCode: (0, import_yup41.string)().trim().uppercase().required().label("Currency")
4009
4369
  }).label("Money");
4010
- var PlanGrantSchema = (0, import_yup40.object)({
4370
+ var PlanGrantSchema = (0, import_yup41.object)({
4011
4371
  /**
4012
4372
  * The gated capability, e.g. `job.post`. A free string on purpose: see the
4013
4373
  * note on `FeatureKey` in `billing.constant.ts`. Adding a feature to the
4014
4374
  * paywall must not require releasing this package.
4015
4375
  */
4016
- feature: (0, import_yup40.string)().trim().required().min(1).max(64).label("Feature"),
4017
- type: (0, import_yup40.string)().oneOf(SupportedGrantTypes).required().default("boolean" /* Boolean */).label("Grant Type"),
4376
+ feature: (0, import_yup41.string)().trim().required().min(1).max(64).label("Feature"),
4377
+ type: (0, import_yup41.string)().oneOf(SupportedGrantTypes).required().default("boolean" /* Boolean */).label("Grant Type"),
4018
4378
  /** Uses per period. Required for `quota`, meaningless otherwise. */
4019
- limit: (0, import_yup40.number)().integer().min(1).max(GRANT_MAX_LIMIT).optional().label("Limit").when("type", {
4379
+ limit: (0, import_yup41.number)().integer().min(1).max(GRANT_MAX_LIMIT).optional().label("Limit").when("type", {
4020
4380
  is: "quota" /* Quota */,
4021
4381
  then: (s) => s.required("A quota grant needs a limit")
4022
4382
  }),
4023
- period: (0, import_yup40.string)().oneOf(SupportedQuotaPeriods).optional().default("month" /* Month */).label("Period").when("type", {
4383
+ period: (0, import_yup41.string)().oneOf(SupportedQuotaPeriods).optional().default("month" /* Month */).label("Period").when("type", {
4024
4384
  is: "quota" /* Quota */,
4025
4385
  then: (s) => s.required("A quota grant needs a period")
4026
4386
  }),
4027
4387
  /** Non-expiring uses granted per purchase. Required for `credit`. */
4028
- amount: (0, import_yup40.number)().integer().min(1).max(GRANT_MAX_LIMIT).optional().label("Amount").when("type", {
4388
+ amount: (0, import_yup41.number)().integer().min(1).max(GRANT_MAX_LIMIT).optional().label("Amount").when("type", {
4029
4389
  is: "credit" /* Credit */,
4030
4390
  then: (s) => s.required("A credit grant needs an amount")
4031
4391
  })
4032
4392
  }).label("Plan Grant");
4033
- var PaddlePriceIdsSchema = (0, import_yup40.object)({
4034
- sandbox: (0, import_yup40.array)().of((0, import_yup40.string)().required()).optional().default([]).label("Sandbox Price IDs"),
4035
- live: (0, import_yup40.array)().of((0, import_yup40.string)().required()).optional().default([]).label("Live Price IDs")
4393
+ var PaddlePriceIdsSchema = (0, import_yup41.object)({
4394
+ sandbox: (0, import_yup41.array)().of((0, import_yup41.string)().required()).optional().default([]).label("Sandbox Price IDs"),
4395
+ live: (0, import_yup41.array)().of((0, import_yup41.string)().required()).optional().default([]).label("Live Price IDs")
4036
4396
  }).label("Paddle Price IDs");
4037
- var PlanSchema = (0, import_yup40.object)({
4397
+ var PlanSchema = (0, import_yup41.object)({
4038
4398
  /** Stable slug, e.g. `employer-pro`. The identifier everything else references. */
4039
- key: (0, import_yup40.string)().trim().lowercase().required().min(2).max(64).label("Key"),
4040
- name: (0, import_yup40.string)().required().min(1).max(120).label("Name"),
4041
- description: (0, import_yup40.string)().optional().max(500).label("Description"),
4042
- kind: (0, import_yup40.string)().oneOf(SupportedPlanKinds).required().default("subscription" /* Subscription */).label("Kind"),
4399
+ key: (0, import_yup41.string)().trim().lowercase().required().min(2).max(64).label("Key"),
4400
+ name: (0, import_yup41.string)().required().min(1).max(120).label("Name"),
4401
+ description: (0, import_yup41.string)().optional().max(500).label("Description"),
4402
+ kind: (0, import_yup41.string)().oneOf(SupportedPlanKinds).required().default("subscription" /* Subscription */).label("Kind"),
4043
4403
  /**
4044
4404
  * The line this plan belongs to; a user holds one live subscription per line.
4045
4405
  * Optional on purpose: absent is the default line, so existing plans need no
4046
4406
  * migration and together still read as "one plan at a time".
4047
4407
  */
4048
- audience: (0, import_yup40.string)().oneOf(SupportedPlanAudiences).optional().label("Audience"),
4049
- status: (0, import_yup40.string)().oneOf(SupportedPlanStatuses).required().default("draft" /* Draft */).label("Status"),
4408
+ audience: (0, import_yup41.string)().oneOf(SupportedPlanAudiences).optional().label("Audience"),
4409
+ status: (0, import_yup41.string)().oneOf(SupportedPlanStatuses).required().default("draft" /* Draft */).label("Status"),
4050
4410
  paddlePriceIds: PaddlePriceIdsSchema.default(
4051
4411
  () => PaddlePriceIdsSchema.getDefault()
4052
4412
  ),
4053
- grants: (0, import_yup40.array)().of(PlanGrantSchema.required()).required().min(1, "A plan must grant at least one feature").label("Grants"),
4413
+ grants: (0, import_yup41.array)().of(PlanGrantSchema.required()).required().min(1, "A plan must grant at least one feature").label("Grants"),
4054
4414
  /** Display price for the pricing page. Billing is always Paddle's own figure. */
4055
4415
  price: MoneySchema.optional().default(void 0).label("Price"),
4056
4416
  /**
@@ -4063,416 +4423,84 @@ var PlanSchema = (0, import_yup40.object)({
4063
4423
  * a subscription as "monthly" at the use site rather than defaulting it here,
4064
4424
  * so an unset value stays visible as unset.
4065
4425
  */
4066
- billingPeriod: (0, import_yup40.string)().oneOf(SupportedBillingPeriods).optional().label("Billing Period"),
4426
+ billingPeriod: (0, import_yup41.string)().oneOf(SupportedBillingPeriods).optional().label("Billing Period"),
4067
4427
  /** Ordering on the pricing page, ascending. */
4068
- sortOrder: (0, import_yup40.number)().integer().optional().default(0).label("Sort Order"),
4069
- createdAt: (0, import_yup40.number)().optional().label("Created At"),
4070
- updatedAt: (0, import_yup40.number)().optional().label("Updated At")
4428
+ sortOrder: (0, import_yup41.number)().integer().optional().default(0).label("Sort Order"),
4429
+ createdAt: (0, import_yup41.number)().optional().label("Created At"),
4430
+ updatedAt: (0, import_yup41.number)().optional().label("Updated At")
4071
4431
  }).label("Plan");
4072
- var BillingSubscriptionSchema = (0, import_yup40.object)({
4073
- userId: (0, import_yup40.string)().required().label("User ID"),
4074
- paddleSubscriptionId: (0, import_yup40.string)().required().label("Paddle Subscription ID"),
4075
- paddleCustomerId: (0, import_yup40.string)().required().label("Paddle Customer ID"),
4076
- planKey: (0, import_yup40.string)().required().label("Plan Key"),
4077
- priceIds: (0, import_yup40.array)().of((0, import_yup40.string)().required()).required().label("Price IDs"),
4078
- status: (0, import_yup40.string)().oneOf(SupportedSubscriptionStatuses).required().label("Status"),
4432
+ var BillingSubscriptionSchema = (0, import_yup41.object)({
4433
+ userId: (0, import_yup41.string)().required().label("User ID"),
4434
+ paddleSubscriptionId: (0, import_yup41.string)().required().label("Paddle Subscription ID"),
4435
+ paddleCustomerId: (0, import_yup41.string)().required().label("Paddle Customer ID"),
4436
+ planKey: (0, import_yup41.string)().required().label("Plan Key"),
4437
+ priceIds: (0, import_yup41.array)().of((0, import_yup41.string)().required()).required().label("Price IDs"),
4438
+ status: (0, import_yup41.string)().oneOf(SupportedSubscriptionStatuses).required().label("Status"),
4079
4439
  /** Start of the period the quota is anchored to. */
4080
- currentPeriodStart: (0, import_yup40.number)().optional().label("Current Period Start"),
4081
- currentPeriodEnd: (0, import_yup40.number)().optional().label("Current Period End"),
4440
+ currentPeriodStart: (0, import_yup41.number)().optional().label("Current Period Start"),
4441
+ currentPeriodEnd: (0, import_yup41.number)().optional().label("Current Period End"),
4082
4442
  /** Set when a cancellation is scheduled; access continues until the period ends. */
4083
- canceledAt: (0, import_yup40.number)().optional().label("Canceled At"),
4443
+ canceledAt: (0, import_yup41.number)().optional().label("Canceled At"),
4084
4444
  /**
4085
4445
  * A downgrade booked for the end of the current period: the plan key it moves
4086
4446
  * to at renewal. Upgrades never set this; they apply immediately. Mirrored
4087
4447
  * from the Paddle subscription's own `customData`, so Paddle stays the source
4088
4448
  * of truth.
4089
4449
  */
4090
- pendingPlanKey: (0, import_yup40.string)().optional().label("Pending Plan Key"),
4450
+ pendingPlanKey: (0, import_yup41.string)().optional().label("Pending Plan Key"),
4091
4451
  /** `occurred_at` of the newest Paddle event applied. Guards out-of-order writes. */
4092
- lastEventAt: (0, import_yup40.number)().required().default(0).label("Last Event At"),
4093
- createdAt: (0, import_yup40.number)().optional().label("Created At"),
4094
- updatedAt: (0, import_yup40.number)().optional().label("Updated At")
4452
+ lastEventAt: (0, import_yup41.number)().required().default(0).label("Last Event At"),
4453
+ createdAt: (0, import_yup41.number)().optional().label("Created At"),
4454
+ updatedAt: (0, import_yup41.number)().optional().label("Updated At")
4095
4455
  }).label("Billing Subscription");
4096
- var BillingTransactionSchema = (0, import_yup40.object)({
4097
- userId: (0, import_yup40.string)().required().label("User ID"),
4098
- paddleTransactionId: (0, import_yup40.string)().required().label("Paddle Transaction ID"),
4099
- paddleCustomerId: (0, import_yup40.string)().optional().label("Paddle Customer ID"),
4100
- paddleSubscriptionId: (0, import_yup40.string)().optional().label("Paddle Subscription ID"),
4101
- planKey: (0, import_yup40.string)().optional().label("Plan Key"),
4102
- priceIds: (0, import_yup40.array)().of((0, import_yup40.string)().required()).required().label("Price IDs"),
4103
- status: (0, import_yup40.string)().oneOf(SupportedTransactionStatuses).required().label("Status"),
4456
+ var BillingTransactionSchema = (0, import_yup41.object)({
4457
+ userId: (0, import_yup41.string)().required().label("User ID"),
4458
+ paddleTransactionId: (0, import_yup41.string)().required().label("Paddle Transaction ID"),
4459
+ paddleCustomerId: (0, import_yup41.string)().optional().label("Paddle Customer ID"),
4460
+ paddleSubscriptionId: (0, import_yup41.string)().optional().label("Paddle Subscription ID"),
4461
+ planKey: (0, import_yup41.string)().optional().label("Plan Key"),
4462
+ priceIds: (0, import_yup41.array)().of((0, import_yup41.string)().required()).required().label("Price IDs"),
4463
+ status: (0, import_yup41.string)().oneOf(SupportedTransactionStatuses).required().label("Status"),
4104
4464
  total: MoneySchema.optional().default(void 0).label("Total"),
4105
- billedAt: (0, import_yup40.number)().optional().label("Billed At"),
4106
- lastEventAt: (0, import_yup40.number)().required().default(0).label("Last Event At"),
4107
- createdAt: (0, import_yup40.number)().optional().label("Created At"),
4108
- updatedAt: (0, import_yup40.number)().optional().label("Updated At")
4465
+ billedAt: (0, import_yup41.number)().optional().label("Billed At"),
4466
+ lastEventAt: (0, import_yup41.number)().required().default(0).label("Last Event At"),
4467
+ createdAt: (0, import_yup41.number)().optional().label("Created At"),
4468
+ updatedAt: (0, import_yup41.number)().optional().label("Updated At")
4109
4469
  }).label("Billing Transaction");
4110
- var FeatureEntitlementSchema = (0, import_yup40.object)({
4111
- feature: (0, import_yup40.string)().required().label("Feature"),
4112
- type: (0, import_yup40.string)().oneOf(SupportedGrantTypes).required().label("Grant Type"),
4470
+ var FeatureEntitlementSchema = (0, import_yup41.object)({
4471
+ feature: (0, import_yup41.string)().required().label("Feature"),
4472
+ type: (0, import_yup41.string)().oneOf(SupportedGrantTypes).required().label("Grant Type"),
4113
4473
  /** Whether the feature may be used AT ALL right now. */
4114
- allowed: (0, import_yup40.boolean)().required().default(false).label("Allowed"),
4474
+ allowed: (0, import_yup41.boolean)().required().default(false).label("Allowed"),
4115
4475
  /** Quota ceiling for the period. Absent for boolean grants. */
4116
- limit: (0, import_yup40.number)().integer().optional().label("Limit"),
4117
- used: (0, import_yup40.number)().integer().optional().label("Used"),
4476
+ limit: (0, import_yup41.number)().integer().optional().label("Limit"),
4477
+ used: (0, import_yup41.number)().integer().optional().label("Used"),
4118
4478
  /** Quota remaining plus any credit balance. Absent for boolean grants. */
4119
- remaining: (0, import_yup40.number)().integer().optional().label("Remaining"),
4479
+ remaining: (0, import_yup41.number)().integer().optional().label("Remaining"),
4120
4480
  /** Non-expiring balance from one-off purchases. */
4121
- credits: (0, import_yup40.number)().integer().optional().label("Credits"),
4481
+ credits: (0, import_yup41.number)().integer().optional().label("Credits"),
4122
4482
  /** When the quota next refreshes. */
4123
- resetsAt: (0, import_yup40.number)().optional().label("Resets At")
4483
+ resetsAt: (0, import_yup41.number)().optional().label("Resets At")
4124
4484
  }).label("Feature Entitlement");
4125
- var EntitlementDenialSchema = (0, import_yup40.object)({
4126
- feature: (0, import_yup40.string)().required().label("Feature"),
4127
- reason: (0, import_yup40.string)().oneOf(SupportedEntitlementDenialReasons).required().label("Reason"),
4128
- limit: (0, import_yup40.number)().integer().optional().label("Limit"),
4129
- used: (0, import_yup40.number)().integer().optional().label("Used"),
4130
- resetsAt: (0, import_yup40.number)().optional().label("Resets At"),
4131
- upgradeUrl: (0, import_yup40.string)().optional().label("Upgrade URL")
4485
+ var EntitlementDenialSchema = (0, import_yup41.object)({
4486
+ feature: (0, import_yup41.string)().required().label("Feature"),
4487
+ reason: (0, import_yup41.string)().oneOf(SupportedEntitlementDenialReasons).required().label("Reason"),
4488
+ limit: (0, import_yup41.number)().integer().optional().label("Limit"),
4489
+ used: (0, import_yup41.number)().integer().optional().label("Used"),
4490
+ resetsAt: (0, import_yup41.number)().optional().label("Resets At"),
4491
+ upgradeUrl: (0, import_yup41.string)().optional().label("Upgrade URL")
4132
4492
  }).label("Entitlement Denial");
4133
- var EntitlementConsumeSchema = (0, import_yup40.object)({
4134
- userId: (0, import_yup40.string)().required().label("User ID"),
4135
- feature: (0, import_yup40.string)().trim().required().min(1).max(64).label("Feature"),
4136
- units: (0, import_yup40.number)().integer().min(1).max(CONSUME_MAX_UNITS).required().default(1).label("Units"),
4493
+ var EntitlementConsumeSchema = (0, import_yup41.object)({
4494
+ userId: (0, import_yup41.string)().required().label("User ID"),
4495
+ feature: (0, import_yup41.string)().trim().required().min(1).max(64).label("Feature"),
4496
+ units: (0, import_yup41.number)().integer().min(1).max(CONSUME_MAX_UNITS).required().default(1).label("Units"),
4137
4497
  /**
4138
4498
  * Caller-supplied de-duplication key. Makes a spend exactly-once across the
4139
4499
  * retries that at-least-once delivery and ordinary network failures produce.
4140
4500
  */
4141
- idempotencyKey: (0, import_yup40.string)().trim().required().min(8).max(128).label("Idempotency Key")
4501
+ idempotencyKey: (0, import_yup41.string)().trim().required().min(8).max(128).label("Idempotency Key")
4142
4502
  }).label("Entitlement Consume");
4143
4503
 
4144
- // src/taxonomy/taxonomy.schema.ts
4145
- var import_yup41 = require("yup");
4146
-
4147
- // src/taxonomy/taxonomy.constant.ts
4148
- var SupportedConceptSources = ["seed", "manual", "import"];
4149
- var SupportedTaxonomySources = ["seed", "manual"];
4150
- var SupportedTaxonomyNodeKinds = ["category", "subcategory", "group"];
4151
- var SupportedPrefilterRules = [
4152
- "spoken_language",
4153
- "no_letters",
4154
- "url_or_email",
4155
- "fragment"
4156
- ];
4157
- var SupportedUnmatchedResolutions = ["pending", "added", "rejected"];
4158
- var SupportedUnmatchedReasons = ["ambiguous"];
4159
- var SupportedProposalKinds = ["concept", "alias", "placement"];
4160
- var SupportedProposalFilters = [...SupportedProposalKinds, "uncategorized"];
4161
- var SupportedProposalStatuses = ["draft", "approved", "discarded"];
4162
- var SupportedProposalMessageRoles = ["operator", "model"];
4163
- var SupportedResolverVerdicts = [
4164
- "same",
4165
- "narrower",
4166
- "broader",
4167
- "related",
4168
- "new_concept",
4169
- "out_of_scope"
4170
- ];
4171
- var SupportedAuditStatuses = ["pending", "correct", "wrong"];
4172
- var SupportedReleaseKinds = ["seed", "manual", "review", "auto"];
4173
- var SupportedReleaseStatuses = ["open", "rolled_back"];
4174
- var SupportedMatchTypes = ["exact", "canonical", "fuzzy", "none"];
4175
- var SupportedTaxonomyJobKinds = [
4176
- "describe-concepts",
4177
- "retype-concepts",
4178
- "generate-aliases",
4179
- "alias-backfill",
4180
- "close-gaps",
4181
- "propose-placements",
4182
- "place-unplaced",
4183
- "calibrate-placement",
4184
- "reseed",
4185
- "reindex",
4186
- "calibrate-resolver",
4187
- "golden",
4188
- "resolve-backlog"
4189
- ];
4190
- var SupportedTaxonomyJobStates = ["running", "succeeded", "failed", "cancelled"];
4191
- var CONCEPT_ID_PATTERN = /^[a-z][a-z0-9-]*_[a-z0-9-]+$/;
4192
- var VOCABULARY_ID_PATTERN = /^[a-z][a-z0-9-]{0,31}$/;
4193
- var TAXONOMY_NODE_ID_PATTERN = /^[A-Za-z0-9_]{1,64}$/;
4194
- var CONCEPT_TYPE_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
4195
- var SCHEDULE_MIN_HOURS = 1;
4196
- var SCHEDULE_MAX_HOURS = 24 * 30;
4197
-
4198
- // src/taxonomy/taxonomy.schema.ts
4199
- var conceptId = () => (0, import_yup41.string)().matches(CONCEPT_ID_PATTERN, "conceptId must look like <vocabulary>_<slug>");
4200
- var vocabularyId = () => (0, import_yup41.string)().matches(VOCABULARY_ID_PATTERN, "vocabulary must match [a-z][a-z0-9-]{0,31}");
4201
- var nodeId = () => (0, import_yup41.string)().matches(TAXONOMY_NODE_ID_PATTERN, "node ids look like CAT_01_TECH");
4202
- var strings = () => (0, import_yup41.array)((0, import_yup41.string)().trim().min(1).required());
4203
- var VocabularyTypeSchema = (0, import_yup41.object)({
4204
- id: (0, import_yup41.string)().matches(CONCEPT_TYPE_PATTERN, "type ids match [a-z][a-z0-9_]*").required().label("Type ID"),
4205
- /** Shown to the model when it classifies a concept; include examples. */
4206
- description: (0, import_yup41.string)().defined().label("Description")
4207
- }).label("Vocabulary Type");
4208
- var VocabularySchema = (0, import_yup41.object)({
4209
- vocabularyId: vocabularyId().required().label("Vocabulary ID"),
4210
- name: (0, import_yup41.string)().trim().min(1).required().label("Name"),
4211
- /** What one concept is called in prompts and messages: "skill", "role". */
4212
- noun: (0, import_yup41.string)().trim().min(1).required().label("Noun"),
4213
- nounPlural: (0, import_yup41.string)().trim().min(1).required().label("Noun (plural)"),
4214
- /** What belongs in this vocabulary, for prompts. */
4215
- description: (0, import_yup41.string)().trim().min(1).required().label("Description"),
4216
- /** Where its terms are written, for the "swap it in" test: "a job posting". */
4217
- appearsIn: (0, import_yup41.string)().trim().min(1).required().label("Appears In"),
4218
- /** Things that are NOT concepts of this vocabulary, for prompts. */
4219
- outOfScope: (0, import_yup41.string)().trim().min(1).required().label("Out of Scope"),
4220
- types: (0, import_yup41.array)(VocabularyTypeSchema.required()).min(1).required().test(
4221
- "unique-type-ids",
4222
- "type ids must be unique",
4223
- (types) => !types || new Set(types.map((t) => t.id)).size === types.length
4224
- ).label("Types"),
4225
- /** The type a concept gets when nothing more specific is known. One of `types`. */
4226
- defaultType: (0, import_yup41.string)().required().test("default-type-listed", "defaultType must be one of the type ids", function(value) {
4227
- const types = this.parent.types ?? [];
4228
- return types.some((t) => t.id === value);
4229
- }).label("Default Type"),
4230
- prefilter: (0, import_yup41.array)((0, import_yup41.string)().oneOf(SupportedPrefilterRules).required()).default([]).label("Prefilter"),
4231
- /**
4232
- * Words that qualify a term without changing what it names ("fluent",
4233
- * "native"), dropped from its canonical lookup form in THIS vocabulary only.
4234
- */
4235
- qualifiers: strings().optional().label("Qualifiers"),
4236
- /** Extra instructions per model task; each is optional. */
4237
- guidance: (0, import_yup41.object)({
4238
- describe: (0, import_yup41.string)().optional(),
4239
- classify: (0, import_yup41.string)().optional(),
4240
- aliases: (0, import_yup41.string)().optional(),
4241
- verify: (0, import_yup41.string)().optional(),
4242
- place: (0, import_yup41.string)().optional()
4243
- }).label("Guidance"),
4244
- source: (0, import_yup41.string)().oneOf(SupportedTaxonomySources).required().label("Source"),
4245
- createdAt: (0, import_yup41.number)().required().label("Created At"),
4246
- updatedAt: (0, import_yup41.number)().required().label("Updated At")
4247
- }).label("Vocabulary");
4248
- var TaxonomyNodeSchema = (0, import_yup41.object)({
4249
- nodeId: nodeId().required().label("Node ID"),
4250
- vocabulary: vocabularyId().required().label("Vocabulary"),
4251
- kind: (0, import_yup41.string)().oneOf(SupportedTaxonomyNodeKinds).required().label("Kind"),
4252
- name: (0, import_yup41.string)().trim().min(1).required().label("Name"),
4253
- /** Groups carry a description; categories and subcategories usually do not. */
4254
- description: (0, import_yup41.string)().optional().label("Description"),
4255
- aliases: strings().default([]).label("Aliases"),
4256
- /** null for categories (the roots). */
4257
- parentId: (0, import_yup41.string)().nullable().defined().label("Parent ID"),
4258
- /** Ancestor ids from the root down to and including this node. */
4259
- path: (0, import_yup41.array)((0, import_yup41.string)().required()).default([]).label("Path"),
4260
- source: (0, import_yup41.string)().oneOf(SupportedTaxonomySources).optional().label("Source"),
4261
- createdAt: (0, import_yup41.number)().required().label("Created At"),
4262
- updatedAt: (0, import_yup41.number)().required().label("Updated At")
4263
- }).label("Taxonomy Node");
4264
- var ConceptMembershipSchema = (0, import_yup41.object)({
4265
- categoryId: (0, import_yup41.string)().required().label("Category ID"),
4266
- categoryName: (0, import_yup41.string)().required().label("Category Name"),
4267
- subcategoryId: (0, import_yup41.string)().required().label("Subcategory ID"),
4268
- subcategoryName: (0, import_yup41.string)().required().label("Subcategory Name"),
4269
- groupId: (0, import_yup41.string)().required().label("Group ID"),
4270
- groupName: (0, import_yup41.string)().required().label("Group Name"),
4271
- /** The source record this membership came from; '' when written by hand. */
4272
- sourceId: (0, import_yup41.string)().default("").label("Source ID")
4273
- }).label("Concept Membership");
4274
- var ConceptSchema = (0, import_yup41.object)({
4275
- /** `<vocabulary>_<slug>`, e.g. `skills_power-bi`. Primary key. */
4276
- conceptId: conceptId().required().label("Concept ID"),
4277
- vocabulary: vocabularyId().required().label("Vocabulary"),
4278
- name: (0, import_yup41.string)().trim().min(1).max(200).required().label("Name"),
4279
- /** One of the vocabulary's type ids; checked against the vocabulary by the service. */
4280
- type: (0, import_yup41.string)().trim().matches(CONCEPT_TYPE_PATTERN, "type must be a type id").required().label("Type"),
4281
- description: (0, import_yup41.string)().trim().max(2e3).default("").label("Description"),
4282
- aliases: strings().default([]).label("Aliases"),
4283
- memberships: (0, import_yup41.array)(ConceptMembershipSchema.required()).default([]).label("Memberships"),
4284
- /** Always concepts of the same vocabulary. */
4285
- relatedConceptIds: strings().default([]).label("Related Concepts"),
4286
- source: (0, import_yup41.string)().oneOf(SupportedConceptSources).required().label("Source"),
4287
- createdAt: (0, import_yup41.number)().required().label("Created At"),
4288
- updatedAt: (0, import_yup41.number)().required().label("Updated At"),
4289
- /** Resume marker of `retype-concepts`: set once the type was examined. */
4290
- retypedAt: (0, import_yup41.number)().optional().label("Retyped At"),
4291
- /** Resume marker of `generate-aliases`: set even when no alias was added. */
4292
- aliasedAt: (0, import_yup41.number)().optional().label("Aliased At"),
4293
- /** Set while `description` is a stand-in; removed once a real one is written. */
4294
- descriptionPending: (0, import_yup41.boolean)().isTrue().optional().label("Description Pending")
4295
- }).label("Concept");
4296
- var UnmatchedTermSchema = (0, import_yup41.object)({
4297
- vocabulary: vocabularyId().required().label("Vocabulary"),
4298
- /** The raw input as first received. */
4299
- term: (0, import_yup41.string)().required().label("Term"),
4300
- /** The dedupe key, so "React JS" and "react.js" share a row. */
4301
- normalizedTerm: (0, import_yup41.string)().required().label("Normalized Term"),
4302
- count: (0, import_yup41.number)().integer().min(0).required().label("Count"),
4303
- firstSeenAt: (0, import_yup41.number)().required().label("First Seen At"),
4304
- lastSeenAt: (0, import_yup41.number)().required().label("Last Seen At"),
4305
- /** Which callers sent it, capped. */
4306
- sources: (0, import_yup41.array)(
4307
- (0, import_yup41.object)({
4308
- service: (0, import_yup41.string)().required(),
4309
- context: (0, import_yup41.string)().optional()
4310
- }).required()
4311
- ).default([]).label("Sources"),
4312
- resolution: (0, import_yup41.string)().oneOf(SupportedUnmatchedResolutions).required().label("Resolution"),
4313
- resolvedConceptId: (0, import_yup41.string)().optional().label("Resolved Concept"),
4314
- /** `ambiguous`: the concept exists; the caller gave no context that picked one. */
4315
- reason: (0, import_yup41.string)().oneOf(SupportedUnmatchedReasons).optional().label("Reason"),
4316
- /** The concepts an ambiguous term could mean. */
4317
- candidates: (0, import_yup41.array)((0, import_yup41.string)().required()).optional().label("Candidates"),
4318
- /** Who rejected it when it was not a human: `prefilter:<rule>` or `model`. */
4319
- rejectedBy: (0, import_yup41.string)().optional().label("Rejected By")
4320
- }).label("Unmatched Term");
4321
- var ProposalCandidateSchema = (0, import_yup41.object)({
4322
- groupId: (0, import_yup41.string)().required(),
4323
- groupName: (0, import_yup41.string)().required(),
4324
- subcategoryId: (0, import_yup41.string)().required(),
4325
- subcategoryName: (0, import_yup41.string)().required(),
4326
- categoryId: (0, import_yup41.string)().required(),
4327
- categoryName: (0, import_yup41.string)().required(),
4328
- score: (0, import_yup41.number)().required(),
4329
- nearestMembers: (0, import_yup41.array)((0, import_yup41.string)().required()).default([])
4330
- }).label("Proposal Candidate");
4331
- var ProposalMessageSchema = (0, import_yup41.object)({
4332
- role: (0, import_yup41.string)().oneOf(SupportedProposalMessageRoles).required(),
4333
- /** The operator's instruction, or the model's note on what it changed and why. */
4334
- content: (0, import_yup41.string)().defined(),
4335
- /** Which model answered. Absent on operator turns. */
4336
- model: (0, import_yup41.string)().optional(),
4337
- at: (0, import_yup41.number)().required()
4338
- }).label("Proposal Message");
4339
- var SuggestedHomeSchema = (0, import_yup41.object)({
4340
- categoryName: (0, import_yup41.string)().defined(),
4341
- subcategoryName: (0, import_yup41.string)().defined(),
4342
- groupName: (0, import_yup41.string)().defined()
4343
- }).label("Suggested Home");
4344
- var ResolverVerificationSchema = (0, import_yup41.object)({
4345
- verdict: (0, import_yup41.string)().oneOf(SupportedResolverVerdicts).required(),
4346
- /** The shown concept the verdict is about; null for `new_concept` / `out_of_scope`. */
4347
- conceptId: (0, import_yup41.string)().nullable().defined(),
4348
- evidence: (0, import_yup41.string)().defined(),
4349
- /** What the judge took the term to mean, abbreviations expanded. */
4350
- means: (0, import_yup41.string)().optional(),
4351
- /** For `same`: did the independent confirm step agree? */
4352
- consistent: (0, import_yup41.boolean)().optional(),
4353
- confirm: (0, import_yup41.object)({
4354
- answer: (0, import_yup41.string)().oneOf(["yes", "no"]).required(),
4355
- evidence: (0, import_yup41.string)().defined()
4356
- }).optional().default(void 0)
4357
- }).label("Resolver Verification");
4358
- var ConceptProposalSchema = (0, import_yup41.object)({
4359
- proposalId: (0, import_yup41.string)().required().label("Proposal ID"),
4360
- vocabulary: vocabularyId().required().label("Vocabulary"),
4361
- /** The miss this closes; `normalizedTerm` links back to its unmatched row. */
4362
- term: (0, import_yup41.string)().required().label("Term"),
4363
- normalizedTerm: (0, import_yup41.string)().required().label("Normalized Term"),
4364
- /** Demand at proposal time, so the queue ranks without a join. */
4365
- count: (0, import_yup41.number)().integer().min(0).required().label("Count"),
4366
- /** Other misses folded into this draft because they name the same new concept. */
4367
- mergedTerms: (0, import_yup41.array)(
4368
- (0, import_yup41.object)({
4369
- term: (0, import_yup41.string)().required(),
4370
- normalizedTerm: (0, import_yup41.string)().required(),
4371
- count: (0, import_yup41.number)().integer().min(0).required()
4372
- }).required()
4373
- ).optional().label("Merged Terms"),
4374
- /** Absent on drafts that predate the field: read as `concept`. */
4375
- kind: (0, import_yup41.string)().oneOf(SupportedProposalKinds).optional().label("Kind"),
4376
- /** For `alias`: the concept the term names. For `placement`: the concept to place. */
4377
- targetConceptId: (0, import_yup41.string)().optional().label("Target Concept"),
4378
- targetName: (0, import_yup41.string)().optional().label("Target Name"),
4379
- // The draft concept, editable until approved. Empty strings are valid here:
4380
- // an alias draft carries no draft concept of its own.
4381
- name: (0, import_yup41.string)().defined().label("Name"),
4382
- type: (0, import_yup41.string)().defined().label("Type"),
4383
- description: (0, import_yup41.string)().defined().label("Description"),
4384
- aliases: (0, import_yup41.array)((0, import_yup41.string)().required()).default([]).label("Aliases"),
4385
- /** Empty when no group fits: approved that way, the concept goes live uncategorized. */
4386
- memberships: (0, import_yup41.array)(ConceptMembershipSchema.required()).default([]).label("Memberships"),
4387
- suggestedHome: SuggestedHomeSchema.nullable().optional().default(void 0).label("Suggested Home"),
4388
- /** Resolved to concepts that exist; names that matched nothing are dropped. */
4389
- relatedConceptIds: (0, import_yup41.array)((0, import_yup41.string)().required()).default([]).label("Related Concepts"),
4390
- /** Every placement candidate considered, best first. */
4391
- candidates: (0, import_yup41.array)(ProposalCandidateSchema.required()).default([]).label("Candidates"),
4392
- needsReview: (0, import_yup41.boolean)().required().label("Needs Review"),
4393
- reason: (0, import_yup41.string)().optional().label("Reason"),
4394
- verification: ResolverVerificationSchema.optional().default(void 0).label("Verification"),
4395
- /** What the auto-accept gate decided for an alias verdict. */
4396
- autoAccept: (0, import_yup41.object)({
4397
- accepted: (0, import_yup41.boolean)().required(),
4398
- reason: (0, import_yup41.string)().optional(),
4399
- tier: (0, import_yup41.string)().optional(),
4400
- calibrationId: (0, import_yup41.string)().optional(),
4401
- releaseId: (0, import_yup41.string)().optional()
4402
- }).optional().default(void 0).label("Auto Accept"),
4403
- /** Which model drafted it. */
4404
- model: (0, import_yup41.string)().defined().label("Model"),
4405
- /** The refinement conversation, oldest first. */
4406
- messages: (0, import_yup41.array)(ProposalMessageSchema.required()).default([]).label("Messages"),
4407
- status: (0, import_yup41.string)().oneOf(SupportedProposalStatuses).required().label("Status"),
4408
- /** Set when approved: the concept this became. */
4409
- resolvedConceptId: (0, import_yup41.string)().optional().label("Resolved Concept"),
4410
- createdAt: (0, import_yup41.number)().required().label("Created At"),
4411
- updatedAt: (0, import_yup41.number)().required().label("Updated At")
4412
- }).label("Concept Proposal");
4413
- var ReleaseSchema = (0, import_yup41.object)({
4414
- releaseId: (0, import_yup41.string)().required().label("Release ID"),
4415
- kind: (0, import_yup41.string)().oneOf(SupportedReleaseKinds).required().label("Kind"),
4416
- status: (0, import_yup41.string)().oneOf(SupportedReleaseStatuses).required().label("Status"),
4417
- createdBy: (0, import_yup41.string)().required().label("Created By"),
4418
- createdAt: (0, import_yup41.number)().required().label("Created At"),
4419
- note: (0, import_yup41.string)().optional().label("Note"),
4420
- rolledBackAt: (0, import_yup41.number)().optional().label("Rolled Back At"),
4421
- rolledBackBy: (0, import_yup41.string)().optional().label("Rolled Back By")
4422
- }).label("Release");
4423
- var AuditSchema = (0, import_yup41.object)({
4424
- auditId: (0, import_yup41.string)().required().label("Audit ID"),
4425
- releaseId: (0, import_yup41.string)().required().label("Release ID"),
4426
- key: (0, import_yup41.string)().required().label("Key"),
4427
- term: (0, import_yup41.string)().required().label("Term"),
4428
- conceptId: (0, import_yup41.string)().required().label("Concept ID"),
4429
- tier: (0, import_yup41.string)().optional().label("Tier"),
4430
- status: (0, import_yup41.string)().oneOf(SupportedAuditStatuses).required().label("Status"),
4431
- createdAt: (0, import_yup41.number)().required().label("Created At"),
4432
- resolvedAt: (0, import_yup41.number)().optional().label("Resolved At"),
4433
- resolvedBy: (0, import_yup41.string)().optional().label("Resolved By")
4434
- }).label("Audit");
4435
- var ResolverSettingsSchema = (0, import_yup41.object)({
4436
- autoAccept: (0, import_yup41.object)({
4437
- /** Stop switch for WRITING auto keys. Off until a calibration passes. */
4438
- enabled: (0, import_yup41.boolean)().required(),
4439
- /** Minimum calibrated precision (Wilson 95% lower bound) for a tier to auto-accept. */
4440
- target: (0, import_yup41.number)().min(0.5).lessThan(1).required(),
4441
- maxPerRun: (0, import_yup41.number)().integer().min(0).required(),
4442
- maxPerDay: (0, import_yup41.number)().integer().min(0).required(),
4443
- /** Share of each auto release sampled into the audit queue (0..1). */
4444
- auditRate: (0, import_yup41.number)().min(0).max(1).required()
4445
- }).label("Auto Accept"),
4446
- /** Stop switch for SERVING auto keys: false hides them from lookups at once. */
4447
- serveAutoKeys: (0, import_yup41.boolean)().required().label("Serve Auto Keys"),
4448
- /** Minimum Meilisearch ranking score (0..1) for a fuzzy hit to be considered. */
4449
- fuzzyMinScore: (0, import_yup41.number)().min(0).max(1).required().label("Fuzzy Min Score"),
4450
- /** Per-client limit on public search, per minute per replica. 0 disables it. */
4451
- searchRateLimitPerMin: (0, import_yup41.number)().integer().min(0).required().label("Search Rate Limit"),
4452
- /** Master switch for scheduled maintenance jobs. */
4453
- scheduledJobs: (0, import_yup41.boolean)().required().label("Scheduled Jobs"),
4454
- updatedAt: (0, import_yup41.number)().optional().label("Updated At"),
4455
- updatedBy: (0, import_yup41.string)().optional().label("Updated By"),
4456
- /** Why auto-accept was last switched off automatically. */
4457
- trippedReason: (0, import_yup41.string)().optional().label("Tripped Reason")
4458
- }).label("Resolver Settings");
4459
- var TaxonomyScheduleSchema = (0, import_yup41.object)({
4460
- kind: (0, import_yup41.string)().oneOf(SupportedTaxonomyJobKinds).required().label("Job"),
4461
- enabled: (0, import_yup41.boolean)().required().label("Enabled"),
4462
- everyHours: (0, import_yup41.number)().min(SCHEDULE_MIN_HOURS).max(SCHEDULE_MAX_HOURS).required().label("Every (hours)"),
4463
- /** Last time the scheduler fired this job. */
4464
- lastRunAt: (0, import_yup41.number)().optional().label("Last Run At"),
4465
- updatedAt: (0, import_yup41.number)().required().label("Updated At")
4466
- }).label("Taxonomy Schedule");
4467
- var NormalizeContextSchema = (0, import_yup41.object)({
4468
- /** The title of the record the terms come from. */
4469
- title: (0, import_yup41.string)().max(200).optional(),
4470
- /** Backbone node ids (category, subcategory or group). */
4471
- categoryIds: (0, import_yup41.array)(nodeId().required()).max(10).optional(),
4472
- /** Other terms from the same record, in the same vocabulary. */
4473
- siblings: (0, import_yup41.array)((0, import_yup41.string)().max(200).required()).max(50).optional()
4474
- }).label("Normalize Context");
4475
-
4476
4504
  // src/taxonomy/taxonomy-filter.schema.ts
4477
4505
  var import_yup42 = require("yup");
4478
4506
  var ConceptFilterShape = (0, import_yup42.object)({