feat(durable): Tx.submission(id) reads a submission record by ID

This commit is contained in:
Mario Zechner 2026-11-04 21:57:57 +00:00
parent 5ff8c02708
commit 9ca159208a
5 changed files with 24 additions and 1 deletions

View file

@ -2,6 +2,10 @@
## [Unreleased]
### Added
- `Tx.submission(id)` reads a committed submission record by ID, so a tool can find the entries of its run's inputs (`pi.live.run.inputs`) without scanning the transcript.
### Changed
- `SqliteStorage.commit()` costs a few SQL statements per call instead of about eight per batch: it reads the check state of all batches up front, checks them in memory, and writes the accepted batches per table in multi-row statements (rows as one JSON array parameter; rows with much text bound column by column), so a group commit over a remote SQLite takes a few round trips, not hundreds. A stored document record that cannot be parsed now rejects the call as a storage failure instead of refusing the batch.

View file

@ -1439,6 +1439,7 @@ interface Tx {
scanConversations(query: ConversationQuery, limit: number, cursor?: Cursor): Promise<Page<ConversationRecord, Cursor>>;
scanEntries(query: EntryQuery, limit: number, cursor?: Cursor): Promise<Page<EntryRecord, Cursor>>;
scanTasks(query: TaskQuery, limit: number, cursor?: Cursor): Promise<Page<TaskRecord<JsonValue, JsonValue, JsonValue>, Cursor>>;
submission(id: SubmissionId): Promise<SubmissionRecord | undefined>;
submissionByRequest(conversationId: ConversationId, requestId: string): Promise<SubmissionRecord | undefined>;
createConversation(options: { readonly ownership: ConversationOwnership }): Promise<ConversationRecord>;

View file

@ -338,7 +338,7 @@ export class Transaction implements Tx {
return this.#read("scanTasks", () => this.#host.storage.scanTasks(query, limit, cursor, this.#context));
}
/** Internal: committed submission record. */
/** Committed submission record. */
submission(id: SubmissionId): Promise<SubmissionRecord | undefined> {
return this.#read("submission", () => this.#host.storage.submission(id, this.#context));
}

View file

@ -1007,6 +1007,8 @@ export interface Tx {
limit: number,
cursor?: Cursor,
): Promise<Page<TaskRecord<JsonValue, JsonValue, JsonValue>, Cursor>>;
/** Committed submission record, such as one of `pi.live.run.inputs`; a placed input's `entry` is its transcript entry. */
submission(id: SubmissionId): Promise<SubmissionRecord | undefined>;
/** Committed submission with a conversation-scoped request ID. */
submissionByRequest(conversationId: ConversationId, requestId: string): Promise<SubmissionRecord | undefined>;

View file

@ -282,6 +282,22 @@ describe("submit inside the caller's commit", () => {
await harness.close(context);
});
it("reads a submission by ID in a commit, with the entry its input was placed at", async () => {
const setup = chatSetup();
setup.faux.setResponses([fauxAssistantMessage([fauxText("answer")])]);
const { harness, root } = await openChat(new ControlledStorage(), setup);
const id = await harness.commit((tx) => root.submit(tx, { type: "input", content: "hi" }), context);
await settledStatus(harness, id);
const found = await harness.commit(async (tx) => {
const submission = await tx.submission(id);
const entry = submission?.entry === undefined ? undefined : await tx.entry(submission.entry);
return { status: submission?.status, conversationId: entry?.conversationId, kind: entry?.kind };
}, context);
expect(found).toEqual({ status: "done", conversationId: root.id, kind: "pi.user" });
expect(await harness.commit((tx) => tx.submission(999_999 as SubmissionId), context)).toBeUndefined();
await harness.close(context);
});
// #11149
it("queues a model-visible write while busy and places it at the next boundary", async () => {
const setup = chatSetup();