@cavuno/board 4.58.0 → 4.59.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/{_spec-DIzdOWz7.d.mts → _spec-DZV8ME1P.d.mts} +11 -5
  3. package/dist/{_spec-DIzdOWz7.d.ts → _spec-DZV8ME1P.d.ts} +11 -5
  4. package/dist/analytics.js +38 -1
  5. package/dist/analytics.mjs +38 -1
  6. package/dist/bin.mjs +27 -14
  7. package/dist/{blog-DgbGOmzW.d.mts → blog-BfVuJQvO.d.mts} +1 -1
  8. package/dist/{blog-CyvB9C6W.d.ts → blog-CLVfmknV.d.ts} +1 -1
  9. package/dist/{board-leXc6_BM.d.ts → board-Cl6cfS-a.d.ts} +2 -2
  10. package/dist/{board-D3ByAGAb.d.mts → board-gmV8MoLW.d.mts} +2 -2
  11. package/dist/{breadcrumbs-zYG4xzpC.d.mts → breadcrumbs-BOM71gXT.d.mts} +2 -2
  12. package/dist/{breadcrumbs-aCKRBNWX.d.ts → breadcrumbs-dTn6GsKo.d.ts} +2 -2
  13. package/dist/browser/cavuno-board.global.min.js +1 -1
  14. package/dist/{chunk-GUXSUQCP.mjs → chunk-LO7D3AIX.mjs} +8 -10
  15. package/dist/doctor.js +27 -14
  16. package/dist/doctor.mjs +27 -14
  17. package/dist/filters.d.mts +2 -2
  18. package/dist/filters.d.ts +2 -2
  19. package/dist/format.d.mts +12 -4
  20. package/dist/format.d.ts +12 -4
  21. package/dist/format.js +6 -1
  22. package/dist/format.mjs +6 -1
  23. package/dist/index.d.mts +15 -11
  24. package/dist/index.d.ts +15 -11
  25. package/dist/index.js +47 -10
  26. package/dist/index.mjs +47 -10
  27. package/dist/{jobs-nA2K33Nu.d.mts → jobs-DfMyFAYs.d.mts} +1 -1
  28. package/dist/{jobs-DodvFXH2.d.ts → jobs-Di0L5sM1.d.ts} +1 -1
  29. package/dist/route-contract.d.mts +3 -4
  30. package/dist/route-contract.d.ts +3 -4
  31. package/dist/route-contract.js +8 -10
  32. package/dist/route-contract.mjs +1 -1
  33. package/dist/{search-8kX6OW03.d.mts → search-1pxkvj_I.d.mts} +1 -1
  34. package/dist/{search-C1XNGSm6.d.ts → search-BPnMYVa7.d.ts} +1 -1
  35. package/dist/{seo-Bn5gQY8t.d.mts → seo-CJtLrGD_.d.mts} +1 -1
  36. package/dist/{seo-DFAwxGej.d.ts → seo-DFPj-7Rl.d.ts} +1 -1
  37. package/dist/seo-files.d.mts +2 -2
  38. package/dist/seo-files.d.ts +2 -2
  39. package/dist/seo.d.mts +6 -6
  40. package/dist/seo.d.ts +6 -6
  41. package/dist/server.d.mts +6 -6
  42. package/dist/server.d.ts +6 -6
  43. package/dist/sitemap.d.mts +6 -6
  44. package/dist/sitemap.d.ts +6 -6
  45. package/dist/suggest.d.mts +2 -2
  46. package/dist/suggest.d.ts +2 -2
  47. package/dist/well-known.mjs +1 -1
  48. package/package.json +1 -1
  49. package/skills/cavuno-board-auth/SKILL.md +7 -0
  50. package/skills/cavuno-board-format/SKILL.md +5 -1
  51. package/skills/manifest.json +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,33 @@
3
3
  This changelog records changes that affect Board API compatibility, exported
4
4
  types, runtime behavior, or supported integration patterns.
5
5
 
6
+ ## 4.59.1 — 2026-11-02
7
+
8
+ - The board context's `contact` carries `tiktokUrl` and `youtubeUrl` next to
9
+ the other social links: a normalized profile URL, or `null`.
10
+ - Operator settings take `companyTiktokUrl` and `companyYoutubeUrl`, a handle
11
+ or profile URL stored as the canonical profile URL.
12
+ - Digital products: `isPublic: true` on a product with no ready file or link
13
+ is kept as a request. The product reports `digitalProduct.listingPending`
14
+ and is listed automatically once its first file or link is ready.
15
+ - On a board whose talent directory is for employers only, a profile shown to
16
+ signed-in members only is returned to approved employers and its owner, not
17
+ to every signed-in user.
18
+ - `talent.retrieve` returns the experience, education, skills and languages of a
19
+ members-only profile to viewers allowed to see it.
20
+
21
+ ## 4.59.0 — 2026-11-01
22
+
23
+ - Security and reliability improvements.
24
+ - `getOAuthAuthorizationUrl` accepts `browserBindingHash` and `exchangeOAuth`
25
+ accepts `browserBinding` for Google and LinkedIn sign-in. Browsers get this
26
+ automatically; server callers pass the SHA-256 hex of a secret they keep in
27
+ an HttpOnly cookie, then the secret itself. Optional today, required later.
28
+ - `resolveCustomFieldDisplay` returns `kind: 'rich_text'` for rich text
29
+ fields, with sanitized HTML as `value`.
30
+ - New operator operations: `getApplication`, `listJobApplications`,
31
+ `deleteApplication`, `deleteCustomObjectType` and `deleteInboxTicket`.
32
+
6
33
  ## 4.58.0 — 2026-10-30
7
34
 
8
35
  - `features.impressum` on the board context is deprecated. Publish your
@@ -252,7 +252,7 @@ interface components {
252
252
  };
253
253
  BoardAuthOAuthExchangeBody: {
254
254
  token: string;
255
- /** @description For SSO, the random secret established in the initiating browser before requesting authorization. Required for SSO tokens; never read it from a completion URL. */
255
+ /** @description The random secret established in the initiating browser before requesting authorization. Required for SSO tokens and for Google or LinkedIn tokens started with a browserBindingHash; it will become required for all Google and LinkedIn tokens. Never read it from a completion URL. Browser SDK supplies the retained secret automatically. */
256
256
  browserBinding?: string;
257
257
  };
258
258
  BoardAuthRefreshBody: {
@@ -1418,7 +1418,7 @@ interface components {
1418
1418
  inOfficeFrequency?: number;
1419
1419
  /** @description Physical office locations associated with the job. Prefer `{locationId}` with an `id` from `GET /locations/search` (what a location picker returns); `{query: "City, Country"}` for free-form text and `{city, country, region?, locality?}` are also accepted and geocoded server-side (a country mismatch returns `400 jobs_unresolvable_location`). A location outside the board's job-form `allowedCountries` returns `400 jobs_constraint_violation`. */
1420
1420
  officeLocations?: components["schemas"]["EmployerJobOfficeLocationInput"][];
1421
- /** @description Collection-reference selections keyed by the configured job collection field key. Values are arrays of record IDs, including for single-select fields. On PATCH omitted keys are preserved; send null or an empty array to clear a field. */
1421
+ /** @description Collection-reference selections keyed by the configured job collection field key. Values are arrays of record IDs, including for single-select fields. On PATCH omitted keys are preserved; send null or an empty array to clear a field. Keys for fields whose collection is archived are ignored, and their stored selections are kept. */
1422
1422
  collectionValues?: {
1423
1423
  [key: string]: string[] | unknown;
1424
1424
  };
@@ -1594,7 +1594,7 @@ interface components {
1594
1594
  customFieldValues: {
1595
1595
  [key: string]: string | string[] | boolean | number;
1596
1596
  };
1597
- /** @description Collection record IDs keyed by configured job collection field. */
1597
+ /** @description Collection record IDs keyed by configured job collection field. Fields whose collection is archived are omitted. */
1598
1598
  collectionValues: {
1599
1599
  [key: string]: string[];
1600
1600
  };
@@ -1733,7 +1733,7 @@ interface components {
1733
1733
  inOfficeFrequency?: number;
1734
1734
  /** @description Physical office locations associated with the job. Prefer `{locationId}` with an `id` from `GET /locations/search` (what a location picker returns); `{query: "City, Country"}` for free-form text and `{city, country, region?, locality?}` are also accepted and geocoded server-side (a country mismatch returns `400 jobs_unresolvable_location`). A location outside the board's job-form `allowedCountries` returns `400 jobs_constraint_violation`. */
1735
1735
  officeLocations?: components["schemas"]["EmployerJobOfficeLocationInput"][];
1736
- /** @description Collection-reference selections keyed by the configured job collection field key. Values are arrays of record IDs, including for single-select fields. On PATCH omitted keys are preserved; send null or an empty array to clear a field. */
1736
+ /** @description Collection-reference selections keyed by the configured job collection field key. Values are arrays of record IDs, including for single-select fields. On PATCH omitted keys are preserved; send null or an empty array to clear a field. Keys for fields whose collection is archived are ignored, and their stored selections are kept. */
1737
1737
  collectionValues?: {
1738
1738
  [key: string]: string[] | unknown;
1739
1739
  };
@@ -2543,6 +2543,7 @@ interface components {
2543
2543
  }[];
2544
2544
  };
2545
2545
  ProfileObjectReferencesResult: {
2546
+ /** @description The reference fields on this profile. Fields whose collection is archived are omitted, along with their selections. */
2546
2547
  definitions: components["schemas"]["ProfileObjectReferenceDefinition"][];
2547
2548
  selections: components["schemas"]["ResolvedProfileObjectReference"][];
2548
2549
  };
@@ -2722,7 +2723,7 @@ interface components {
2722
2723
  customFields: {
2723
2724
  /** @description This model's custom-field definitions in display order. Empty when the board defines none for the model. */
2724
2725
  job: components["schemas"]["CustomFieldDefinition"][];
2725
- /** @description Collection-reference job field definitions in display order. Fetch choices from /boards/{identifier}/job-fields/{fieldKey}/choices. */
2726
+ /** @description Collection-reference job field definitions in display order. Fetch choices from /boards/{identifier}/job-fields/{fieldKey}/choices. Fields whose collection is archived are omitted. */
2726
2727
  jobCollections: components["schemas"]["JobCollectionFieldDefinition"][];
2727
2728
  };
2728
2729
  /** @description Built-in job-form field configuration from Settings → Job form (custom fields live on `customFields.job`). The platform ENFORCES these constraints when a job is created — a violation is a 400 — so a posting form that ignores them offers options the server will reject and the employer only finds out on submit. */
@@ -2787,6 +2788,10 @@ interface components {
2787
2788
  linkedinUrl: string | null;
2788
2789
  /** @description Instagram profile URL. Null when unset or invalid. */
2789
2790
  instagramUrl: string | null;
2791
+ /** @description TikTok profile URL (`https://www.tiktok.com/@handle`). Null when unset or invalid. */
2792
+ tiktokUrl: string | null;
2793
+ /** @description YouTube channel URL (`https://www.youtube.com/...`). Null when unset or invalid. */
2794
+ youtubeUrl: string | null;
2790
2795
  };
2791
2796
  };
2792
2797
  PublicCompaniesSearchBody: {
@@ -3521,6 +3526,7 @@ interface components {
3521
3526
  orderedStageIds: string[];
3522
3527
  };
3523
3528
  ReplaceProfileObjectReferenceSelectionsBody: {
3529
+ /** @description Replaces the stored selections. Selections for fields whose collection is archived are ignored, and their stored selections are kept. */
3524
3530
  selections: components["schemas"]["ProfileObjectReferenceSelectionWrite"][];
3525
3531
  };
3526
3532
  ReplyBody: {
@@ -252,7 +252,7 @@ interface components {
252
252
  };
253
253
  BoardAuthOAuthExchangeBody: {
254
254
  token: string;
255
- /** @description For SSO, the random secret established in the initiating browser before requesting authorization. Required for SSO tokens; never read it from a completion URL. */
255
+ /** @description The random secret established in the initiating browser before requesting authorization. Required for SSO tokens and for Google or LinkedIn tokens started with a browserBindingHash; it will become required for all Google and LinkedIn tokens. Never read it from a completion URL. Browser SDK supplies the retained secret automatically. */
256
256
  browserBinding?: string;
257
257
  };
258
258
  BoardAuthRefreshBody: {
@@ -1418,7 +1418,7 @@ interface components {
1418
1418
  inOfficeFrequency?: number;
1419
1419
  /** @description Physical office locations associated with the job. Prefer `{locationId}` with an `id` from `GET /locations/search` (what a location picker returns); `{query: "City, Country"}` for free-form text and `{city, country, region?, locality?}` are also accepted and geocoded server-side (a country mismatch returns `400 jobs_unresolvable_location`). A location outside the board's job-form `allowedCountries` returns `400 jobs_constraint_violation`. */
1420
1420
  officeLocations?: components["schemas"]["EmployerJobOfficeLocationInput"][];
1421
- /** @description Collection-reference selections keyed by the configured job collection field key. Values are arrays of record IDs, including for single-select fields. On PATCH omitted keys are preserved; send null or an empty array to clear a field. */
1421
+ /** @description Collection-reference selections keyed by the configured job collection field key. Values are arrays of record IDs, including for single-select fields. On PATCH omitted keys are preserved; send null or an empty array to clear a field. Keys for fields whose collection is archived are ignored, and their stored selections are kept. */
1422
1422
  collectionValues?: {
1423
1423
  [key: string]: string[] | unknown;
1424
1424
  };
@@ -1594,7 +1594,7 @@ interface components {
1594
1594
  customFieldValues: {
1595
1595
  [key: string]: string | string[] | boolean | number;
1596
1596
  };
1597
- /** @description Collection record IDs keyed by configured job collection field. */
1597
+ /** @description Collection record IDs keyed by configured job collection field. Fields whose collection is archived are omitted. */
1598
1598
  collectionValues: {
1599
1599
  [key: string]: string[];
1600
1600
  };
@@ -1733,7 +1733,7 @@ interface components {
1733
1733
  inOfficeFrequency?: number;
1734
1734
  /** @description Physical office locations associated with the job. Prefer `{locationId}` with an `id` from `GET /locations/search` (what a location picker returns); `{query: "City, Country"}` for free-form text and `{city, country, region?, locality?}` are also accepted and geocoded server-side (a country mismatch returns `400 jobs_unresolvable_location`). A location outside the board's job-form `allowedCountries` returns `400 jobs_constraint_violation`. */
1735
1735
  officeLocations?: components["schemas"]["EmployerJobOfficeLocationInput"][];
1736
- /** @description Collection-reference selections keyed by the configured job collection field key. Values are arrays of record IDs, including for single-select fields. On PATCH omitted keys are preserved; send null or an empty array to clear a field. */
1736
+ /** @description Collection-reference selections keyed by the configured job collection field key. Values are arrays of record IDs, including for single-select fields. On PATCH omitted keys are preserved; send null or an empty array to clear a field. Keys for fields whose collection is archived are ignored, and their stored selections are kept. */
1737
1737
  collectionValues?: {
1738
1738
  [key: string]: string[] | unknown;
1739
1739
  };
@@ -2543,6 +2543,7 @@ interface components {
2543
2543
  }[];
2544
2544
  };
2545
2545
  ProfileObjectReferencesResult: {
2546
+ /** @description The reference fields on this profile. Fields whose collection is archived are omitted, along with their selections. */
2546
2547
  definitions: components["schemas"]["ProfileObjectReferenceDefinition"][];
2547
2548
  selections: components["schemas"]["ResolvedProfileObjectReference"][];
2548
2549
  };
@@ -2722,7 +2723,7 @@ interface components {
2722
2723
  customFields: {
2723
2724
  /** @description This model's custom-field definitions in display order. Empty when the board defines none for the model. */
2724
2725
  job: components["schemas"]["CustomFieldDefinition"][];
2725
- /** @description Collection-reference job field definitions in display order. Fetch choices from /boards/{identifier}/job-fields/{fieldKey}/choices. */
2726
+ /** @description Collection-reference job field definitions in display order. Fetch choices from /boards/{identifier}/job-fields/{fieldKey}/choices. Fields whose collection is archived are omitted. */
2726
2727
  jobCollections: components["schemas"]["JobCollectionFieldDefinition"][];
2727
2728
  };
2728
2729
  /** @description Built-in job-form field configuration from Settings → Job form (custom fields live on `customFields.job`). The platform ENFORCES these constraints when a job is created — a violation is a 400 — so a posting form that ignores them offers options the server will reject and the employer only finds out on submit. */
@@ -2787,6 +2788,10 @@ interface components {
2787
2788
  linkedinUrl: string | null;
2788
2789
  /** @description Instagram profile URL. Null when unset or invalid. */
2789
2790
  instagramUrl: string | null;
2791
+ /** @description TikTok profile URL (`https://www.tiktok.com/@handle`). Null when unset or invalid. */
2792
+ tiktokUrl: string | null;
2793
+ /** @description YouTube channel URL (`https://www.youtube.com/...`). Null when unset or invalid. */
2794
+ youtubeUrl: string | null;
2790
2795
  };
2791
2796
  };
2792
2797
  PublicCompaniesSearchBody: {
@@ -3521,6 +3526,7 @@ interface components {
3521
3526
  orderedStageIds: string[];
3522
3527
  };
3523
3528
  ReplaceProfileObjectReferenceSelectionsBody: {
3529
+ /** @description Replaces the stored selections. Selections for fields whose collection is archived are ignored, and their stored selections are kept. */
3524
3530
  selections: components["schemas"]["ProfileObjectReferenceSelectionWrite"][];
3525
3531
  };
3526
3532
  ReplyBody: {
package/dist/analytics.js CHANGED
@@ -69,6 +69,40 @@ function withCors(response) {
69
69
  function corsPreflight() {
70
70
  return withCors(new Response(null, { status: 204 }));
71
71
  }
72
+ var COLLECT_BODY_MAX_BYTES = 64 * 1024;
73
+ async function readCollectBody(request) {
74
+ if (Number(request.headers.get("Content-Length")) > COLLECT_BODY_MAX_BYTES) {
75
+ await request.body?.cancel().catch(() => {
76
+ });
77
+ return null;
78
+ }
79
+ if (!request.body) return new Uint8Array();
80
+ const reader = request.body.getReader();
81
+ const chunks = [];
82
+ let size = 0;
83
+ try {
84
+ for (; ; ) {
85
+ const { done, value } = await reader.read();
86
+ if (done) break;
87
+ size += value.byteLength;
88
+ if (size > COLLECT_BODY_MAX_BYTES) {
89
+ await reader.cancel().catch(() => {
90
+ });
91
+ return null;
92
+ }
93
+ chunks.push(value);
94
+ }
95
+ } finally {
96
+ reader.releaseLock();
97
+ }
98
+ const bytes = new Uint8Array(size);
99
+ let offset = 0;
100
+ for (const chunk of chunks) {
101
+ bytes.set(chunk, offset);
102
+ offset += chunk.byteLength;
103
+ }
104
+ return bytes;
105
+ }
72
106
  async function proxyCollect(request, targetUrl) {
73
107
  if (request.method === "OPTIONS") {
74
108
  return corsPreflight();
@@ -85,7 +119,10 @@ async function proxyCollect(request, targetUrl) {
85
119
  if (accept) headers.set("Accept", accept);
86
120
  const target = new URL(targetUrl);
87
121
  target.search = new URL(request.url).search;
88
- const body = await request.arrayBuffer();
122
+ const body = await readCollectBody(request);
123
+ if (body === null) {
124
+ return withCors(new Response(null, { status: 413 }));
125
+ }
89
126
  const upstream = await fetch(target.toString(), {
90
127
  method: "POST",
91
128
  headers,
@@ -32,6 +32,40 @@ function withCors(response) {
32
32
  function corsPreflight() {
33
33
  return withCors(new Response(null, { status: 204 }));
34
34
  }
35
+ var COLLECT_BODY_MAX_BYTES = 64 * 1024;
36
+ async function readCollectBody(request) {
37
+ if (Number(request.headers.get("Content-Length")) > COLLECT_BODY_MAX_BYTES) {
38
+ await request.body?.cancel().catch(() => {
39
+ });
40
+ return null;
41
+ }
42
+ if (!request.body) return new Uint8Array();
43
+ const reader = request.body.getReader();
44
+ const chunks = [];
45
+ let size = 0;
46
+ try {
47
+ for (; ; ) {
48
+ const { done, value } = await reader.read();
49
+ if (done) break;
50
+ size += value.byteLength;
51
+ if (size > COLLECT_BODY_MAX_BYTES) {
52
+ await reader.cancel().catch(() => {
53
+ });
54
+ return null;
55
+ }
56
+ chunks.push(value);
57
+ }
58
+ } finally {
59
+ reader.releaseLock();
60
+ }
61
+ const bytes = new Uint8Array(size);
62
+ let offset = 0;
63
+ for (const chunk of chunks) {
64
+ bytes.set(chunk, offset);
65
+ offset += chunk.byteLength;
66
+ }
67
+ return bytes;
68
+ }
35
69
  async function proxyCollect(request, targetUrl) {
36
70
  if (request.method === "OPTIONS") {
37
71
  return corsPreflight();
@@ -48,7 +82,10 @@ async function proxyCollect(request, targetUrl) {
48
82
  if (accept) headers.set("Accept", accept);
49
83
  const target = new URL(targetUrl);
50
84
  target.search = new URL(request.url).search;
51
- const body = await request.arrayBuffer();
85
+ const body = await readCollectBody(request);
86
+ if (body === null) {
87
+ return withCors(new Response(null, { status: 413 }));
88
+ }
52
89
  const upstream = await fetch(target.toString(), {
53
90
  method: "POST",
54
91
  headers,
package/dist/bin.mjs CHANGED
@@ -259,7 +259,7 @@ function splitSegments(path) {
259
259
  if (withoutTrailing === "") return [];
260
260
  return withoutTrailing.split("/");
261
261
  }
262
- var PREFIXED_PARAM_RE = /^([^:]+):([A-Za-z_][A-Za-z0-9_]*)(.*)$/;
262
+ var PARAM_SEGMENT_RE = /^([^:]*):([A-Za-z_][A-Za-z0-9_]*)([A-Za-z0-9_.@-]*)$/;
263
263
  function matchPathToTemplate(template, pathname) {
264
264
  const templateSegs = splitSegments(template);
265
265
  const pathSegs = splitSegments(pathname);
@@ -272,16 +272,14 @@ function matchPathToTemplate(template, pathname) {
272
272
  const pSeg = pathSegs[i];
273
273
  let name = null;
274
274
  let value = pSeg;
275
- if (tSeg.startsWith(":") && tSeg.length > 1) {
275
+ const parameter = PARAM_SEGMENT_RE.exec(tSeg);
276
+ if (parameter) {
277
+ const [, prefix = "", param = "", suffix = ""] = parameter;
278
+ if (!pSeg.startsWith(prefix) || !pSeg.endsWith(suffix)) return null;
279
+ name = param;
280
+ value = pSeg.slice(prefix.length, pSeg.length - suffix.length);
281
+ } else if (tSeg.startsWith(":") && tSeg.length > 1) {
276
282
  name = tSeg.slice(1);
277
- } else {
278
- const prefixed = PREFIXED_PARAM_RE.exec(tSeg);
279
- if (prefixed) {
280
- const [, prefix = "", param = "", suffix = ""] = prefixed;
281
- if (!pSeg.startsWith(prefix) || !pSeg.endsWith(suffix)) return null;
282
- name = param;
283
- value = pSeg.slice(prefix.length, pSeg.length - suffix.length);
284
- }
285
283
  }
286
284
  if (name !== null) {
287
285
  if (value.length === 0) return null;
@@ -8761,7 +8759,8 @@ var LOCALIZED_URLS = record("read.localizedUrls", 2);
8761
8759
  var REDIRECT_OK = /* @__PURE__ */ new Set([
8762
8760
  "employers",
8763
8761
  "alertsManage",
8764
- "alertsConfirm"
8762
+ "alertsConfirm",
8763
+ "mePurchases"
8765
8764
  ]);
8766
8765
  var MAY_BE_UNPUBLISHED = /* @__PURE__ */ new Set([
8767
8766
  "blog",
@@ -9018,10 +9017,18 @@ function scoreBoardIdentity(board, robotsBody, env) {
9018
9017
  }
9019
9018
  function onFrontend(base, locUrl) {
9020
9019
  try {
9021
- const parsed = new URL(locUrl);
9022
- return `${base}${parsed.pathname}${parsed.search}`;
9020
+ const frontend = new URL(base);
9021
+ const parsed = new URL(locUrl, frontend.origin + "/");
9022
+ if (frontend.protocol !== "http:" && frontend.protocol !== "https:" || parsed.protocol !== "http:" && parsed.protocol !== "https:") {
9023
+ return null;
9024
+ }
9025
+ const rebased = new URL(frontend);
9026
+ rebased.pathname = frontend.pathname.replace(/\/$/, "") + parsed.pathname;
9027
+ rebased.search = parsed.search;
9028
+ rebased.hash = "";
9029
+ return rebased.origin === frontend.origin ? rebased.href : null;
9023
9030
  } catch {
9024
- return locUrl;
9031
+ return null;
9025
9032
  }
9026
9033
  }
9027
9034
  async function probeSitemap(fetchImpl, base) {
@@ -9039,6 +9046,9 @@ async function probeSitemap(fetchImpl, base) {
9039
9046
  let urls = doc.urls;
9040
9047
  if (doc.kind === "index") {
9041
9048
  const childUrl = onFrontend(base, urls[0]);
9049
+ if (childUrl === null) {
9050
+ return READ.sitemap("fail", "child sitemap has an invalid HTTP(S) URL");
9051
+ }
9042
9052
  let child = await probe(fetchImpl, childUrl);
9043
9053
  if (!child.ok && (child.status === 503 || child.status === 0)) {
9044
9054
  child = await probe(fetchImpl, childUrl);
@@ -9053,6 +9063,9 @@ async function probeSitemap(fetchImpl, base) {
9053
9063
  urls = childDoc.urls;
9054
9064
  }
9055
9065
  const sampleUrl = onFrontend(base, urls[0]);
9066
+ if (sampleUrl === null) {
9067
+ return READ.sitemap("fail", "sitemap page has an invalid HTTP(S) URL");
9068
+ }
9056
9069
  const sample = await probe(fetchImpl, sampleUrl);
9057
9070
  return sample.ok ? READ.sitemap("pass", `${urls.length} entries; sample page resolves`) : READ.sitemap("fail", `sitemap page ${sampleUrl} \u2192 HTTP ${sample.status}`);
9058
9071
  }
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-DIzdOWz7.mjs';
1
+ import { S as Schemas } from './_spec-DZV8ME1P.mjs';
2
2
 
3
3
  /** Author shape embedded on posts (no `object` discriminator). */
4
4
  type BlogAuthorEmbed = Schemas['PublicBlogAuthorEmbed'];
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-DIzdOWz7.js';
1
+ import { S as Schemas } from './_spec-DZV8ME1P.js';
2
2
 
3
3
  /** Author shape embedded on posts (no `object` discriminator). */
4
4
  type BlogAuthorEmbed = Schemas['PublicBlogAuthorEmbed'];
@@ -1,5 +1,5 @@
1
- import { S as Schemas } from './_spec-DIzdOWz7.js';
2
- import { L as ListEnvelope, v as RelatedSearch, b as CustomFieldFilter, O as ObjectReferenceFilter, a as JobCapability } from './jobs-DodvFXH2.js';
1
+ import { S as Schemas } from './_spec-DZV8ME1P.js';
2
+ import { L as ListEnvelope, v as RelatedSearch, b as CustomFieldFilter, O as ObjectReferenceFilter, a as JobCapability } from './jobs-Di0L5sM1.js';
3
3
 
4
4
  type PublicCompany = Schemas['CompanyPublic'];
5
5
  /**
@@ -1,5 +1,5 @@
1
- import { S as Schemas } from './_spec-DIzdOWz7.mjs';
2
- import { L as ListEnvelope, v as RelatedSearch, b as CustomFieldFilter, O as ObjectReferenceFilter, a as JobCapability } from './jobs-nA2K33Nu.mjs';
1
+ import { S as Schemas } from './_spec-DZV8ME1P.mjs';
2
+ import { L as ListEnvelope, v as RelatedSearch, b as CustomFieldFilter, O as ObjectReferenceFilter, a as JobCapability } from './jobs-DfMyFAYs.mjs';
3
3
 
4
4
  type PublicCompany = Schemas['CompanyPublic'];
5
5
  /**
@@ -1,5 +1,5 @@
1
- import { P as PublicBoard, a as CompanyCategorySalary, b as CompanySalary, L as LocationSalaryDetail, S as SkillSalaryDetail, T as TitleSalaryDetail } from './board-D3ByAGAb.mjs';
2
- import { P as PublicJob } from './jobs-nA2K33Nu.mjs';
1
+ import { P as PublicBoard, a as CompanyCategorySalary, b as CompanySalary, L as LocationSalaryDetail, S as SkillSalaryDetail, T as TitleSalaryDetail } from './board-gmV8MoLW.mjs';
2
+ import { P as PublicJob } from './jobs-DfMyFAYs.mjs';
3
3
 
4
4
  /**
5
5
  * Wire enum for `salaryTimeframe`. Defined here (not re-exported from any
@@ -1,5 +1,5 @@
1
- import { P as PublicBoard, a as CompanyCategorySalary, b as CompanySalary, L as LocationSalaryDetail, S as SkillSalaryDetail, T as TitleSalaryDetail } from './board-leXc6_BM.js';
2
- import { P as PublicJob } from './jobs-DodvFXH2.js';
1
+ import { P as PublicBoard, a as CompanyCategorySalary, b as CompanySalary, L as LocationSalaryDetail, S as SkillSalaryDetail, T as TitleSalaryDetail } from './board-Cl6cfS-a.js';
2
+ import { P as PublicJob } from './jobs-Di0L5sM1.js';
3
3
 
4
4
  /**
5
5
  * Wire enum for `salaryTimeframe`. Defined here (not re-exported from any