@thejob/schema 2.1.4 → 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -50,8 +50,31 @@ Practical consequences:
50
50
 
51
51
  Enum string values are consumed as literals across services and stored in Mongo, so
52
52
  their casing is data, not style. Group `managedBy` / `visibility` / `status` are
53
- **lowercase** (`system`, `private`, `archived`). Changing a case is a data migration,
54
- not a refactor existing documents will silently stop matching.
53
+ **lowercase** (`system`, `private`, `archived`), as are `SavedSearchStatus` and
54
+ `JobAlertFrequency`. Changing a case is a data migration, not a refactor —
55
+ existing documents will silently stop matching.
56
+
57
+ ## The consent fields are a compliance surface
58
+
59
+ Three shapes here decide whether the platform may mail someone, and each behaves
60
+ differently on purpose:
61
+
62
+ - **`notificationPrefs`** (`user.schema.ts`) — per-channel, opt-**out**. The
63
+ consumer reads `!== false`, so a user whose document predates the field must
64
+ read as *enabled*. That is why adding it needed no backfill migration, and why
65
+ a "tidy-up" that made the fields required would silently mute every legacy user.
66
+ - **`marketingConsent.subscribed`** — opt-**in**, read as `=== true`. The exact
67
+ inverse. Do not collapse the two into one shape; they have different legal
68
+ footing, which email-service's footer rules already encode.
69
+ - **`marketingConsent.jobAlertFrequency`** — a *second* gate on job-alert mail
70
+ only, so a user can stop alerts while staying subscribed generally.
71
+
72
+ `NotificationChannel` values are duplicated as a string union in `@thejob/notify`
73
+ (so that package stays dependency-free). This package remains the source of
74
+ truth; adding a channel means editing the enum, the `notificationPrefs` object
75
+ keys, its `.default()`, and that union.
76
+
77
+ Full picture: `thejob-notification-service/NOTIFICATIONS.md`.
55
78
 
56
79
  ## Conventions
57
80
 
package/dist/index.cjs CHANGED
@@ -102,6 +102,7 @@ __export(index_exports, {
102
102
  SYSTEM_DATE_FORMAT: () => SYSTEM_DATE_FORMAT,
103
103
  SavedSearchSchema: () => SavedSearchSchema,
104
104
  SavedSearchStatus: () => SavedSearchStatus,
105
+ SignupContextSource: () => SignupContextSource,
105
106
  SkillSchema: () => SkillSchema,
106
107
  SocialAccount: () => SocialAccount,
107
108
  SocialAccountSchema: () => SocialAccountSchema,
@@ -135,6 +136,7 @@ __export(index_exports, {
135
136
  SupportedResourceTypes: () => SupportedResourceTypes,
136
137
  SupportedSalaryCurrencies: () => SupportedSalaryCurrencies,
137
138
  SupportedSavedSearchStatuses: () => SupportedSavedSearchStatuses,
139
+ SupportedSignupContextSources: () => SupportedSignupContextSources,
138
140
  SupportedSocialAccounts: () => SupportedSocialAccounts,
139
141
  SupportedStudyTypes: () => SupportedStudyTypes,
140
142
  SupportedUserProfileVisibilities: () => SupportedUserProfileVisibilities,
@@ -2110,6 +2112,13 @@ var UserProfileVisibility = /* @__PURE__ */ ((UserProfileVisibility2) => {
2110
2112
  var SupportedUserProfileVisibilities = Object.values(
2111
2113
  UserProfileVisibility
2112
2114
  );
2115
+ var SignupContextSource = /* @__PURE__ */ ((SignupContextSource2) => {
2116
+ SignupContextSource2["CfHeader"] = "cf-header";
2117
+ SignupContextSource2["Location"] = "location";
2118
+ SignupContextSource2["Default"] = "default";
2119
+ return SignupContextSource2;
2120
+ })(SignupContextSource || {});
2121
+ var SupportedSignupContextSources = Object.values(SignupContextSource);
2113
2122
  var UserDetailType = /* @__PURE__ */ ((UserDetailType2) => {
2114
2123
  UserDetailType2["Overview"] = "overview";
2115
2124
  UserDetailType2["AdditionalInfo"] = "additionalInfo";
@@ -2533,10 +2542,28 @@ var UserGeneralDetailSchema = (0, import_yup33.object)({
2533
2542
  mobileVerified: (0, import_yup33.string)().nullable().optional().label("Mobile Verified"),
2534
2543
  experienceLevel: (0, import_yup33.string)().oneOf(SupportedExperienceLevels).required().label("Experience level"),
2535
2544
  location: LocationSchema.required().label("Location"),
2536
- region: (0, import_yup33.object)().shape({
2537
- country: (0, import_yup33.string)().trim().required().label("Region Country"),
2538
- lang: (0, import_yup33.string)().trim().required().label("Region Language")
2539
- }).optional().default(void 0).label("Region"),
2545
+ /**
2546
+ * Where the account was created, captured once and never rewritten.
2547
+ *
2548
+ * Server-owned: listed in user-service's SERVER_CONTROLLED_FIELDS and absent
2549
+ * from every user-writable schema, so a client cannot set or amend it. That
2550
+ * immutability is the point — this answers "which market/jurisdiction governed
2551
+ * this user at sign-up?", which a field the user can PATCH could never do.
2552
+ *
2553
+ * `source` records HOW the value was obtained, because most of it is inferred
2554
+ * rather than measured, and an audit has to be able to tell those apart:
2555
+ * - `cf-header` measured from Cloudflare's `cf-ipcountry` at sign-up
2556
+ * - `location` derived from the user's self-entered profile location
2557
+ * - `default` assumed (no signal available); presumption, not evidence
2558
+ */
2559
+ signupContext: (0, import_yup33.object)().shape({
2560
+ // ISO 3166-1 alpha-2, uppercase — the shape `cf-ipcountry` returns, so
2561
+ // measured and inferred values are directly comparable.
2562
+ country: (0, import_yup33.string)().trim().uppercase().length(2).required().label("Signup Country"),
2563
+ locale: (0, import_yup33.string)().trim().required().label("Signup Locale"),
2564
+ source: (0, import_yup33.string)().oneOf(SupportedSignupContextSources).required().label("Signup Context Source"),
2565
+ at: (0, import_yup33.number)().required().label("Signup Context At")
2566
+ }).optional().default(void 0).label("Signup Context"),
2540
2567
  profileVisibility: (0, import_yup33.string)().oneOf(SupportedUserProfileVisibilities).default("public" /* Public */).label("Profile Visibility")
2541
2568
  }).noUnknown().strict().label("General Detail");
2542
2569
 
@@ -2886,6 +2913,7 @@ var StudentCompletenessSchema = UserCompletenessSchema.omit([
2886
2913
  SYSTEM_DATE_FORMAT,
2887
2914
  SavedSearchSchema,
2888
2915
  SavedSearchStatus,
2916
+ SignupContextSource,
2889
2917
  SkillSchema,
2890
2918
  SocialAccount,
2891
2919
  SocialAccountSchema,
@@ -2919,6 +2947,7 @@ var StudentCompletenessSchema = UserCompletenessSchema.omit([
2919
2947
  SupportedResourceTypes,
2920
2948
  SupportedSalaryCurrencies,
2921
2949
  SupportedSavedSearchStatuses,
2950
+ SupportedSignupContextSources,
2922
2951
  SupportedSocialAccounts,
2923
2952
  SupportedStudyTypes,
2924
2953
  SupportedUserProfileVisibilities,
package/dist/index.d.cts CHANGED
@@ -1500,6 +1500,24 @@ declare enum UserProfileVisibility {
1500
1500
  Hidden = "hidden"
1501
1501
  }
1502
1502
  declare const SupportedUserProfileVisibilities: UserProfileVisibility[];
1503
+ /**
1504
+ * How a user's `signupContext` was obtained. Stored on the document, so these
1505
+ * strings are data: lowercase, and a rename is a migration.
1506
+ *
1507
+ * The distinction is deliberate. Only `CfHeader` is measured; the other two are
1508
+ * inferred, and `Default` is a bare presumption with no signal behind it at all.
1509
+ * Collapsing them would make an assumed country indistinguishable from an
1510
+ * observed one, which is precisely what a compliance record must not do.
1511
+ */
1512
+ declare enum SignupContextSource {
1513
+ /** Measured from Cloudflare's `cf-ipcountry` header at sign-up. */
1514
+ CfHeader = "cf-header",
1515
+ /** Derived from the user's self-entered profile location (backfill). */
1516
+ Location = "location",
1517
+ /** Assumed; no signal was available. A presumption, not evidence. */
1518
+ Default = "default"
1519
+ }
1520
+ declare const SupportedSignupContextSources: SignupContextSource[];
1503
1521
  declare enum UserDetailType {
1504
1522
  Overview = "overview",
1505
1523
  AdditionalInfo = "additionalInfo",
@@ -1932,9 +1950,11 @@ declare const UserSchema: yup.ObjectSchema<{
1932
1950
  coordinates: number[];
1933
1951
  };
1934
1952
  };
1935
- region: {
1953
+ signupContext: {
1936
1954
  country: string;
1937
- lang: string;
1955
+ at: number;
1956
+ locale: string;
1957
+ source: NonNullable<SignupContextSource | undefined>;
1938
1958
  } | undefined;
1939
1959
  profileVisibility: UserProfileVisibility;
1940
1960
  openToWork: boolean;
@@ -2137,7 +2157,7 @@ declare const UserSchema: yup.ObjectSchema<{
2137
2157
  coordinates: "";
2138
2158
  };
2139
2159
  };
2140
- region: undefined;
2160
+ signupContext: undefined;
2141
2161
  profileVisibility: UserProfileVisibility.Public;
2142
2162
  openToWork: false;
2143
2163
  jobSearchUrgency: undefined;
@@ -2189,9 +2209,11 @@ declare const UserGeneralDetailSchema: yup.ObjectSchema<{
2189
2209
  coordinates: number[];
2190
2210
  };
2191
2211
  };
2192
- region: {
2212
+ signupContext: {
2193
2213
  country: string;
2194
- lang: string;
2214
+ at: number;
2215
+ locale: string;
2216
+ source: NonNullable<SignupContextSource | undefined>;
2195
2217
  } | undefined;
2196
2218
  profileVisibility: UserProfileVisibility;
2197
2219
  }, yup.AnyObject, {
@@ -2221,7 +2243,7 @@ declare const UserGeneralDetailSchema: yup.ObjectSchema<{
2221
2243
  coordinates: "";
2222
2244
  };
2223
2245
  };
2224
- region: undefined;
2246
+ signupContext: undefined;
2225
2247
  profileVisibility: UserProfileVisibility.Public;
2226
2248
  }, "">;
2227
2249
 
@@ -2995,4 +3017,4 @@ declare const StudentCompletenessSchema: yup.ObjectSchema<{
2995
3017
  };
2996
3018
  }, "">;
2997
3019
 
2998
- export { AnswerChoiceType, ApplicationReceivePreference, CampaignSchema, CampaignStatus, ChoiceQuestionSchema, Common, CompensationType, CompletenessScoreSchema, ContactTypes, CoordinatorPageLinkSchema, DISPLAY_DATE_FORMAT, DISPLAY_DATE_FORMAT_SHORT, DateStringSchema, DbDefaultSchema, DefaultPaginatedResponse, DefaultPaginationOptions, DefaultUserRoles, DurationSchema, EMPTY_STRING, EducationLevel, EducationSchema, EmployeeCount, EmploymentType, ExperienceLevel, FEEDBACK_MESSAGE_MAX, FeedbackSchema, FeedbackStatus, FeedbackType, GeneraDetailFields, GroupManagedBy, GroupMembershipSchema, GroupMembershipStatus, GroupSchema, GroupStatus, GroupTranslationSchema, GroupVisibility, InputQuestionSchema, JOB_TAXONOMY, JobAlertFrequency, JobCategory, JobIndustry, JobSchema, JobSearchUrgency, JobStatus, JobSubCategory, ListFilterSchema, LocationSchema, MIN_SALARY_LOWER_BOUND, MIN_SALARY_UPPER_BOUND, MailingListSchema, NotificationChannel, PREFILL_MIRRORED_KEYS, PRIVATE_PROFILE_FIELDS, PageSchema, PageStatus, PageType, type PaginatedResponse, PaginationSchema, PostSchema, PostStatus, type PrefillMirroredKey, type PrivateProfileField, ProficiencyLevel, QuestionSchema, QuestionType, ReadAndAcknowledgeQuestionSchema, RecruiterPageLinkSchema, ReferralSource, ReportReason, ReportSchema, ResourceType, SITEMAP_FORMAT, SYSTEM_DATE_FORMAT, SavedSearchSchema, SavedSearchStatus, SkillSchema, SocialAccount, SocialAccountSchema, StudentCompletenessSchema, StudyType, SupportedAnswerChoiceTypes, SupportedApplicationReceivePreferences, SupportedCampaignStatuses, SupportedCompensationTypes, SupportedContactTypes, SupportedEducationLevels, SupportedEmployeeCounts, SupportedEmploymentTypes, SupportedExperienceLevels, SupportedFeedbackStatuses, SupportedFeedbackTypes, SupportedJobAlertFrequencies, SupportedJobCategories, SupportedJobIndustries, SupportedJobSearchUrgencies, SupportedJobStatuses, SupportedJobSubCategories, SupportedNotificationChannels, SupportedPageStatuses, SupportedPageTypes, SupportedPostStatuses, SupportedProficiencyLevels, SupportedQuestionTypes, SupportedReferralSources, SupportedReportReasons, SupportedResourceTypes, SupportedSalaryCurrencies, type SupportedSalaryCurrency, SupportedSavedSearchStatuses, SupportedSocialAccounts, SupportedStudyTypes, SupportedUserProfileVisibilities, SupportedUserRoles, SupportedUserStatuses, SupportedWorkModes, TAXONOMY_LABELS, type TChoiceQuestionSchema, type TDurationSchema, type TInputQuestionSchema, type TJobSchema, type TPostSchema, type TQuestionSchema, type TReadAndAcknowledgeQuestionSchema, type TUserIdAndCreatedAtSchema, TermsAcceptedSchema, UserAdditionalInfoSchema, UserCertificationSchema, UserCompletenessSchema, UserCoordinatorProfileSchema, UserDetailType, UserGeneralDetailSchema, UserIdAndCreatedAtSchema, UserInterestSchema, UserJobPreferencesSchema, UserLanguageSchema, type UserProfileOverview, UserProfileVisibility, UserProjectSchema, UserRecruiterProfileSchema, UserRole, UserSchema, UserSkillSchema, UserStatus, WorkExperienceSchema, WorkMode, categoryOfIndustry, categoryOfSubCategory, dateString, deriveIndustry, getSchemaByQuestion, industriesOfCategory, industryOfSubCategory, subCategoriesOfIndustry };
3020
+ export { AnswerChoiceType, ApplicationReceivePreference, CampaignSchema, CampaignStatus, ChoiceQuestionSchema, Common, CompensationType, CompletenessScoreSchema, ContactTypes, CoordinatorPageLinkSchema, DISPLAY_DATE_FORMAT, DISPLAY_DATE_FORMAT_SHORT, DateStringSchema, DbDefaultSchema, DefaultPaginatedResponse, DefaultPaginationOptions, DefaultUserRoles, DurationSchema, EMPTY_STRING, EducationLevel, EducationSchema, EmployeeCount, EmploymentType, ExperienceLevel, FEEDBACK_MESSAGE_MAX, FeedbackSchema, FeedbackStatus, FeedbackType, GeneraDetailFields, GroupManagedBy, GroupMembershipSchema, GroupMembershipStatus, GroupSchema, GroupStatus, GroupTranslationSchema, GroupVisibility, InputQuestionSchema, JOB_TAXONOMY, JobAlertFrequency, JobCategory, JobIndustry, JobSchema, JobSearchUrgency, JobStatus, JobSubCategory, ListFilterSchema, LocationSchema, MIN_SALARY_LOWER_BOUND, MIN_SALARY_UPPER_BOUND, MailingListSchema, NotificationChannel, PREFILL_MIRRORED_KEYS, PRIVATE_PROFILE_FIELDS, PageSchema, PageStatus, PageType, type PaginatedResponse, PaginationSchema, PostSchema, PostStatus, type PrefillMirroredKey, type PrivateProfileField, ProficiencyLevel, QuestionSchema, QuestionType, ReadAndAcknowledgeQuestionSchema, RecruiterPageLinkSchema, ReferralSource, ReportReason, ReportSchema, ResourceType, SITEMAP_FORMAT, SYSTEM_DATE_FORMAT, SavedSearchSchema, SavedSearchStatus, SignupContextSource, SkillSchema, SocialAccount, SocialAccountSchema, StudentCompletenessSchema, StudyType, SupportedAnswerChoiceTypes, SupportedApplicationReceivePreferences, SupportedCampaignStatuses, SupportedCompensationTypes, SupportedContactTypes, SupportedEducationLevels, SupportedEmployeeCounts, SupportedEmploymentTypes, SupportedExperienceLevels, SupportedFeedbackStatuses, SupportedFeedbackTypes, SupportedJobAlertFrequencies, SupportedJobCategories, SupportedJobIndustries, SupportedJobSearchUrgencies, SupportedJobStatuses, SupportedJobSubCategories, SupportedNotificationChannels, SupportedPageStatuses, SupportedPageTypes, SupportedPostStatuses, SupportedProficiencyLevels, SupportedQuestionTypes, SupportedReferralSources, SupportedReportReasons, SupportedResourceTypes, SupportedSalaryCurrencies, type SupportedSalaryCurrency, SupportedSavedSearchStatuses, SupportedSignupContextSources, SupportedSocialAccounts, SupportedStudyTypes, SupportedUserProfileVisibilities, SupportedUserRoles, SupportedUserStatuses, SupportedWorkModes, TAXONOMY_LABELS, type TChoiceQuestionSchema, type TDurationSchema, type TInputQuestionSchema, type TJobSchema, type TPostSchema, type TQuestionSchema, type TReadAndAcknowledgeQuestionSchema, type TUserIdAndCreatedAtSchema, TermsAcceptedSchema, UserAdditionalInfoSchema, UserCertificationSchema, UserCompletenessSchema, UserCoordinatorProfileSchema, UserDetailType, UserGeneralDetailSchema, UserIdAndCreatedAtSchema, UserInterestSchema, UserJobPreferencesSchema, UserLanguageSchema, type UserProfileOverview, UserProfileVisibility, UserProjectSchema, UserRecruiterProfileSchema, UserRole, UserSchema, UserSkillSchema, UserStatus, WorkExperienceSchema, WorkMode, categoryOfIndustry, categoryOfSubCategory, dateString, deriveIndustry, getSchemaByQuestion, industriesOfCategory, industryOfSubCategory, subCategoriesOfIndustry };
package/dist/index.d.ts CHANGED
@@ -1500,6 +1500,24 @@ declare enum UserProfileVisibility {
1500
1500
  Hidden = "hidden"
1501
1501
  }
1502
1502
  declare const SupportedUserProfileVisibilities: UserProfileVisibility[];
1503
+ /**
1504
+ * How a user's `signupContext` was obtained. Stored on the document, so these
1505
+ * strings are data: lowercase, and a rename is a migration.
1506
+ *
1507
+ * The distinction is deliberate. Only `CfHeader` is measured; the other two are
1508
+ * inferred, and `Default` is a bare presumption with no signal behind it at all.
1509
+ * Collapsing them would make an assumed country indistinguishable from an
1510
+ * observed one, which is precisely what a compliance record must not do.
1511
+ */
1512
+ declare enum SignupContextSource {
1513
+ /** Measured from Cloudflare's `cf-ipcountry` header at sign-up. */
1514
+ CfHeader = "cf-header",
1515
+ /** Derived from the user's self-entered profile location (backfill). */
1516
+ Location = "location",
1517
+ /** Assumed; no signal was available. A presumption, not evidence. */
1518
+ Default = "default"
1519
+ }
1520
+ declare const SupportedSignupContextSources: SignupContextSource[];
1503
1521
  declare enum UserDetailType {
1504
1522
  Overview = "overview",
1505
1523
  AdditionalInfo = "additionalInfo",
@@ -1932,9 +1950,11 @@ declare const UserSchema: yup.ObjectSchema<{
1932
1950
  coordinates: number[];
1933
1951
  };
1934
1952
  };
1935
- region: {
1953
+ signupContext: {
1936
1954
  country: string;
1937
- lang: string;
1955
+ at: number;
1956
+ locale: string;
1957
+ source: NonNullable<SignupContextSource | undefined>;
1938
1958
  } | undefined;
1939
1959
  profileVisibility: UserProfileVisibility;
1940
1960
  openToWork: boolean;
@@ -2137,7 +2157,7 @@ declare const UserSchema: yup.ObjectSchema<{
2137
2157
  coordinates: "";
2138
2158
  };
2139
2159
  };
2140
- region: undefined;
2160
+ signupContext: undefined;
2141
2161
  profileVisibility: UserProfileVisibility.Public;
2142
2162
  openToWork: false;
2143
2163
  jobSearchUrgency: undefined;
@@ -2189,9 +2209,11 @@ declare const UserGeneralDetailSchema: yup.ObjectSchema<{
2189
2209
  coordinates: number[];
2190
2210
  };
2191
2211
  };
2192
- region: {
2212
+ signupContext: {
2193
2213
  country: string;
2194
- lang: string;
2214
+ at: number;
2215
+ locale: string;
2216
+ source: NonNullable<SignupContextSource | undefined>;
2195
2217
  } | undefined;
2196
2218
  profileVisibility: UserProfileVisibility;
2197
2219
  }, yup.AnyObject, {
@@ -2221,7 +2243,7 @@ declare const UserGeneralDetailSchema: yup.ObjectSchema<{
2221
2243
  coordinates: "";
2222
2244
  };
2223
2245
  };
2224
- region: undefined;
2246
+ signupContext: undefined;
2225
2247
  profileVisibility: UserProfileVisibility.Public;
2226
2248
  }, "">;
2227
2249
 
@@ -2995,4 +3017,4 @@ declare const StudentCompletenessSchema: yup.ObjectSchema<{
2995
3017
  };
2996
3018
  }, "">;
2997
3019
 
2998
- export { AnswerChoiceType, ApplicationReceivePreference, CampaignSchema, CampaignStatus, ChoiceQuestionSchema, Common, CompensationType, CompletenessScoreSchema, ContactTypes, CoordinatorPageLinkSchema, DISPLAY_DATE_FORMAT, DISPLAY_DATE_FORMAT_SHORT, DateStringSchema, DbDefaultSchema, DefaultPaginatedResponse, DefaultPaginationOptions, DefaultUserRoles, DurationSchema, EMPTY_STRING, EducationLevel, EducationSchema, EmployeeCount, EmploymentType, ExperienceLevel, FEEDBACK_MESSAGE_MAX, FeedbackSchema, FeedbackStatus, FeedbackType, GeneraDetailFields, GroupManagedBy, GroupMembershipSchema, GroupMembershipStatus, GroupSchema, GroupStatus, GroupTranslationSchema, GroupVisibility, InputQuestionSchema, JOB_TAXONOMY, JobAlertFrequency, JobCategory, JobIndustry, JobSchema, JobSearchUrgency, JobStatus, JobSubCategory, ListFilterSchema, LocationSchema, MIN_SALARY_LOWER_BOUND, MIN_SALARY_UPPER_BOUND, MailingListSchema, NotificationChannel, PREFILL_MIRRORED_KEYS, PRIVATE_PROFILE_FIELDS, PageSchema, PageStatus, PageType, type PaginatedResponse, PaginationSchema, PostSchema, PostStatus, type PrefillMirroredKey, type PrivateProfileField, ProficiencyLevel, QuestionSchema, QuestionType, ReadAndAcknowledgeQuestionSchema, RecruiterPageLinkSchema, ReferralSource, ReportReason, ReportSchema, ResourceType, SITEMAP_FORMAT, SYSTEM_DATE_FORMAT, SavedSearchSchema, SavedSearchStatus, SkillSchema, SocialAccount, SocialAccountSchema, StudentCompletenessSchema, StudyType, SupportedAnswerChoiceTypes, SupportedApplicationReceivePreferences, SupportedCampaignStatuses, SupportedCompensationTypes, SupportedContactTypes, SupportedEducationLevels, SupportedEmployeeCounts, SupportedEmploymentTypes, SupportedExperienceLevels, SupportedFeedbackStatuses, SupportedFeedbackTypes, SupportedJobAlertFrequencies, SupportedJobCategories, SupportedJobIndustries, SupportedJobSearchUrgencies, SupportedJobStatuses, SupportedJobSubCategories, SupportedNotificationChannels, SupportedPageStatuses, SupportedPageTypes, SupportedPostStatuses, SupportedProficiencyLevels, SupportedQuestionTypes, SupportedReferralSources, SupportedReportReasons, SupportedResourceTypes, SupportedSalaryCurrencies, type SupportedSalaryCurrency, SupportedSavedSearchStatuses, SupportedSocialAccounts, SupportedStudyTypes, SupportedUserProfileVisibilities, SupportedUserRoles, SupportedUserStatuses, SupportedWorkModes, TAXONOMY_LABELS, type TChoiceQuestionSchema, type TDurationSchema, type TInputQuestionSchema, type TJobSchema, type TPostSchema, type TQuestionSchema, type TReadAndAcknowledgeQuestionSchema, type TUserIdAndCreatedAtSchema, TermsAcceptedSchema, UserAdditionalInfoSchema, UserCertificationSchema, UserCompletenessSchema, UserCoordinatorProfileSchema, UserDetailType, UserGeneralDetailSchema, UserIdAndCreatedAtSchema, UserInterestSchema, UserJobPreferencesSchema, UserLanguageSchema, type UserProfileOverview, UserProfileVisibility, UserProjectSchema, UserRecruiterProfileSchema, UserRole, UserSchema, UserSkillSchema, UserStatus, WorkExperienceSchema, WorkMode, categoryOfIndustry, categoryOfSubCategory, dateString, deriveIndustry, getSchemaByQuestion, industriesOfCategory, industryOfSubCategory, subCategoriesOfIndustry };
3020
+ export { AnswerChoiceType, ApplicationReceivePreference, CampaignSchema, CampaignStatus, ChoiceQuestionSchema, Common, CompensationType, CompletenessScoreSchema, ContactTypes, CoordinatorPageLinkSchema, DISPLAY_DATE_FORMAT, DISPLAY_DATE_FORMAT_SHORT, DateStringSchema, DbDefaultSchema, DefaultPaginatedResponse, DefaultPaginationOptions, DefaultUserRoles, DurationSchema, EMPTY_STRING, EducationLevel, EducationSchema, EmployeeCount, EmploymentType, ExperienceLevel, FEEDBACK_MESSAGE_MAX, FeedbackSchema, FeedbackStatus, FeedbackType, GeneraDetailFields, GroupManagedBy, GroupMembershipSchema, GroupMembershipStatus, GroupSchema, GroupStatus, GroupTranslationSchema, GroupVisibility, InputQuestionSchema, JOB_TAXONOMY, JobAlertFrequency, JobCategory, JobIndustry, JobSchema, JobSearchUrgency, JobStatus, JobSubCategory, ListFilterSchema, LocationSchema, MIN_SALARY_LOWER_BOUND, MIN_SALARY_UPPER_BOUND, MailingListSchema, NotificationChannel, PREFILL_MIRRORED_KEYS, PRIVATE_PROFILE_FIELDS, PageSchema, PageStatus, PageType, type PaginatedResponse, PaginationSchema, PostSchema, PostStatus, type PrefillMirroredKey, type PrivateProfileField, ProficiencyLevel, QuestionSchema, QuestionType, ReadAndAcknowledgeQuestionSchema, RecruiterPageLinkSchema, ReferralSource, ReportReason, ReportSchema, ResourceType, SITEMAP_FORMAT, SYSTEM_DATE_FORMAT, SavedSearchSchema, SavedSearchStatus, SignupContextSource, SkillSchema, SocialAccount, SocialAccountSchema, StudentCompletenessSchema, StudyType, SupportedAnswerChoiceTypes, SupportedApplicationReceivePreferences, SupportedCampaignStatuses, SupportedCompensationTypes, SupportedContactTypes, SupportedEducationLevels, SupportedEmployeeCounts, SupportedEmploymentTypes, SupportedExperienceLevels, SupportedFeedbackStatuses, SupportedFeedbackTypes, SupportedJobAlertFrequencies, SupportedJobCategories, SupportedJobIndustries, SupportedJobSearchUrgencies, SupportedJobStatuses, SupportedJobSubCategories, SupportedNotificationChannels, SupportedPageStatuses, SupportedPageTypes, SupportedPostStatuses, SupportedProficiencyLevels, SupportedQuestionTypes, SupportedReferralSources, SupportedReportReasons, SupportedResourceTypes, SupportedSalaryCurrencies, type SupportedSalaryCurrency, SupportedSavedSearchStatuses, SupportedSignupContextSources, SupportedSocialAccounts, SupportedStudyTypes, SupportedUserProfileVisibilities, SupportedUserRoles, SupportedUserStatuses, SupportedWorkModes, TAXONOMY_LABELS, type TChoiceQuestionSchema, type TDurationSchema, type TInputQuestionSchema, type TJobSchema, type TPostSchema, type TQuestionSchema, type TReadAndAcknowledgeQuestionSchema, type TUserIdAndCreatedAtSchema, TermsAcceptedSchema, UserAdditionalInfoSchema, UserCertificationSchema, UserCompletenessSchema, UserCoordinatorProfileSchema, UserDetailType, UserGeneralDetailSchema, UserIdAndCreatedAtSchema, UserInterestSchema, UserJobPreferencesSchema, UserLanguageSchema, type UserProfileOverview, UserProfileVisibility, UserProjectSchema, UserRecruiterProfileSchema, UserRole, UserSchema, UserSkillSchema, UserStatus, WorkExperienceSchema, WorkMode, categoryOfIndustry, categoryOfSubCategory, dateString, deriveIndustry, getSchemaByQuestion, industriesOfCategory, industryOfSubCategory, subCategoriesOfIndustry };
package/dist/index.js CHANGED
@@ -1935,6 +1935,13 @@ var UserProfileVisibility = /* @__PURE__ */ ((UserProfileVisibility2) => {
1935
1935
  var SupportedUserProfileVisibilities = Object.values(
1936
1936
  UserProfileVisibility
1937
1937
  );
1938
+ var SignupContextSource = /* @__PURE__ */ ((SignupContextSource2) => {
1939
+ SignupContextSource2["CfHeader"] = "cf-header";
1940
+ SignupContextSource2["Location"] = "location";
1941
+ SignupContextSource2["Default"] = "default";
1942
+ return SignupContextSource2;
1943
+ })(SignupContextSource || {});
1944
+ var SupportedSignupContextSources = Object.values(SignupContextSource);
1938
1945
  var UserDetailType = /* @__PURE__ */ ((UserDetailType2) => {
1939
1946
  UserDetailType2["Overview"] = "overview";
1940
1947
  UserDetailType2["AdditionalInfo"] = "additionalInfo";
@@ -2253,7 +2260,7 @@ var FeedbackSchema = object22({
2253
2260
  }).label("Feedback Schema");
2254
2261
 
2255
2262
  // src/user/user.schema.ts
2256
- import { array as array14, boolean as boolean15, number as number11, object as object34, string as string32 } from "yup";
2263
+ import { array as array14, boolean as boolean15, number as number12, object as object34, string as string32 } from "yup";
2257
2264
 
2258
2265
  // src/user/work-experience.schema.ts
2259
2266
  import { boolean as boolean12, object as object23, string as string20 } from "yup";
@@ -2342,7 +2349,7 @@ var UserAdditionalInfoSchema = object29({
2342
2349
  }).noUnknown().strict().label("User Additional Info");
2343
2350
 
2344
2351
  // src/user/general-detail.schema.ts
2345
- import { object as object30, string as string28 } from "yup";
2352
+ import { number as number10, object as object30, string as string28 } from "yup";
2346
2353
  var UserGeneralDetailSchema = object30({
2347
2354
  id: string28().optional().label("ID"),
2348
2355
  name: object30().shape({
@@ -2358,15 +2365,33 @@ var UserGeneralDetailSchema = object30({
2358
2365
  mobileVerified: string28().nullable().optional().label("Mobile Verified"),
2359
2366
  experienceLevel: string28().oneOf(SupportedExperienceLevels).required().label("Experience level"),
2360
2367
  location: LocationSchema.required().label("Location"),
2361
- region: object30().shape({
2362
- country: string28().trim().required().label("Region Country"),
2363
- lang: string28().trim().required().label("Region Language")
2364
- }).optional().default(void 0).label("Region"),
2368
+ /**
2369
+ * Where the account was created, captured once and never rewritten.
2370
+ *
2371
+ * Server-owned: listed in user-service's SERVER_CONTROLLED_FIELDS and absent
2372
+ * from every user-writable schema, so a client cannot set or amend it. That
2373
+ * immutability is the point — this answers "which market/jurisdiction governed
2374
+ * this user at sign-up?", which a field the user can PATCH could never do.
2375
+ *
2376
+ * `source` records HOW the value was obtained, because most of it is inferred
2377
+ * rather than measured, and an audit has to be able to tell those apart:
2378
+ * - `cf-header` measured from Cloudflare's `cf-ipcountry` at sign-up
2379
+ * - `location` derived from the user's self-entered profile location
2380
+ * - `default` assumed (no signal available); presumption, not evidence
2381
+ */
2382
+ signupContext: object30().shape({
2383
+ // ISO 3166-1 alpha-2, uppercase — the shape `cf-ipcountry` returns, so
2384
+ // measured and inferred values are directly comparable.
2385
+ country: string28().trim().uppercase().length(2).required().label("Signup Country"),
2386
+ locale: string28().trim().required().label("Signup Locale"),
2387
+ source: string28().oneOf(SupportedSignupContextSources).required().label("Signup Context Source"),
2388
+ at: number10().required().label("Signup Context At")
2389
+ }).optional().default(void 0).label("Signup Context"),
2365
2390
  profileVisibility: string28().oneOf(SupportedUserProfileVisibilities).default("public" /* Public */).label("Profile Visibility")
2366
2391
  }).noUnknown().strict().label("General Detail");
2367
2392
 
2368
2393
  // src/user/user-job-preferences.schema.ts
2369
- import { array as array11, boolean as boolean14, date as date2, number as number10, object as object31, string as string29 } from "yup";
2394
+ import { array as array11, boolean as boolean14, date as date2, number as number11, object as object31, string as string29 } from "yup";
2370
2395
  var UserJobPreferencesSchema = object31({
2371
2396
  /**
2372
2397
  * Whether the job seeker is openly signalling availability. Surfaces the
@@ -2412,7 +2437,7 @@ var UserJobPreferencesSchema = object31({
2412
2437
  * Defaults to 0 ("any salary") rather than being optional - every job-seeker
2413
2438
  * has a floor, even if it's zero.
2414
2439
  */
2415
- minSalary: number10().integer().min(MIN_SALARY_LOWER_BOUND).max(MIN_SALARY_UPPER_BOUND).default(0).required().label("Minimum Salary"),
2440
+ minSalary: number11().integer().min(MIN_SALARY_LOWER_BOUND).max(MIN_SALARY_UPPER_BOUND).default(0).required().label("Minimum Salary"),
2416
2441
  minSalaryCurrency: string29().oneOf(SupportedSalaryCurrencies).default("USD").required().label("Minimum Salary Currency"),
2417
2442
  /**
2418
2443
  * Marketing-attribution capture from the final onboarding step.
@@ -2439,7 +2464,7 @@ var UserJobPreferencesSchema = object31({
2439
2464
  id: string29().required().label("ID"),
2440
2465
  url: string29().required().label("Resume URL"),
2441
2466
  filename: string29().trim().max(255).optional().label("Filename"),
2442
- sizeBytes: number10().integer().min(0).optional().label("Size (bytes)"),
2467
+ sizeBytes: number11().integer().min(0).optional().label("Size (bytes)"),
2443
2468
  mimeType: string29().trim().max(120).optional().label("MIME Type"),
2444
2469
  uploadedAt: date2().required().label("Uploaded At"),
2445
2470
  isPrimary: boolean14().default(false).label("Is Primary")
@@ -2569,7 +2594,7 @@ var UserSchema = object34({
2569
2594
  marketingConsent: object34({
2570
2595
  subscribed: boolean15().default(false).label("Subscribed to marketing email"),
2571
2596
  /** Epoch ms of the last consent change (opt-in or opt-out). */
2572
- updatedAt: number11().optional().label("Consent updated at"),
2597
+ updatedAt: number12().optional().label("Consent updated at"),
2573
2598
  /** Where the consent change originated, e.g. `onboarding`, `unsubscribe`, `settings`. */
2574
2599
  source: string32().optional().label("Consent source"),
2575
2600
  /**
@@ -2609,7 +2634,7 @@ var UserSchema = object34({
2609
2634
  * Only generated for public profiles with sufficient completeness (≥60%).
2610
2635
  */
2611
2636
  embedding: object34({
2612
- vector: array14(number11().required()).optional().label("Embedding vector"),
2637
+ vector: array14(number12().required()).optional().label("Embedding vector"),
2613
2638
  model: string32().optional().label("Embedding model")
2614
2639
  }).nullable().optional().label("Embedding")
2615
2640
  }).concat(UserGeneralDetailSchema).concat(UserJobPreferencesSchema).concat(UserRecruiterProfileSchema).concat(UserCoordinatorProfileSchema).noUnknown().strict().label("User Schema");
@@ -2710,6 +2735,7 @@ export {
2710
2735
  SYSTEM_DATE_FORMAT,
2711
2736
  SavedSearchSchema,
2712
2737
  SavedSearchStatus,
2738
+ SignupContextSource,
2713
2739
  SkillSchema,
2714
2740
  SocialAccount,
2715
2741
  SocialAccountSchema,
@@ -2743,6 +2769,7 @@ export {
2743
2769
  SupportedResourceTypes,
2744
2770
  SupportedSalaryCurrencies,
2745
2771
  SupportedSavedSearchStatuses,
2772
+ SupportedSignupContextSources,
2746
2773
  SupportedSocialAccounts,
2747
2774
  SupportedStudyTypes,
2748
2775
  SupportedUserProfileVisibilities,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thejob/schema",
3
- "version": "2.1.4",
3
+ "version": "2.1.5",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -1,7 +1,8 @@
1
- import { object, string } from "yup";
1
+ import { number, object, string } from "yup";
2
2
  import { SupportedExperienceLevels } from "../common/common.constant.js";
3
3
  import { LocationSchema } from "../location/location.schema.js";
4
4
  import {
5
+ SupportedSignupContextSources,
5
6
  SupportedUserProfileVisibilities,
6
7
  UserProfileVisibility,
7
8
  } from "./user.constant.js";
@@ -30,14 +31,35 @@ export const UserGeneralDetailSchema = object({
30
31
  .required()
31
32
  .label("Experience level"),
32
33
  location: LocationSchema.required().label("Location"),
33
- region: object()
34
+ /**
35
+ * Where the account was created, captured once and never rewritten.
36
+ *
37
+ * Server-owned: listed in user-service's SERVER_CONTROLLED_FIELDS and absent
38
+ * from every user-writable schema, so a client cannot set or amend it. That
39
+ * immutability is the point — this answers "which market/jurisdiction governed
40
+ * this user at sign-up?", which a field the user can PATCH could never do.
41
+ *
42
+ * `source` records HOW the value was obtained, because most of it is inferred
43
+ * rather than measured, and an audit has to be able to tell those apart:
44
+ * - `cf-header` measured from Cloudflare's `cf-ipcountry` at sign-up
45
+ * - `location` derived from the user's self-entered profile location
46
+ * - `default` assumed (no signal available); presumption, not evidence
47
+ */
48
+ signupContext: object()
34
49
  .shape({
35
- country: string().trim().required().label("Region Country"),
36
- lang: string().trim().required().label("Region Language"),
50
+ // ISO 3166-1 alpha-2, uppercase — the shape `cf-ipcountry` returns, so
51
+ // measured and inferred values are directly comparable.
52
+ country: string().trim().uppercase().length(2).required().label("Signup Country"),
53
+ locale: string().trim().required().label("Signup Locale"),
54
+ source: string()
55
+ .oneOf(SupportedSignupContextSources)
56
+ .required()
57
+ .label("Signup Context Source"),
58
+ at: number().required().label("Signup Context At"),
37
59
  })
38
60
  .optional()
39
61
  .default(undefined)
40
- .label("Region"),
62
+ .label("Signup Context"),
41
63
  profileVisibility: string()
42
64
  .oneOf(SupportedUserProfileVisibilities)
43
65
  .default(UserProfileVisibility.Public)
@@ -34,6 +34,26 @@ export const SupportedUserProfileVisibilities = Object.values(
34
34
  UserProfileVisibility,
35
35
  );
36
36
 
37
+ /**
38
+ * How a user's `signupContext` was obtained. Stored on the document, so these
39
+ * strings are data: lowercase, and a rename is a migration.
40
+ *
41
+ * The distinction is deliberate. Only `CfHeader` is measured; the other two are
42
+ * inferred, and `Default` is a bare presumption with no signal behind it at all.
43
+ * Collapsing them would make an assumed country indistinguishable from an
44
+ * observed one, which is precisely what a compliance record must not do.
45
+ */
46
+ export enum SignupContextSource {
47
+ /** Measured from Cloudflare's `cf-ipcountry` header at sign-up. */
48
+ CfHeader = "cf-header",
49
+ /** Derived from the user's self-entered profile location (backfill). */
50
+ Location = "location",
51
+ /** Assumed; no signal was available. A presumption, not evidence. */
52
+ Default = "default",
53
+ }
54
+
55
+ export const SupportedSignupContextSources = Object.values(SignupContextSource);
56
+
37
57
  export enum UserDetailType {
38
58
  Overview = "overview",
39
59
  AdditionalInfo = "additionalInfo",