Skip to content

Commit f606da9

Browse files
committed
test: attribution wire coverage + close Level 4 residual notes
Unit-test toHit/SearchHitSchema attribution shapes; document list without attribution; feed status filter comment reflects 4b retention outcomes; memory_search tool blurb mentions attribution guidance.
1 parent 0ce591c commit f606da9

5 files changed

Lines changed: 97 additions & 4 deletions

File tree

‎IMPLEMENTATION.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,8 @@ Capture-time `authority` is never rewritten. See `docs/RELEVANCY.md`.
417417
onto candidates; `toHit` emits provenance, source/temporal class, occurred_at,
418418
valid_until, corroboration counts, and derived_from. The plane maps these into
419419
additive `SearchItem.attribution` (and `DocumentStoreSearchItem.attribution`).
420+
**List/timeline** stays document-title oriented and does not attach the full
421+
attribution block (version-level fields are search/feed concerns).
420422

421423
**Retention (CL-5871):** `memory.version.retention_class` + migration
422424
`0007_retention.sql`. Plane helpers in `services/retention.ts`

‎src/core/schemas/search.test.ts‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,28 @@ describe("SearchHitSchema", () => {
4747
const out = SearchHitSchema(fixture);
4848
expect(out instanceof type.errors).toBe(true);
4949
});
50+
51+
it("accepts additive attribution fields (CL-5870)", () => {
52+
const fixture = fullHitFixture();
53+
fixture.provenance = "inferred";
54+
fixture.source_class = "derived";
55+
fixture.temporal_class = "lesson";
56+
fixture.occurred_at = "2026-07-01T00:00:00.000Z";
57+
fixture.valid_until = null;
58+
fixture.supports = 3;
59+
fixture.contradicts = 1;
60+
fixture.derived_from = ["kv_src"];
61+
fixture.generator_agent_id = "resident-distiller";
62+
fixture.created_by_kind = "agent";
63+
const out = SearchHitSchema(fixture);
64+
expect(out instanceof type.errors ? out.summary : out).toEqual(fixture);
65+
});
66+
67+
it("still parses without attribution fields (additive / back-compat)", () => {
68+
const fixture = fullHitFixture();
69+
const out = SearchHitSchema(fixture);
70+
expect(out instanceof type.errors).toBe(false);
71+
});
5072
});
5173

5274
describe("SearchResponseSchema", () => {

‎src/services/feed.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ export async function fetchFeed(
6969
const conditions = [
7070
eq(knowledgeVersion.tenantId, args.tenantId),
7171
eq(knowledgeVersion.generation, LIVE_GENERATION),
72-
// Active (or superseded historical) live rows — exclude tombstones later in 4b.
73-
// Feed surfaces commits; status filter keeps deprecated/tombstoned out once written.
72+
// Live feed: active or superseded commits only. Deprecated / tombstoned
73+
// versions are retention write-path outcomes and stay out of the cursor stream.
7474
sql`${knowledgeVersion.status} IN ('active', 'superseded')`,
7575
gt(knowledgeVersion.feedSeq, after),
7676
];

‎src/services/search.test.ts‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
fetchDenseCandidates,
77
hnswEfSearch,
88
snippet,
9+
toHit,
910
type CandidateRow,
1011
} from "./search.ts";
1112

@@ -523,3 +524,69 @@ describe("fetchDenseCandidates kind/entity filtering", () => {
523524
expect(chunkIds).toContain("chunk-note");
524525
});
525526
});
527+
528+
describe("toHit — wire attribution (CL-5870)", () => {
529+
it("surfaces provenance, temporal, corroboration, and derived_from on the hit", () => {
530+
const hit = toHit(
531+
candidate({
532+
provenance: "inferred",
533+
sourceClass: "derived",
534+
temporalClass: "state",
535+
validUntil: new Date("2026-12-01T00:00:00Z"),
536+
supports: 2,
537+
contradicts: 0,
538+
derivedFrom: ["kv_source_1", "kv_source_2"],
539+
generatorAgentId: "resident-distiller",
540+
createdByKind: "agent",
541+
}),
542+
);
543+
expect(hit.version_id).toBe("ver_1");
544+
expect(hit.provenance).toBe("inferred");
545+
expect(hit.source_class).toBe("derived");
546+
expect(hit.temporal_class).toBe("state");
547+
expect(hit.valid_until).toBe("2026-12-01T00:00:00.000Z");
548+
expect(hit.supports).toBe(2);
549+
expect(hit.contradicts).toBe(0);
550+
expect(hit.derived_from).toEqual(["kv_source_1", "kv_source_2"]);
551+
expect(hit.generator_agent_id).toBe("resident-distiller");
552+
expect(hit.created_by_kind).toBe("agent");
553+
});
554+
555+
it("omits optional attribution fields when absent (additive wire)", () => {
556+
const hit = toHit(candidate());
557+
expect(hit.provenance).toBeUndefined();
558+
expect(hit.source_class).toBeUndefined();
559+
expect(hit.derived_from).toBeUndefined();
560+
expect(hit.generator_agent_id).toBeUndefined();
561+
expect(hit.supports).toBe(0);
562+
expect(hit.contradicts).toBe(0);
563+
expect(hit.temporal_class).toBe("event");
564+
});
565+
566+
it("distinguishes stated human vs inferred agent attribution shapes", () => {
567+
const human = toHit(
568+
candidate({
569+
provenance: "stated",
570+
sourceClass: "native",
571+
createdByKind: "human",
572+
generatorAgentId: null,
573+
}),
574+
);
575+
const distilled = toHit(
576+
candidate({
577+
provenance: "inferred",
578+
sourceClass: "derived",
579+
createdByKind: "agent",
580+
generatorAgentId: "resident-distiller",
581+
derivedFrom: ["kv_raw"],
582+
}),
583+
);
584+
expect(human.provenance).toBe("stated");
585+
expect(human.created_by_kind).toBe("human");
586+
expect(human.generator_agent_id).toBeUndefined();
587+
expect(distilled.provenance).toBe("inferred");
588+
expect(distilled.created_by_kind).toBe("agent");
589+
expect(distilled.generator_agent_id).toBe("resident-distiller");
590+
expect(distilled.derived_from).toEqual(["kv_raw"]);
591+
});
592+
});

‎src/tools/search.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ export const memorySearch = defineMemoryHttpTool({
3737
name: "memory_search",
3838
description:
3939
"Hybrid semantic + keyword search over tenant memory. " +
40-
"Returns ranked items (and optional evidence). Identity is " +
41-
"the authenticated principal on the hub.",
40+
"Returns ranked items with optional additive attribution " +
41+
"(provenance, temporal class, corroboration, derivedFrom) and evidence. " +
42+
"Attribute stated content to the actor; treat inferred as own-voice claims. " +
43+
"Identity is the authenticated principal on the hub.",
4244
inputSchema: {
4345
type: "object",
4446
properties: {

0 commit comments

Comments
 (0)