feat(linkedin-studio): SB-S3b — supersede arm in the consolidation engine [skip-docs]
The engine can now RETIRE a fact via an operator-gated, explicitly-signalled
temporal-update supersede — completing the consolidation motor's one S2 TODO
("no supersede in S2 — that's S3"). A `supersedes` signal on a candidate retires
the stale fact (re-minted to an archival id, status: superseded, REPLACED IN
PLACE, retained as audit) and installs the new winner under the canonical key-id,
so mintEntityId(key) always points at the live fact.
- consolidate.ts: Candidate.supersedes? + ProfileDiff.supersedes (SupersedeOp
carries the full winner fact, so applyDiff stays a pure projector). proposeDiff
value-guarded routing fork (routes only when the target holds a DIFFERENT value —
else a re-sent signal would self-supersede every run) + intra-batch
first-supersede-wins guard. applyDiff replace-in-place + value-matched state-check
(oldId present, active, value===oldValue) → idempotent re-apply + stale-diff safe;
superseded facts never bumped/promoted (supersede wins). Decay excludes superseded.
archivalId seeded with the pre-archival id (collision-free).
- cli.ts: renderDiffMd `## Supersessions (old → new)` (rendered last, only when
present → zero-supersession diffs stay byte-identical); validateCandidates optional
single-line `supersedes`; `--gather` profileFacts filtered to active (superseded
archival facts never re-presented as live context).
- Tests: +12 brain (consolidate 10 + consolidate-cli 2). TDD: RED (6 fail) → GREEN
(94/94). BRAIN_TESTS_FLOOR 82->94; ASSERT_BASELINE_FLOOR unchanged at 80 (no new
test-runner.sh section). Gate 95/0/0.
- Docs: consolidation-loop.md rule table + honest-limit reconciled (the operator
gate is the only classification net); engine docstring updated.
All 13 success criteria deterministically tested (unlike S3a, no behavioural-only
SC). READ-only gate unchanged — brain consolidate --apply --confirm stays the sole
profile.md writer. Scope held: scripts/brain/ only; temporal-update only
(condition-dependent/distractor deferred).
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
d2ec7482be
commit
585f972a05
6 changed files with 267 additions and 19 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { proposeDiff, applyDiff, type Candidate } from "../src/consolidate.js";
|
||||
import { proposeDiff, applyDiff, type Candidate, type ProfileDiff } from "../src/consolidate.js";
|
||||
import { mintEntityId } from "../src/id.js";
|
||||
import { parseProfile, serializeProfile } from "../src/profile.js";
|
||||
import type { ProfileDoc, ProfileFact } from "../src/types.js";
|
||||
|
|
@ -158,3 +158,130 @@ describe("proposeDiff purity + applyDiff round-trip (SC2, SC3, SC4)", () => {
|
|||
assert.equal(factsTwice, factsOnce, "no duplicate fact on re-run (bump only)");
|
||||
});
|
||||
});
|
||||
|
||||
describe("supersede arm (SB-S3b)", () => {
|
||||
test("S3b-SC1 — routing: a supersede candidate retires the active prior, no keep-both", () => {
|
||||
const id = observedId("role");
|
||||
const cur = doc([], [fact({ id, value: "advisor", evidence_count: 2 })]);
|
||||
const diff = proposeDiff({ current: cur, candidates: [cand({ key: "role", value: "architect", supersedes: "role" })], today: TODAY });
|
||||
assert.equal(diff.supersedes.length, 1, "one supersede op");
|
||||
assert.equal(diff.conflicts.length, 0, "supersede routes away from keep-both");
|
||||
assert.equal(diff.supersedes[0].oldId, id);
|
||||
assert.equal(diff.supersedes[0].oldValue, "advisor");
|
||||
assert.equal(diff.supersedes[0].winner.value, "architect");
|
||||
assert.equal(diff.supersedes[0].winner.id, observedId("role"), "winner takes the canonical key-id");
|
||||
assert.equal(diff.additions.length, 0, "winner is carried in the op, not additions");
|
||||
});
|
||||
|
||||
test("S3b-SC2 — apply: retired fact superseded+re-minted, winner at key-id, both retained", () => {
|
||||
const id = observedId("role");
|
||||
const cur = doc([], [fact({ id, value: "advisor", evidence_count: 2, first_seen: "2026-01-01" })]);
|
||||
const next = applyDiff(cur, proposeDiff({ current: cur, candidates: [cand({ key: "role", value: "architect", supersedes: "role" })], today: TODAY }));
|
||||
const all = [...next.static, ...next.dynamic];
|
||||
const winner = all.find((f) => f.value === "architect")!;
|
||||
const retired = all.find((f) => f.value === "advisor")!;
|
||||
assert.ok(winner && retired, "both winner + retired present");
|
||||
assert.equal(winner.id, id, "winner takes the canonical key-id");
|
||||
assert.equal(winner.status, "active");
|
||||
assert.equal(winner.evidence_count, 1, "winner re-earns evidence");
|
||||
assert.equal(retired.status, "superseded");
|
||||
assert.notEqual(retired.id, id, "retired fact re-minted off the key-id (archival)");
|
||||
assert.equal(retired.first_seen, "2026-01-01", "retired fact keeps its OWN first_seen (audit)");
|
||||
const ids = all.map((f) => f.id);
|
||||
assert.equal(new Set(ids).size, ids.length, "no duplicate id");
|
||||
});
|
||||
|
||||
test("S3b-SC3a — propose-idempotent: a re-sent supersede whose target already holds the new value bumps, no new supersession", () => {
|
||||
const id = observedId("role");
|
||||
const cur = doc([], [fact({ id, value: "advisor", evidence_count: 2 })]);
|
||||
const once = applyDiff(cur, proposeDiff({ current: cur, candidates: [cand({ key: "role", value: "architect", supersedes: "role" })], today: TODAY }));
|
||||
// re-extract the same candidate (value now matches the winner) WITH the stale signal still set
|
||||
const diff2 = proposeDiff({ current: once, candidates: [cand({ key: "role", value: "architect", supersedes: "role" })], today: TODAY });
|
||||
assert.equal(diff2.supersedes.length, 0, "no self-supersede when the target already holds the value");
|
||||
assert.equal(diff2.evidenceBumps.length, 1, "it bumps the winner instead");
|
||||
});
|
||||
|
||||
test("S3b-SC3b — apply-idempotent: re-applying the same supersede diff is a no-op (value-matched skip)", () => {
|
||||
const id = observedId("role");
|
||||
const cur = doc([], [fact({ id, value: "advisor", evidence_count: 2 })]);
|
||||
const diff = proposeDiff({ current: cur, candidates: [cand({ key: "role", value: "architect", supersedes: "role" })], today: TODAY });
|
||||
const once = applyDiff(cur, diff);
|
||||
const twice = applyDiff(once, diff);
|
||||
assert.deepEqual(twice, once, "re-applying the same supersede diff changes nothing");
|
||||
});
|
||||
|
||||
test("S3b-SC4 — graceful no-target: a supersede signal with no active prior degrades to a plain add", () => {
|
||||
const cur = doc([], []);
|
||||
const diff = proposeDiff({ current: cur, candidates: [cand({ key: "role", value: "architect", supersedes: "role" })], today: TODAY });
|
||||
assert.equal(diff.supersedes.length, 0);
|
||||
assert.equal(diff.additions.length, 1, "degrades to a plain add");
|
||||
assert.equal(diff.additions[0].value, "architect");
|
||||
assert.equal(diff.additions[0].id, observedId("role"));
|
||||
});
|
||||
|
||||
test("S3b-SC5 — round-trip: a superseded fact produced by applyDiff parses/serializes identically", () => {
|
||||
const id = observedId("role");
|
||||
const cur = doc([], [fact({ id, value: "advisor", evidence_count: 2 })]);
|
||||
const next = applyDiff(cur, proposeDiff({ current: cur, candidates: [cand({ key: "role", value: "architect", supersedes: "role" })], today: TODAY }));
|
||||
const round = parseProfile(serializeProfile(next));
|
||||
assert.deepEqual(round, next, "superseded fact round-trips through the grammar");
|
||||
const ids = [...round.static, ...round.dynamic].map((f) => f.id);
|
||||
assert.equal(new Set(ids).size, ids.length, "all ids unique");
|
||||
});
|
||||
|
||||
test("S3b-SC7 — no regression: a candidate WITHOUT the signal still keeps-both (no supersede)", () => {
|
||||
const id = observedId("role");
|
||||
const cur = doc([], [fact({ id, value: "advisor", evidence_count: 2 })]);
|
||||
const diff = proposeDiff({ current: cur, candidates: [cand({ key: "role", value: "architect" })], today: TODAY });
|
||||
assert.equal(diff.supersedes.length, 0);
|
||||
assert.equal(diff.conflicts.length, 1, "keep-both unchanged when no signal");
|
||||
});
|
||||
|
||||
test("S3b-SC8 — intra-batch: two supersede candidates for the same key resolve deterministically (first wins)", () => {
|
||||
const id = observedId("role");
|
||||
const cur = doc([], [fact({ id, value: "advisor", evidence_count: 2 })]);
|
||||
const diff = proposeDiff({
|
||||
current: cur,
|
||||
candidates: [
|
||||
cand({ key: "role", value: "architect", supersedes: "role" }),
|
||||
cand({ key: "role", value: "principal", supersedes: "role" }),
|
||||
],
|
||||
today: TODAY,
|
||||
});
|
||||
assert.equal(diff.supersedes.length, 1, "only the first supersede for the key is taken");
|
||||
assert.equal(diff.supersedes[0].winner.value, "architect");
|
||||
const next = applyDiff(cur, diff);
|
||||
const ids = [...next.static, ...next.dynamic].map((f) => f.id);
|
||||
assert.equal(new Set(ids).size, ids.length, "no duplicate id after intra-batch supersede");
|
||||
});
|
||||
|
||||
test("S3b-SC11 — decay excludes superseded: a retained superseded dynamic fact is not stale-flagged", () => {
|
||||
const old = "2026-03-01"; // ~114 days before TODAY
|
||||
const superseded = fact({ id: observedId("old-role"), value: "advisor", last_seen: old, status: "superseded" });
|
||||
const active = fact({ id: observedId("topic"), value: "x", last_seen: old, status: "active" });
|
||||
const cur = doc([], [superseded, active]);
|
||||
const diff = proposeDiff({ current: cur, candidates: [], today: TODAY });
|
||||
assert.equal(diff.staleFlags.length, 1, "only the active stale fact is flagged");
|
||||
assert.equal(diff.staleFlags[0].id, active.id);
|
||||
});
|
||||
|
||||
test("S3b-SC12 — supersede wins over a same-id bump in one diff", () => {
|
||||
const id = observedId("role");
|
||||
const cur = doc([], [fact({ id, value: "advisor", evidence_count: 2 })]);
|
||||
const winner = fact({ id, value: "architect", evidence_count: 1, status: "active" });
|
||||
const diff: ProfileDiff = {
|
||||
additions: [],
|
||||
promotions: [],
|
||||
conflicts: [],
|
||||
staleFlags: [],
|
||||
evidenceBumps: [{ id, newCount: 3, last_seen: TODAY }], // a stale bump targeting the same id
|
||||
supersedes: [{ oldId: id, oldValue: "advisor", winner }],
|
||||
};
|
||||
const next = applyDiff(cur, diff);
|
||||
const all = [...next.static, ...next.dynamic];
|
||||
const w = all.find((f) => f.value === "architect")!;
|
||||
const r = all.find((f) => f.value === "advisor")!;
|
||||
assert.equal(r.status, "superseded", "the old fact is superseded, not bumped");
|
||||
assert.equal(w.evidence_count, 1, "winner unaffected by the stale bump");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue