Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ stored resume file is Evidence. Extracted jobs, schools, and skills are
Career Facts that point at that file.
_Avoid_: Attachment, proof

**Linked Evidence**:
Evidence captured from a public link, such as a review, article, talk, demo,
press release, social post, or award, and attached to a Role. It may be pinned
to one achievement or event on that Role. The occupant records what they did
and whether the source names them or is about work they contributed to. The
captured copy stays private; the occupant's record is published only when they
mark it public.
_Avoid_: Public mention, press clipping, source

**Career Fact**:
A versioned assertion about the occupant's identity, work, education, skills,
achievements, or preferences, linked to its evidence.
Expand Down
10 changes: 9 additions & 1 deletion core/career-memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ export type Sensitivity = "private" | "restricted" | "public";
export type Evidence = {
id: string;
occupantId: string;
sourceType: "user" | "resume" | "worklog" | "work-map" | "document" | "import" | "message";
sourceType:
| "user"
| "resume"
| "worklog"
| "work-map"
| "document"
| "import"
| "message"
| "link";
sourceRef: string;
title: string;
capturedAt: string;
Expand Down
23 changes: 23 additions & 0 deletions core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ export {
keptOnboardingHistory,
nextOnboardingSourceIndex,
onboardingCheckItems,
onboardingDone,
onboardingExtractSummary,
profileAfterOnboarding,
ONBOARDING_NO_PLACE,
type OnboardingCheckItem,
type OnboardingDone,
type OnboardingLinkAnswer,
type OnboardingPlaceAnswer,
} from "./onboarding-review";
Expand All @@ -34,8 +36,11 @@ export {
holdVerifiedRoles,
parseHistoryEvidence,
queuedResumeDuplicate,
resumesToQueue,
rememberSetAside,
rememberSkills,
reviseSkills,
skillLines,
roleFieldDiffs,
roleTookFile,
type HeldHistory,
Expand Down Expand Up @@ -346,19 +351,31 @@ export {
} from "./map-settings";
export {
EMPTY_WORK_MAP_DETAILS,
LINKED_EVIDENCE_CREDITS,
LINKED_EVIDENCE_KINDS,
attachFactsToWorkMapRole,
buildWorkMapSnapshot,
groupLinkedEvidence,
isLinkedEvidenceUrl,
linkedEvidenceFromPage,
normalizeSlug,
normalizeWorkMapDetails,
planLinkedEvidenceFacts,
planWorkMapPublicationSettings,
planWorkMapRoleFactSync,
prepareLinkedEvidence,
prepareLinkedEvidenceDelete,
prepareWorkMapLocation,
prepareWorkMapRoleCreate,
nominatimAddressLine,
presentWorkMapPlace,
roleCoverPhoto,
publicationAllowsSnapshot,
publicationNeedsAudienceConfirm,
type LinkedEvidence,
type LinkedEvidenceError,
type PreparedLinkedEvidence,
type PublishedLinkedEvidence,
type PreparedWorkMapLocation,
type WorkMapClaim,
type WorkMapLocation,
Expand All @@ -372,6 +389,12 @@ export {
type WorkMapShare,
type WorkMapSnapshot,
} from "./work-map";
export {
LINKED_PAGE_TEXT_LIMIT,
isPublicAddress,
readLinkedPage,
type LinkedPage,
} from "./linked-page";
export {
careerFrame,
careerMoments,
Expand Down
64 changes: 64 additions & 0 deletions core/linked-page.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { describe, expect, it } from "vitest";
import { isPublicAddress, readLinkedPage } from "./linked-page";

describe("reading a linked page", () => {
it("keeps the title, social tags, and readable text", () => {
const page = readLinkedPage(`<!doctype html>
<html><head>
<title>Fold review &amp; verdict</title>
<meta property="og:title" content="The crease is &quot;gone&quot;">
<meta content="Reviews Weekly" property="og:site_name" />
<meta name="description" content="Hands on.">
<style>.x { color: red }</style>
<script>window.track = "SECRET_SCRIPT";</script>
</head><body>
<h1>The crease</h1>
<p>You cannot feel the fold anymore.</p>
<noscript>SECRET_NOSCRIPT</noscript>
</body></html>`);
expect(page.title).toBe("Fold review & verdict");
expect(page.meta).toEqual({
"og:title": 'The crease is "gone"',
"og:site_name": "Reviews Weekly",
description: "Hands on.",
});
expect(page.text).toContain("The crease You cannot feel the fold anymore.");
expect(page.text).not.toContain("SECRET_");
expect(page.text).not.toContain("color");
});

it("caps the stored text", () => {
const page = readLinkedPage(`<p>${"word ".repeat(100_000)}</p>`);
expect(page.text.length).toBeLessThanOrEqual(100_000);
});
});

describe("public addresses", () => {
it("allows addresses on the public internet", () => {
for (const address of ["93.184.216.34", "2606:2800:220:1:248:1893:25c8:1946"]) {
expect(isPublicAddress(address)).toBe(true);
}
});

it("refuses this machine, the local network, and reserved ranges", () => {
for (const address of [
"127.0.0.1",
"10.0.0.5",
"172.16.4.2",
"192.168.1.1",
"169.254.169.254",
"100.64.0.1",
"0.0.0.0",
"224.0.0.1",
"::1",
"::",
"fe80::1",
"fd00::1",
"::ffff:127.0.0.1",
"::ffff:7f00:1",
"not-an-address",
]) {
expect(isPublicAddress(address)).toBe(false);
}
});
});
129 changes: 129 additions & 0 deletions core/linked-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
export const LINKED_PAGE_TEXT_LIMIT = 100_000;

export type LinkedPage = {
title: string;
meta: Record<string, string>;
text: string;
};

export function readLinkedPage(html: string): LinkedPage {
const title = decodeEntities(
/<title\b[^>]*>([\s\S]*?)<\/title>/i.exec(html)?.[1] ?? "",
)
.replace(/\s+/g, " ")
.trim();
const meta: Record<string, string> = {};
for (const tag of html.match(/<meta\b[^>]*>/gi) ?? []) {
const attributes = readAttributes(tag);
const key = (attributes.property ?? attributes.name ?? "").toLowerCase();
const content = attributes.content;
if (!key || content === undefined || key in meta) continue;
meta[key] = decodeEntities(content).trim();
}
const text = decodeEntities(
html
.replace(/<!--[\s\S]*?-->/g, " ")
.replace(/<head\b[\s\S]*?<\/head>/gi, " ")
.replace(/<(script|style|noscript|template|svg)\b[\s\S]*?<\/\1>/gi, " ")
.replace(/<[^>]+>/g, " "),
)
.replace(/\s+/g, " ")
.trim()
.slice(0, LINKED_PAGE_TEXT_LIMIT);
return { title, meta, text };
}

function readAttributes(tag: string): Record<string, string> {
const attributes: Record<string, string> = {};
const pattern = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))/g;
for (const match of tag.matchAll(pattern)) {
attributes[match[1].toLowerCase()] = match[2] ?? match[3] ?? match[4] ?? "";
}
return attributes;
}

function decodeEntities(value: string): string {
const named: Record<string, string> = {
amp: "&",
lt: "<",
gt: ">",
quot: '"',
apos: "'",
nbsp: " ",
};
return value.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (entity, body: string) => {
if (body[0] === "#") {
const code =
body[1] === "x" || body[1] === "X"
? Number.parseInt(body.slice(2), 16)
: Number.parseInt(body.slice(1), 10);
return Number.isFinite(code) && code > 0 && code <= 0x10ffff
? String.fromCodePoint(code)
: entity;
}
return named[body.toLowerCase()] ?? entity;
});
}

export function isPublicAddress(address: string): boolean {
const v4 = parseIpv4(address);
if (v4) return isPublicIpv4(v4);
const v6 = parseIpv6(address);
if (!v6) return false;
if (v6.every((group) => group === 0)) return false;
if (v6.slice(0, 7).every((group) => group === 0) && v6[7] === 1) return false;
if (v6.slice(0, 5).every((group) => group === 0) && v6[5] === 0xffff) {
return isPublicIpv4(embeddedIpv4(v6));
}
if (v6[0] === 0x64 && v6[1] === 0xff9b && v6.slice(2, 6).every((g) => g === 0)) {
return isPublicIpv4(embeddedIpv4(v6));
}
if ((v6[0] & 0xfe00) === 0xfc00) return false;
if ((v6[0] & 0xffc0) === 0xfe80) return false;
if ((v6[0] & 0xff00) === 0xff00) return false;
if (v6[0] === 0x2001 && v6[1] === 0x0db8) return false;
return true;
}

function isPublicIpv4([a, b, c]: number[]): boolean {
if (a === 0 || a === 10 || a === 127 || a >= 224) return false;
if (a === 100 && b >= 64 && b <= 127) return false;
if (a === 169 && b === 254) return false;
if (a === 172 && b >= 16 && b <= 31) return false;
if (a === 192 && b === 168) return false;
if (a === 192 && b === 0 && (c === 0 || c === 2)) return false;
if (a === 198 && (b === 18 || b === 19)) return false;
if (a === 198 && b === 51 && c === 100) return false;
if (a === 203 && b === 0 && c === 113) return false;
return true;
}

function embeddedIpv4(groups: number[]): number[] {
return [groups[6] >> 8, groups[6] & 0xff, groups[7] >> 8, groups[7] & 0xff];
}

function parseIpv4(value: string): number[] | null {
if (!/^\d{1,3}(?:\.\d{1,3}){3}$/.test(value)) return null;
const parts = value.split(".").map(Number);
return parts.every((part) => part <= 255) ? parts : null;
}

function parseIpv6(value: string): number[] | null {
let address = value.replace(/^\[|\]$/g, "").split("%")[0] ?? "";
if (!address.includes(":")) return null;
const tail = /(\d{1,3}(?:\.\d{1,3}){3})$/.exec(address);
if (tail) {
const v4 = parseIpv4(tail[1]);
if (!v4) return null;
address = `${address.slice(0, -tail[1].length)}${((v4[0] << 8) | v4[1]).toString(16)}:${((v4[2] << 8) | v4[3]).toString(16)}`;
}
const halves = address.split("::");
if (halves.length > 2) return null;
const head = halves[0] ? halves[0].split(":") : [];
const rest = halves.length === 2 && halves[1] ? halves[1].split(":") : [];
const missing = 8 - head.length - rest.length;
if (halves.length === 1 ? missing !== 0 : missing < 1) return null;
const groups = [...head, ...Array<string>(halves.length === 2 ? missing : 0).fill("0"), ...rest];
if (!groups.every((group) => /^[0-9a-f]{1,4}$/i.test(group))) return null;
return groups.map((group) => Number.parseInt(group, 16));
}
12 changes: 12 additions & 0 deletions core/model-onboarding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,18 @@ describe("planModelOnboarding", () => {
).toBe("Qwen3.8-27B-Q6_K.gguf");
});

it("leaves out embedding and reranking models, which cannot read a resume", () => {
expect(
presentRangeHits([
{
baseUrl: DEFAULT_LOCAL_BASE_URL,
models: ["nomic-embed-text:latest", "qwen3.8:27b", "mxbai-embed-large", "bge-m3", "all-MiniLM-L6-v2"],
},
{ baseUrl: LLAMA_CPP_BASE_URL, models: ["bge-reranker-v2-m3.gguf", "text-embedding-3-small"] },
]),
).toEqual([{ baseUrl: DEFAULT_LOCAL_BASE_URL, models: ["qwen3.8:27b"] }]);
});

it("keeps addresses that listed models and orders them by port", () => {
expect(
presentRangeHits([
Expand Down
5 changes: 4 additions & 1 deletion core/model-onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,15 @@ export function modelCountLabel(count: number): string {
return count === 1 ? "1 model" : `${count} models`;
}

/** A model list gives names only, so embedding and reranking models are told apart by name. */
const NON_READER = /embed|rerank|minilm|(^|[^a-z])bge([^a-z]|$)/i;

export function presentRangeHits(
hits: readonly { baseUrl: string; models: readonly string[] }[],
): LocalScanHit[] {
const kept = hits.flatMap((hit) => {
const baseUrl = hit.baseUrl.trim();
const models = hit.models.map((id) => id.trim()).filter(Boolean);
const models = hit.models.map((id) => id.trim()).filter((id) => id && !NON_READER.test(presentModelId(id)));
if (!baseUrl || models.length === 0) return [];
return [{ baseUrl, models }];
});
Expand Down
Loading
Loading