fix(environment): answer a missing entity with a 2xx null only for reads

A served write of a missing entity also looked at the write tool's own
recordings, so a delete that recorded 200 null for a successful
deletion answered a missing target with success. A 2xx null is now an
absence only when the call is a read, as the gate takes it from item
reads; a write still answers with a recorded 400, 404 or 410, or is
refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Edgar ArakelyanandClaude Opus 5.5 committed 2026-10-20 16:35:52 -07:00
1 parent b6c3b168f6
commit f2b95351d4
2 files changed
+21 -2

No files matched your search

@@ -264,7 +264,9 @@ export const missingAnswer = (
readonly id: string;
},
): Result<StateAnswer, AnalysisError> => {
// The write gate takes a 2xx JSON null as an absence too, so a delete it confirmed that way can be answered.
// A read also answers with a 2xx JSON null, the absence the write gate accepts; a write never does, as a 2xx would
// report success for an entity that is not there.
const nullAbsence = tool.method === "GET";
const latestAbsence = (ordinals: readonly number[]) =>
ordinals
.map((ordinal) => serving.source.exchanges.get(ordinal))
@@ -272,7 +274,8 @@ export const missingAnswer = (
(exchange): exchange is RecordedExchange =>
exchange !== undefined &&
(isAbsence(exchange.status) ||
(exchange.status !== null &&
(nullAbsence &&
exchange.status !== null &&
exchange.status >= 200 &&
exchange.status < 300 &&
recordedJson(exchange) === null)),
@@ -472,4 +472,20 @@ describe("missing entities after a delete", () => {
state: { view: "missing", id: "1" },
});
});
it("never answers a write of a missing entity with success, even when the write recorded a 2xx null", () => {
const books = servedFrom([
...BOOK_INIT_ROWS,
["DELETE", "/api/books/2", null, 200],
["GET", "/api/books/2", BOOK_NOT_FOUND, 404],
]);
expect(books.state.writes.get("delete_books_by_id")).toMatchObject({
served: true,
});
expect(books.answer("delete_books_by_id", { id: "9" })).toMatchObject({
status: 404,
body: BOOK_NOT_FOUND,
state: { view: "missing", id: "9" },
});
});
});