feat: unify Memory REST DTO authority
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
// Generated from workspace-api. Do not edit by hand.
|
||||
// Regenerate: cargo run -q -p workspace-api --features typescript --example generate_memory_api_types > web/workspace/src/lib/generated/memory-api.ts
|
||||
|
||||
export type DiagnosticSeverity = "info" | "warning" | "error";
|
||||
|
||||
export type Diagnostic = {
|
||||
code: string;
|
||||
severity: DiagnosticSeverity;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type MemoryDocumentResponse = {
|
||||
body_md: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
bytes: number;
|
||||
record_source: string;
|
||||
};
|
||||
|
||||
export type MemoryCandidateKind =
|
||||
| "preference"
|
||||
| "working_assumption"
|
||||
| "constraint"
|
||||
| "decision"
|
||||
| "open_question"
|
||||
| "lesson";
|
||||
|
||||
export type MemoryEvidenceOriginKind =
|
||||
| "human_input"
|
||||
| "worker_input"
|
||||
| "flow_instruction"
|
||||
| "backend_instruction"
|
||||
| "model_output"
|
||||
| "tool_output"
|
||||
| "derived_summary"
|
||||
| "legacy_unknown";
|
||||
|
||||
export type MemoryEvidenceOrigin = {
|
||||
kind: MemoryEvidenceOriginKind;
|
||||
account_id?: string | null;
|
||||
workspace_id?: string | null;
|
||||
runtime_id?: string | null;
|
||||
worker_id?: string | null;
|
||||
flow_selector?: string | null;
|
||||
flow_definition_id?: string | null;
|
||||
flow_definition_revision?: number | null;
|
||||
};
|
||||
|
||||
export type MemorySourceRef = { segment_id: string; range: [number, number] };
|
||||
|
||||
export type MemoryStagingEvidence = {
|
||||
id: string;
|
||||
kind: string;
|
||||
entry_range: [number, number] | null;
|
||||
origin?: MemoryEvidenceOrigin | null;
|
||||
excerpt: string | null;
|
||||
summary: string | null;
|
||||
};
|
||||
|
||||
export type MemorySourceEvidenceRef = {
|
||||
session_id: string | null;
|
||||
segment_id: string | null;
|
||||
entry_range: [number, number] | null;
|
||||
evidence_id: string | null;
|
||||
origin?: MemoryEvidenceOrigin | null;
|
||||
evidence_kind: string | null;
|
||||
label: string | null;
|
||||
summary: string | null;
|
||||
};
|
||||
|
||||
export type MemoryStagingRecord = {
|
||||
schema_version: number;
|
||||
id: string;
|
||||
extract_run_id: string;
|
||||
source: MemorySourceRef;
|
||||
kind: MemoryCandidateKind;
|
||||
claim: string;
|
||||
why_useful: string;
|
||||
staleness: string | null;
|
||||
evidence: Array<MemoryStagingEvidence>;
|
||||
source_refs: Array<MemorySourceEvidenceRef>;
|
||||
};
|
||||
|
||||
export type MemoryStagingEntry = {
|
||||
id: string;
|
||||
byte_len: number;
|
||||
record: MemoryStagingRecord;
|
||||
};
|
||||
|
||||
export type MemoryStagingListResponse = {
|
||||
limit: number;
|
||||
returned_count: number;
|
||||
total_valid_count: number;
|
||||
invalid_count: number;
|
||||
truncated: boolean;
|
||||
order: string;
|
||||
record_authority: string;
|
||||
items: Array<MemoryStagingEntry>;
|
||||
diagnostics: Array<Diagnostic>;
|
||||
};
|
||||
@@ -0,0 +1,392 @@
|
||||
import type {
|
||||
Diagnostic,
|
||||
DiagnosticSeverity,
|
||||
MemoryCandidateKind,
|
||||
MemoryDocumentResponse,
|
||||
MemoryEvidenceOrigin,
|
||||
MemoryEvidenceOriginKind,
|
||||
MemorySourceEvidenceRef,
|
||||
MemorySourceRef,
|
||||
MemoryStagingEntry,
|
||||
MemoryStagingEvidence,
|
||||
MemoryStagingListResponse,
|
||||
MemoryStagingRecord,
|
||||
} from "$lib/generated/memory-api";
|
||||
|
||||
const MAX_STAGING_ITEMS = 500;
|
||||
const MAX_EVIDENCE_PER_RECORD = 500;
|
||||
const MAX_SOURCE_REFS_PER_RECORD = 500;
|
||||
const MAX_ORIGIN_VALUE_LENGTH = 512;
|
||||
|
||||
const candidateKinds = new Set<MemoryCandidateKind>([
|
||||
"preference",
|
||||
"working_assumption",
|
||||
"constraint",
|
||||
"decision",
|
||||
"open_question",
|
||||
"lesson",
|
||||
]);
|
||||
const originKinds = new Set<MemoryEvidenceOriginKind>([
|
||||
"human_input",
|
||||
"worker_input",
|
||||
"flow_instruction",
|
||||
"backend_instruction",
|
||||
"model_output",
|
||||
"tool_output",
|
||||
"derived_summary",
|
||||
"legacy_unknown",
|
||||
]);
|
||||
const diagnosticSeverities = new Set<DiagnosticSeverity>([
|
||||
"info",
|
||||
"warning",
|
||||
"error",
|
||||
]);
|
||||
|
||||
export function parseMemoryDocumentResponse(
|
||||
value: unknown,
|
||||
): MemoryDocumentResponse {
|
||||
const record = strictRecord(
|
||||
value,
|
||||
["body_md", "created_at", "updated_at", "bytes", "record_source"],
|
||||
"Memory document response",
|
||||
);
|
||||
return {
|
||||
body_md: requiredString(record, "body_md"),
|
||||
created_at: requiredString(record, "created_at"),
|
||||
updated_at: requiredString(record, "updated_at"),
|
||||
bytes: requiredNonNegativeInteger(record, "bytes"),
|
||||
record_source: requiredString(record, "record_source"),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseMemoryStagingListResponse(
|
||||
value: unknown,
|
||||
): MemoryStagingListResponse {
|
||||
const record = strictRecord(
|
||||
value,
|
||||
[
|
||||
"limit",
|
||||
"returned_count",
|
||||
"total_valid_count",
|
||||
"invalid_count",
|
||||
"truncated",
|
||||
"order",
|
||||
"record_authority",
|
||||
"items",
|
||||
"diagnostics",
|
||||
],
|
||||
"Memory staging list response",
|
||||
);
|
||||
const items = boundedArray(record.items, MAX_STAGING_ITEMS, "items").map(
|
||||
parseStagingEntry,
|
||||
);
|
||||
const diagnostics = boundedArray(
|
||||
record.diagnostics,
|
||||
MAX_STAGING_ITEMS,
|
||||
"diagnostics",
|
||||
).map(parseDiagnostic);
|
||||
const returnedCount = requiredNonNegativeInteger(record, "returned_count");
|
||||
if (returnedCount !== items.length) {
|
||||
invalid("returned_count does not match items");
|
||||
}
|
||||
return {
|
||||
limit: requiredNonNegativeInteger(record, "limit"),
|
||||
returned_count: returnedCount,
|
||||
total_valid_count: requiredNonNegativeInteger(record, "total_valid_count"),
|
||||
invalid_count: requiredNonNegativeInteger(record, "invalid_count"),
|
||||
truncated: requiredBoolean(record, "truncated"),
|
||||
order: requiredString(record, "order"),
|
||||
record_authority: requiredString(record, "record_authority"),
|
||||
items,
|
||||
diagnostics,
|
||||
};
|
||||
}
|
||||
|
||||
function parseStagingEntry(value: unknown): MemoryStagingEntry {
|
||||
const record = strictRecord(
|
||||
value,
|
||||
["id", "byte_len", "record"],
|
||||
"Memory staging entry",
|
||||
);
|
||||
return {
|
||||
id: requiredString(record, "id"),
|
||||
byte_len: requiredNonNegativeInteger(record, "byte_len"),
|
||||
record: parseStagingRecord(record.record),
|
||||
};
|
||||
}
|
||||
|
||||
function parseStagingRecord(value: unknown): MemoryStagingRecord {
|
||||
const record = strictRecord(
|
||||
value,
|
||||
[
|
||||
"schema_version",
|
||||
"id",
|
||||
"extract_run_id",
|
||||
"source",
|
||||
"kind",
|
||||
"claim",
|
||||
"why_useful",
|
||||
"staleness",
|
||||
"evidence",
|
||||
"source_refs",
|
||||
],
|
||||
"Memory staging record",
|
||||
);
|
||||
const kind = requiredString(record, "kind") as MemoryCandidateKind;
|
||||
if (!candidateKinds.has(kind)) {
|
||||
invalid("unknown Memory candidate kind");
|
||||
}
|
||||
return {
|
||||
schema_version: requiredNonNegativeInteger(record, "schema_version"),
|
||||
id: requiredString(record, "id"),
|
||||
extract_run_id: requiredString(record, "extract_run_id"),
|
||||
source: parseSourceRef(record.source),
|
||||
kind,
|
||||
claim: requiredString(record, "claim"),
|
||||
why_useful: requiredString(record, "why_useful"),
|
||||
staleness: nullableString(record, "staleness"),
|
||||
evidence: boundedArray(
|
||||
record.evidence,
|
||||
MAX_EVIDENCE_PER_RECORD,
|
||||
"evidence",
|
||||
).map(parseStagingEvidence),
|
||||
source_refs: boundedArray(
|
||||
record.source_refs,
|
||||
MAX_SOURCE_REFS_PER_RECORD,
|
||||
"source_refs",
|
||||
).map(parseSourceEvidenceRef),
|
||||
};
|
||||
}
|
||||
|
||||
function parseSourceRef(value: unknown): MemorySourceRef {
|
||||
const record = strictRecord(
|
||||
value,
|
||||
["segment_id", "range"],
|
||||
"Memory source ref",
|
||||
);
|
||||
return {
|
||||
segment_id: requiredString(record, "segment_id"),
|
||||
range: parseEntryRange(record.range, "range"),
|
||||
};
|
||||
}
|
||||
|
||||
function parseStagingEvidence(value: unknown): MemoryStagingEvidence {
|
||||
const record = strictRecord(
|
||||
value,
|
||||
["id", "kind", "entry_range", "origin", "excerpt", "summary"],
|
||||
"Memory staging evidence",
|
||||
["origin"],
|
||||
);
|
||||
const result: MemoryStagingEvidence = {
|
||||
id: requiredString(record, "id"),
|
||||
kind: requiredString(record, "kind"),
|
||||
entry_range: parseNullableEntryRange(record.entry_range, "entry_range"),
|
||||
excerpt: nullableString(record, "excerpt"),
|
||||
summary: nullableString(record, "summary"),
|
||||
};
|
||||
if ("origin" in record) {
|
||||
result.origin = record.origin === null
|
||||
? null
|
||||
: parseEvidenceOrigin(record.origin);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseSourceEvidenceRef(value: unknown): MemorySourceEvidenceRef {
|
||||
const record = strictRecord(
|
||||
value,
|
||||
[
|
||||
"session_id",
|
||||
"segment_id",
|
||||
"entry_range",
|
||||
"evidence_id",
|
||||
"origin",
|
||||
"evidence_kind",
|
||||
"label",
|
||||
"summary",
|
||||
],
|
||||
"Memory source evidence ref",
|
||||
["origin"],
|
||||
);
|
||||
const result: MemorySourceEvidenceRef = {
|
||||
session_id: nullableString(record, "session_id"),
|
||||
segment_id: nullableString(record, "segment_id"),
|
||||
entry_range: parseNullableEntryRange(record.entry_range, "entry_range"),
|
||||
evidence_id: nullableString(record, "evidence_id"),
|
||||
evidence_kind: nullableString(record, "evidence_kind"),
|
||||
label: nullableString(record, "label"),
|
||||
summary: nullableString(record, "summary"),
|
||||
};
|
||||
if ("origin" in record) {
|
||||
result.origin = record.origin === null
|
||||
? null
|
||||
: parseEvidenceOrigin(record.origin);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseEvidenceOrigin(value: unknown): MemoryEvidenceOrigin {
|
||||
const optional = [
|
||||
"account_id",
|
||||
"workspace_id",
|
||||
"runtime_id",
|
||||
"worker_id",
|
||||
"flow_selector",
|
||||
"flow_definition_id",
|
||||
"flow_definition_revision",
|
||||
] as const;
|
||||
const record = strictRecord(
|
||||
value,
|
||||
["kind", ...optional],
|
||||
"Memory evidence origin",
|
||||
[...optional],
|
||||
);
|
||||
const kind = requiredString(record, "kind") as MemoryEvidenceOriginKind;
|
||||
if (!originKinds.has(kind)) {
|
||||
invalid("unknown Memory evidence origin kind");
|
||||
}
|
||||
const result: MemoryEvidenceOrigin = { kind };
|
||||
for (
|
||||
const key of [
|
||||
"account_id",
|
||||
"workspace_id",
|
||||
"runtime_id",
|
||||
"worker_id",
|
||||
"flow_selector",
|
||||
"flow_definition_id",
|
||||
] as const
|
||||
) {
|
||||
if (key in record) {
|
||||
const text = nullableString(record, key);
|
||||
if (text !== null && text.length > MAX_ORIGIN_VALUE_LENGTH) {
|
||||
invalid(`${key} exceeds the Memory origin limit`);
|
||||
}
|
||||
result[key] = text;
|
||||
}
|
||||
}
|
||||
if ("flow_definition_revision" in record) {
|
||||
result.flow_definition_revision = record.flow_definition_revision === null
|
||||
? null
|
||||
: nonNegativeInteger(
|
||||
record.flow_definition_revision,
|
||||
"flow_definition_revision",
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseDiagnostic(value: unknown): Diagnostic {
|
||||
const record = strictRecord(
|
||||
value,
|
||||
["code", "severity", "message"],
|
||||
"Memory diagnostic",
|
||||
);
|
||||
const severity = requiredString(record, "severity") as DiagnosticSeverity;
|
||||
if (!diagnosticSeverities.has(severity)) {
|
||||
invalid("unknown diagnostic severity");
|
||||
}
|
||||
return {
|
||||
code: requiredString(record, "code"),
|
||||
severity,
|
||||
message: requiredString(record, "message"),
|
||||
};
|
||||
}
|
||||
|
||||
function strictRecord(
|
||||
value: unknown,
|
||||
keys: readonly string[],
|
||||
label: string,
|
||||
optionalKeys: readonly string[] = [],
|
||||
): Record<string, unknown> {
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
||||
invalid(`${label} must be an object`);
|
||||
}
|
||||
const record = value as Record<string, unknown>;
|
||||
const allowed = new Set(keys);
|
||||
for (const key of Object.keys(record)) {
|
||||
if (!allowed.has(key)) {
|
||||
invalid(`${label} has an unknown field`);
|
||||
}
|
||||
}
|
||||
const optional = new Set(optionalKeys);
|
||||
for (const key of keys) {
|
||||
if (!optional.has(key) && !(key in record)) {
|
||||
invalid(`${label} is missing a required field`);
|
||||
}
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
function boundedArray(
|
||||
value: unknown,
|
||||
maximum: number,
|
||||
label: string,
|
||||
): unknown[] {
|
||||
if (!Array.isArray(value) || value.length > maximum) {
|
||||
invalid(`${label} must be a bounded array`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function requiredString(record: Record<string, unknown>, key: string): string {
|
||||
if (typeof record[key] !== "string") {
|
||||
invalid(`${key} must be a string`);
|
||||
}
|
||||
return record[key];
|
||||
}
|
||||
|
||||
function nullableString(
|
||||
record: Record<string, unknown>,
|
||||
key: string,
|
||||
): string | null {
|
||||
const value = record[key];
|
||||
if (value !== null && typeof value !== "string") {
|
||||
invalid(`${key} must be a string or null`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function requiredBoolean(
|
||||
record: Record<string, unknown>,
|
||||
key: string,
|
||||
): boolean {
|
||||
if (typeof record[key] !== "boolean") {
|
||||
invalid(`${key} must be a boolean`);
|
||||
}
|
||||
return record[key];
|
||||
}
|
||||
|
||||
function requiredNonNegativeInteger(
|
||||
record: Record<string, unknown>,
|
||||
key: string,
|
||||
): number {
|
||||
return nonNegativeInteger(record[key], key);
|
||||
}
|
||||
|
||||
function nonNegativeInteger(value: unknown, label: string): number {
|
||||
if (!Number.isSafeInteger(value) || (value as number) < 0) {
|
||||
invalid(`${label} must be a non-negative safe integer`);
|
||||
}
|
||||
return value as number;
|
||||
}
|
||||
|
||||
function parseEntryRange(value: unknown, label: string): [number, number] {
|
||||
if (!Array.isArray(value) || value.length !== 2) {
|
||||
invalid(`${label} must be a two-item entry range`);
|
||||
}
|
||||
return [
|
||||
nonNegativeInteger(value[0], label),
|
||||
nonNegativeInteger(value[1], label),
|
||||
];
|
||||
}
|
||||
|
||||
function parseNullableEntryRange(
|
||||
value: unknown,
|
||||
label: string,
|
||||
): [number, number] | null {
|
||||
return value === null ? null : parseEntryRange(value, label);
|
||||
}
|
||||
|
||||
function invalid(message: string): never {
|
||||
throw new Error(`Invalid Memory API response: ${message}`);
|
||||
}
|
||||
@@ -206,75 +206,6 @@ export type RepositoryListResponse = SharedRepositoryListResponse;
|
||||
export type RepositoryDetailResponse = SharedRepositoryDetailResponse;
|
||||
export type RepositoryLogResponse = SharedRepositoryLogResponse;
|
||||
|
||||
export type MemoryDocumentResponse = {
|
||||
body_md: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
bytes: number;
|
||||
record_source: string;
|
||||
};
|
||||
|
||||
export type MemoryCandidateKind =
|
||||
| "preference"
|
||||
| "working_assumption"
|
||||
| "constraint"
|
||||
| "decision"
|
||||
| "open_question"
|
||||
| "lesson";
|
||||
|
||||
export type MemorySourceRef = {
|
||||
segment_id: string;
|
||||
range: [number, number];
|
||||
};
|
||||
|
||||
export type MemoryStagingEvidence = {
|
||||
id: string;
|
||||
kind: string;
|
||||
entry_range?: [number, number] | null;
|
||||
excerpt?: string | null;
|
||||
summary?: string | null;
|
||||
};
|
||||
|
||||
export type MemorySourceEvidenceRef = {
|
||||
session_id?: string | null;
|
||||
segment_id?: string | null;
|
||||
entry_range?: [number, number] | null;
|
||||
evidence_id?: string | null;
|
||||
evidence_kind?: string | null;
|
||||
label?: string | null;
|
||||
summary?: string | null;
|
||||
};
|
||||
|
||||
export type MemoryStagingRecord = {
|
||||
schema_version: number;
|
||||
id: string;
|
||||
extract_run_id: string;
|
||||
source: MemorySourceRef;
|
||||
kind: MemoryCandidateKind;
|
||||
claim: string;
|
||||
why_useful: string;
|
||||
staleness?: string | null;
|
||||
evidence?: MemoryStagingEvidence[];
|
||||
source_refs?: MemorySourceEvidenceRef[];
|
||||
};
|
||||
|
||||
export type MemoryStagingEntry = {
|
||||
id: string;
|
||||
byte_len: number;
|
||||
record: MemoryStagingRecord;
|
||||
};
|
||||
|
||||
export type MemoryStagingListResponse = {
|
||||
limit: number;
|
||||
returned_count: number;
|
||||
total_valid_count: number;
|
||||
invalid_count: number;
|
||||
truncated: boolean;
|
||||
order: string;
|
||||
record_authority: string;
|
||||
items: MemoryStagingEntry[];
|
||||
};
|
||||
|
||||
export type {
|
||||
DerivedTicketRelation,
|
||||
TicketDetail,
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { loadJson, workspaceApiPath } from "$lib/workspace/api/http";
|
||||
import type { MemoryDocumentResponse } from "$lib/workspace/sidebar/types";
|
||||
import { parseMemoryDocumentResponse } from "$lib/workspace/memory/api";
|
||||
import type { PageLoad } from "./$types";
|
||||
|
||||
export const load: PageLoad = async ({ fetch, params }) => {
|
||||
return {
|
||||
workspaceId: params.workspaceId,
|
||||
memory: await loadJson<MemoryDocumentResponse>(
|
||||
memory: await loadJson(
|
||||
fetch,
|
||||
workspaceApiPath(params.workspaceId, "/memory"),
|
||||
undefined,
|
||||
parseMemoryDocumentResponse,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { MemoryStagingEntry, MemoryStagingRecord } from '$lib/workspace/sidebar/types';
|
||||
import type { MemoryStagingEntry, MemoryStagingRecord } from '$lib/generated/memory-api';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
@@ -68,6 +68,12 @@
|
||||
<p class="section-note">Showing first {data.staging.data.limit} staged record(s).</p>
|
||||
{/if}
|
||||
|
||||
{#each data.staging.data.diagnostics as diagnostic (diagnostic.code)}
|
||||
<p class:error={diagnostic.severity === 'error'} class="section-note">
|
||||
{diagnostic.message}
|
||||
</p>
|
||||
{/each}
|
||||
|
||||
{#if entries.length === 0}
|
||||
<p>No Memory Staging records are present.</p>
|
||||
{:else}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { loadJson, workspaceApiPath } from "$lib/workspace/api/http";
|
||||
import type { MemoryStagingListResponse } from "$lib/workspace/sidebar/types";
|
||||
import { parseMemoryStagingListResponse } from "$lib/workspace/memory/api";
|
||||
import type { PageLoad } from "./$types";
|
||||
|
||||
export const load: PageLoad = async ({ fetch, params }) => {
|
||||
return {
|
||||
workspaceId: params.workspaceId,
|
||||
staging: await loadJson<MemoryStagingListResponse>(
|
||||
staging: await loadJson(
|
||||
fetch,
|
||||
`${workspaceApiPath(params.workspaceId, "/memory/staging")}?limit=200`,
|
||||
undefined,
|
||||
parseMemoryStagingListResponse,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
declare const Deno: {
|
||||
test(name: string, fn: () => void | Promise<void>): void;
|
||||
};
|
||||
|
||||
import {
|
||||
parseMemoryDocumentResponse,
|
||||
parseMemoryStagingListResponse,
|
||||
} from "../src/lib/workspace/memory/api.ts";
|
||||
|
||||
function assertEquals(actual: unknown, expected: unknown): void {
|
||||
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
||||
throw new Error(
|
||||
`expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function assertThrows(fn: () => void, expectedMessage: string): void {
|
||||
try {
|
||||
fn();
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.includes(expectedMessage)) {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
throw new Error(`expected function to throw ${expectedMessage}`);
|
||||
}
|
||||
|
||||
function fixture(origin: Record<string, unknown>) {
|
||||
return {
|
||||
limit: 100,
|
||||
returned_count: 1,
|
||||
total_valid_count: 1,
|
||||
invalid_count: 0,
|
||||
truncated: false,
|
||||
order: "imported_at_desc_candidate_id_asc",
|
||||
record_authority: "sqlite_workspace_authority.memory_staging",
|
||||
items: [{
|
||||
id: "candidate-1",
|
||||
byte_len: 128,
|
||||
record: {
|
||||
schema_version: 2,
|
||||
id: "candidate-1",
|
||||
extract_run_id: "extract-run-1",
|
||||
source: { segment_id: "segment-1", range: [10, 20] },
|
||||
kind: "decision",
|
||||
claim: "Keep provenance typed.",
|
||||
why_useful: "Prevents origin loss.",
|
||||
staleness: null,
|
||||
evidence: [{
|
||||
id: "evidence-1",
|
||||
kind: "message",
|
||||
entry_range: [10, 10],
|
||||
origin,
|
||||
excerpt: null,
|
||||
summary: "bounded summary",
|
||||
}],
|
||||
source_refs: [{
|
||||
session_id: "session-1",
|
||||
segment_id: "segment-1",
|
||||
entry_range: [10, 10],
|
||||
evidence_id: "evidence-1",
|
||||
origin,
|
||||
evidence_kind: "message",
|
||||
label: "source",
|
||||
summary: null,
|
||||
}],
|
||||
},
|
||||
}],
|
||||
diagnostics: [],
|
||||
};
|
||||
}
|
||||
|
||||
Deno.test("Memory document response requires the generated DTO fields", () => {
|
||||
assertEquals(
|
||||
parseMemoryDocumentResponse({
|
||||
body_md: "# Memory\n",
|
||||
created_at: "2026-09-01T00:00:00Z",
|
||||
updated_at: "2026-09-01T00:00:00Z",
|
||||
bytes: 9,
|
||||
record_source: "sqlite_workspace_authority.memory_document",
|
||||
}).bytes,
|
||||
9,
|
||||
);
|
||||
assertThrows(
|
||||
() => parseMemoryDocumentResponse({ body_md: "# Memory\n" }),
|
||||
"missing a required field",
|
||||
);
|
||||
});
|
||||
|
||||
for (
|
||||
const [kind, fields] of [
|
||||
["human_input", { account_id: "account-1" }],
|
||||
[
|
||||
"worker_input",
|
||||
{
|
||||
workspace_id: "workspace-1",
|
||||
runtime_id: "runtime-1",
|
||||
worker_id: "worker-1",
|
||||
},
|
||||
],
|
||||
["model_output", { runtime_id: "runtime-1", worker_id: "worker-1" }],
|
||||
["tool_output", { runtime_id: "runtime-1", worker_id: "worker-1" }],
|
||||
["legacy_unknown", {}],
|
||||
] as const
|
||||
) {
|
||||
Deno.test(`Memory staging parser preserves ${kind} origin`, () => {
|
||||
const parsed = parseMemoryStagingListResponse(fixture({ kind, ...fields }));
|
||||
assertEquals(parsed.items[0].record.evidence[0].origin, {
|
||||
kind,
|
||||
...fields,
|
||||
});
|
||||
assertEquals(parsed.items[0].record.source_refs[0].origin, {
|
||||
kind,
|
||||
...fields,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Deno.test("Memory staging parser preserves Flow origin fields", () => {
|
||||
const origin = {
|
||||
kind: "flow_instruction" as const,
|
||||
workspace_id: "workspace-1",
|
||||
runtime_id: "runtime-1",
|
||||
worker_id: "worker-1",
|
||||
flow_selector: "builtin:coder-review",
|
||||
flow_definition_id: "flow-1",
|
||||
flow_definition_revision: 7,
|
||||
};
|
||||
const parsed = parseMemoryStagingListResponse(fixture(origin));
|
||||
assertEquals(parsed.items[0].record.source_refs[0].origin, origin);
|
||||
});
|
||||
|
||||
Deno.test("Memory staging parser rejects unknown or newer origin shapes", () => {
|
||||
assertThrows(
|
||||
() => parseMemoryStagingListResponse(fixture({ kind: "future_origin" })),
|
||||
"unknown Memory evidence origin kind",
|
||||
);
|
||||
assertThrows(
|
||||
() =>
|
||||
parseMemoryStagingListResponse(
|
||||
fixture({ kind: "human_input", future_field: "must not be accepted" }),
|
||||
),
|
||||
"unknown field",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("Memory staging parser rejects malformed records and unbounded origins", () => {
|
||||
const malformed = fixture({ kind: "legacy_unknown" });
|
||||
malformed.items[0].record.source_refs[0].entry_range = [1] as unknown as [
|
||||
number,
|
||||
number,
|
||||
];
|
||||
assertThrows(
|
||||
() => parseMemoryStagingListResponse(malformed),
|
||||
"two-item entry range",
|
||||
);
|
||||
assertThrows(
|
||||
() =>
|
||||
parseMemoryStagingListResponse(
|
||||
fixture({ kind: "worker_input", worker_id: "x".repeat(513) }),
|
||||
),
|
||||
"exceeds the Memory origin limit",
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user