fix(environment): create in a held collection when an Evidence spelling fits the call equally

Known collection spellings now list the collections holding entities first, so a create whose argument fits a held collection and an emptied or Evidence-only spelling equally goes to the held one. A create after loading `/api/orgs/a%63me1/books` lands in that collection and reads back, rather than going to the Evidence's emptied `%61cme1` spelling with a duplicate id.

The verdicts over the 50 captures are unchanged plan by plan, and the smoke applies all 31 served write tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Edgar ArakelyanandClaude Opus 5.5 committed 2026-10-20 23:34:15 -07:00
1 parent fe8ab58483
commit 73c0e9b50a
5 files changed
+39 -4

No files matched your search

@@ -270,7 +270,8 @@ A create in a collection held under more than one spelling (`acme1` and
`%61cme1`) goes to the one the call spells verbatim, as item calls choose their
entity; a collection keeps its spelling after its last entity is deleted. A
reset or load knows the spellings of the Evidence's state and of the state it
installs, never those of a state it replaced. `data/add` refuses a document that `data/get` would write back past the
installs, never those of a state it replaced; when two spellings fit a call
equally, the one holding entities wins. `data/add` refuses a document that `data/get` would write back past the
32 MiB intake limit once it adds the members a document may omit, such as
`types` and the state Evidence id.
@@ -253,3 +253,8 @@ collection spellings, so a create in a collection the loaded state does not
hold keeps its recorded spelling. Each new test fails on the previous head. The
verdicts over the 50 captures are unchanged plan by plan, the smoke applies all
31 served write tools, and the gates passed again.
A ninth round made a create prefer a collection the state holds over an equally
fitting spelling known only from the Evidence, so a create after a load lands in
the loaded collection and reads back. Its test fails on the previous head; the
verdicts and the smoke are unchanged and the gates passed again.
@@ -288,9 +288,14 @@ export class EnvironmentStateStore {
return this.#typeCollections.get(type) ?? [];
}
/** Every collection spelling a type's entities have been held under since the last reset or load, including ones that have since emptied. */
/** Every collection spelling a type's entities have been held under since the last reset or load, or the Evidence's state holds: those holding entities first. */
knownCollectionsOf(type: string): readonly string[] {
return this.#knownCollections.get(type) ?? [];
return [
...new Set([
...this.collectionsOf(type),
...(this.#knownCollections.get(type) ?? []),
]),
];
}
document(tool: string): HeldDocument | undefined {
@@ -567,6 +567,30 @@ describe("values the state started with", () => {
expect(create("my%20org1", "Epsilon")).toBe("/api/orgs/my%20org1/books");
});
it("creates in the loaded state's collection when the Evidence spells the same one differently", () => {
const { books, create } = twoOrgs("%61cme1", "acme1");
const { rea_state: held } = books.store.snapshot();
expect(
books.store.replace({
rea_state: {
version: held.version,
entities: held.entities.map((entity) =>
entity.collection.includes("%61cme1")
? {
...entity,
collection: entity.collection.replace("%61cme1", "a%63me1"),
record_ordinals: [],
}
: entity,
),
documents: held.documents,
},
}).ok,
).toBe(true);
// Both spellings decode to the argument; the one holding books wins over the Evidence's emptied one.
expect(create("acme1", "Epsilon")).toBe("/api/orgs/a%63me1/books");
});
it("forgets the spellings of a loaded state once reset", () => {
const { books, create } = twoOrgs("%61cme1", "acme1");
const { rea_state: held } = books.store.snapshot();
@@ -246,7 +246,7 @@ const createdCollection = (
): string | undefined => {
const segments = callSegments(request.tool, request.args);
if (segments === undefined) return undefined;
// The spelling the call supplies verbatim wins, as item calls choose their entity; an emptied collection keeps its spelling.
// The spelling the call supplies verbatim wins, as item calls choose their entity, then the first known: held ones lead.
let held: string | undefined;
let verbatim = -1;
for (const collection of serving.store.knownCollectionsOf(