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
This commit is contained in:
parent
016d823f3b
commit
edd3e15ef7
12 changed files with 515 additions and 14 deletions
|
|
@ -14,6 +14,8 @@ const baseRec = (body: string): PublishedRecord => ({
|
|||
published_date: "2026-05-26",
|
||||
captured_at: "2026-06-23",
|
||||
source: "manual",
|
||||
specifics: [],
|
||||
trends: [],
|
||||
body,
|
||||
});
|
||||
|
||||
|
|
@ -82,3 +84,41 @@ describe("PublishedRecord grammar — parse∘serialize identity (SC2, B1 edge b
|
|||
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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue