linkedin-studio/scripts/brain/tests/ingest.test.ts
Kjell Tore Guttormsen edd3e15ef7 feat(linkedin-studio): SB-S3c — cross-silo id-threading + post→analytics assembler [skip-docs]
Hub-side design: the published record now carries the specifics/trends ids
it was built from (additive, omit-empty → byte-backward-compatible), and a
new pure assembler (scripts/brain/src/assemble.ts + `brain assemble`) joins
post↔analytics by normalized title-prefix + date with honest confidence
tiers (high/low/none). Answers the arc's north-star query: which raw
material actually performs? (specific → post → measured analytics).

All four tributaries untouched (analytics READ-only via inlined raw-JSON,
no package import); profile.md grammar untouched (the fact→post link stays
OUT — C-1). The repeatable --specific/--trend ingest flags collect via a
new collectRepeated helper, leaving parseFlags untouched.

TDD: 19 new brain tests (ingest 4 + publish 3 + assemble 8 + cli 4), all
SC1–SC12. brain 113/113, gate 95/0/0, BRAIN_TESTS_FLOOR 94→113,
ASSERT_BASELINE_FLOOR unchanged at 80. Light-Voyage hardened
(brief-review 5 FIX · plan-critic 1 BLOCK+4 MAJOR+4 MINOR · scope-guardian ALIGNED).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RigJBiRFNtFZKCz21qNbQ4
2026-06-23 20:47:34 +02:00

124 lines
5.1 KiB
TypeScript

import { describe, test } from "node:test";
import assert from "node:assert/strict";
import { mintContentId } from "../src/id.js";
import {
parsePublishedRecord,
serializePublishedRecord,
type PublishedRecord,
} from "../src/ingest.js";
const baseRec = (body: string): PublishedRecord => ({
id: mintContentId(body),
provenance: "published",
published_date: "2026-05-26",
captured_at: "2026-06-23",
source: "manual",
specifics: [],
trends: [],
body,
});
/** Round-trip helper: parse∘serialize must equal the original record. */
function roundTrip(rec: PublishedRecord): PublishedRecord {
return parsePublishedRecord(serializePublishedRecord(rec));
}
describe("mintContentId — verbatim-body content hash (SC3)", () => {
test("deterministic: same body → same id", () => {
const body = "Jeg lærte noe om dømmekraft i dag.\n\nDel 1 av serien.";
assert.equal(mintContentId(body), mintContentId(body));
});
test("returns a 12-hex id", () => {
assert.match(mintContentId("any body"), /^[0-9a-f]{12}$/);
});
test("different bodies → different ids", () => {
assert.notEqual(mintContentId("post A"), mintContentId("post B"));
});
test("whitespace/case differences mint DIFFERENT ids (no normalize-collision, B2 guard)", () => {
// Under specifics-bank normalizeContent these would collapse to the SAME id —
// proving the post-body hash is byte-identity, not normalized.
assert.notEqual(mintContentId("Hello World"), mintContentId("hello world"));
assert.notEqual(
mintContentId("Line one\nLine two"),
mintContentId("Line one Line two"),
);
});
});
describe("PublishedRecord grammar — parse∘serialize identity (SC2, B1 edge battery)", () => {
const bodies: Record<string, string> = {
"plain multi-paragraph": "Hook line.\n\nBody paragraph one.\n\nBody paragraph two.",
"empty body": "",
"single newline": "\n",
"trailing newlines": "post text\n\n",
"body starting with ---": "---\nlooks like a separator but is body",
"body containing a \\n---\\n sentinel mid-text": "before\n---\nafter the fake separator",
"header-shaped first line": "id: 0123456789ab\nprovenance: published",
"special chars ] | quotes": 'value with ] and | and "quotes" inside',
};
for (const [label, body] of Object.entries(bodies)) {
test(`round-trips: ${label}`, () => {
const rec = baseRec(body);
assert.deepEqual(roundTrip(rec), rec);
});
}
test("serialized form has the fixed 5-line header then the --- sentinel", () => {
const text = serializePublishedRecord(baseRec("hi"));
const [header, ...rest] = text.split("\n---\n");
assert.equal(header.split("\n").length, 5, "exactly 5 header lines before the sentinel");
assert.equal(rest.join("\n---\n"), "hi", "body is everything after the FIRST sentinel");
assert.match(text, /^id: [0-9a-f]{12}\nprovenance: published\n/);
});
test("a published/ record with non-published provenance is rejected (corruption signal)", () => {
const corrupt = serializePublishedRecord(baseRec("x")).replace(
"provenance: published",
"provenance: ai-draft",
);
assert.throws(() => parsePublishedRecord(corrupt), /provenance/i);
});
});
describe("SB-S3c — raw-material ids on the published record", () => {
const ids2 = ["aaaaaaaaaaaa", "bbbbbbbbbbbb"];
// SC1 — round-trip with non-empty specifics/trends, order preserved.
test("SC1: parse∘serialize round-trips a record carrying specifics/trends (order preserved)", () => {
const rec: PublishedRecord = { ...baseRec("a tagged post"), specifics: ids2, trends: ["cccccccccccc"] };
assert.deepEqual(roundTrip(rec), rec);
});
// SC2 — empty arrays → unchanged 5-line header; a pre-S3c fixture round-trips byte-identically.
test("SC2: empty specifics/trends serialize byte-identically to a pre-S3c record", () => {
const oldText =
"id: 0123456789ab\nprovenance: published\npublished_date: 2026-05-26\n" +
"captured_at: 2026-06-23\nsource: manual\n---\nthe body text";
assert.equal(serializePublishedRecord(parsePublishedRecord(oldText)), oldText);
// and the live empty-array record still emits exactly 5 header lines.
const text = serializePublishedRecord(baseRec("hi"));
assert.equal(text.split("\n---\n")[0].split("\n").length, 5);
});
// SC2b — when present, the new lines are appended AFTER source: and before the sentinel.
test("SC2b: non-empty specifics/trends are appended after source:, before the --- sentinel", () => {
const text = serializePublishedRecord({ ...baseRec("x"), specifics: [ids2[0]], trends: [ids2[1]] });
const header = text.split("\n---\n")[0];
assert.match(header, /source: manual\nspecifics: aaaaaaaaaaaa\ntrends: bbbbbbbbbbbb$/);
});
// SC3 (parse side) — a non-12-hex id in the header throws, never silently dropped.
test("SC3: a malformed specifics/trends id throws on parse", () => {
const bad =
serializePublishedRecord(baseRec("x")).replace(
"source: manual",
"source: manual\nspecifics: NOTAHEXID",
);
assert.throws(() => parsePublishedRecord(bad), /bad specifics id/i);
});
});