Commit Graph

267 Commits

Author SHA1 Message Date
Maurice 658536c217 fix(trips): a member another member removed cannot rejoin through the invite link
Removing a member deleted their membership row only, and the invite link
checked nothing but its own token, so whoever kept the link walked straight
back in (GHSA-wr4c-964p-39jg).

trip_member_removals now notes who another member took off a trip. The invite
link refuses them on preview and on accept with a 403 that says so, old link
or regenerated, and the join page shows that instead of the join button.
Adding them back by name, or through a trip-bound invite or a plugin acting
for a member manager, lifts the note. Leaving a trip yourself leaves none, so
the link keeps working for whoever left and for everybody else.
2026-10-24 08:29:51 +02:00
Maurice 82b8eb1067 feat(studio): book themes with palette, font pairing and motif set 2026-10-23 22:08:13 +00:00
Maurice 31dba65906 fix: the last red checks after the tour, home and place picture merges
A GPX file that mixes routes and tracks gives each its own text: the pieces
are matched to the reader's rows by kind, routes first as the reader gives
them, where they were paired by index and a route got a track's points. The
place pictures reach the network through a small repo instead of the api
module, the wanderer parity case and the tool catalog test allow wanderer,
and the knip and theme baselines follow the lower counts.
2026-10-23 22:40:14 +02:00
Maurice cd60d4d859 feat(tours): assistant takes found places into saved tours and route files from the chat
P6: insert_tour_stop and remove_tour_stop take a place into a saved hike or
city walk as one stop and out again, each the other's undo, with change
cards in the chat. The planner's insertion rule (poiInsertion) moved to
shared so the planner and the server place a stop the same way. "+ Tour" on
a found place holds it as a stop of the saved tour open in the planner.

P7: GPX, KML and KMZ files attached in the chat are read into their tracks
and each is held as a change that takes it in as a hike through
import_tour_gpx, undoable per track; the GPX text stays on the held call.
2026-10-23 22:00:05 +02:00
Maurice 454fc1a5f4 feat(tours): round hikes, other ways of a leg and wanderer trails through the assistant
The assistant can now search a round hike of about a wished length from one
point and the router's other ways for one leg of a hike, with the planner's
own search, and propose each one as a hike card to compare and take. It can
also search the user's wanderer trails, preview one as a card, import it and
check a wanderer tour against its source.

The round search, its loop geometry, the test whether two lines are one way,
the other ways among a router's offers and the pins that hold a chosen way
move to @trek/shared; the planner and the road trip use them from there.
2026-10-23 21:59:43 +02:00
Maurice 767d91146a feat(tours): the assistant edits stations, reverses and closes tours, sets breaks, colour and copies, and writes ski and paddle plans
City walk changes take edit, reverse and close_loop; edits alone keep the
saved way and never ask the router. update_tour turns a hike round or closes
its loop with reshape. New MCP tools mirror the Tours tab's REST writes and
reads: update_tour_details, set_tour_route_color, copy_tour,
classify_ski_track, set_paddle_plan, decide_paddle_obstacles,
restore_recorded_track, get_tour_trail_details and find_horse_routes, with
change cards, undo where the answer keeps the values before, and broadcast
parity cases.
2026-10-23 21:59:02 +02:00
Maurice 6e43490ba0 feat(assistant): plan and save every tour type, bike tours, runs, paddle and ski tours included
The assistant can now plan and save every type the Tours tab plans. A bike
tour or a run is proposed and created through the hike's own plan and save
(propose_tour, create_tour), routed on the server with the costing of its
profile, now kept once in shared and read by the planner as well. A paddle
or ski tour planned with plan_paddle_route or plan_ski_route is saved with
create_paddle_tour or create_ski_tour, and "Plan this" takes those plans,
bike tours and runs too. Every create has its card and its way back.
2026-10-23 21:01:13 +02:00
Maurice 5ccd4674ac fix(assistant): CI of the found places, kept notes and walk visits; a tour's answer shows no found places 2026-10-23 18:21:54 +02:00
Maurice bda07d6a3e feat(journey): the assistant designs the whole book as one proposal
design_book, revise_book_design and apply_book_design work on a draft, the
apply keeps the old book as a revision for undo, skips pages changed since,
and broadcasts once after the commit. Book level tools set the page setup,
duplicate pages and save layouts.
2026-10-22 21:34:15 +00:00
Maurice 809002adb3 feat(studio): change a book page by page over MCP, and pick its photos from the gallery 2026-10-22 21:34:14 +00:00
Maurice 42ce65708f fix(realtime): journey, atlas, vacay and collection writes in parity 2026-10-22 21:31:23 +00:00
Maurice a79c3778e7 fix(realtime): trip, member, chat, file, settings, tag and notification writes in parity 2026-10-22 21:30:39 +00:00
Maurice 8fe2ae80af fix(realtime): budget, packing and todo writes send what REST sends 2026-10-22 21:30:39 +00:00
Maurice 588ec8feb1 fix(realtime): road trip vias and day boundaries send the same events everywhere 2026-10-22 21:30:39 +00:00
Maurice dbb70fc525 fix(realtime): booking writes share one use case on REST and MCP 2026-10-22 21:30:39 +00:00
Maurice 2563afa582 fix(realtime): place writes over MCP and plugins send what REST sends 2026-10-22 21:30:39 +00:00
Maurice 0427ccc23b test(realtime): broadcast parity for every MCP write tool
A suite under tests/integration/broadcast-parity boots the app once per
group, runs a write tool through a real MCP session the way the
assistant attaches the registry, runs its REST twin on a twin scene, and
records every broadcast. Per case it checks the exact events in order and
count, that no MCP broadcast skips a socket (the asking tab hears its own
change), that nothing is sent inside a transaction, that every payload
parses with its registered schema, that REST sends the same sequence to
the same room and recipients, and that MCP's keys cover REST's.

A registry test lists the write tools as an MCP client sees them and
fails on a tool without a row, a stale row, or an exemption without a
reason; exemptions run too and must send nothing, and the tools still
waiting for a case are frozen in a list that may only shrink.

The new events the next changes send are registered now (member:updated,
todo:reordered, trips:changed, settings:updated, tags:changed,
notification:read-all, journey:updated, journey:deleted, atlas:changed);
the client keeps them apart as awaiting a sender, and the journey page
already leaves a journey that journey:deleted says is gone.
2026-10-22 21:30:39 +00:00
Maurice d24db65cb8 fix(assistant): green CI round, trip notes the assistant can keep, calmer change cards
- the assistant keeps facts for a trip (remember_for_trip) as a change card,
  applied into the user's trip notes; existing expenses can be changed
- change card fields as a few icon chips with the rest folded under Details,
  the day band marks only what changes, the week grid as compact tiles
- no map button on receipts of things that have no place on the map
- tests follow the composer's accessible name, the receipt's status icon and
  the GL map's container; the hanging stream helper honours an early abort
- Postgres probe: qualified columns in the upsert updates; entities restored
  to the generator's output; strict ratchet reads the SDK client as untyped
2026-10-22 21:30:26 +00:00
Maurice b64da26651 feat(assistant): trip assistant chat with your own AI connection
Adds an in-app assistant that reads and plans the open trip. Users connect
their own provider (Sign in with ChatGPT, or an Anthropic, OpenAI, Gemini or
OpenRouter key) or use the one the admin set up, including a custom
OpenAI-compatible server. It is an addon and only shows while a connection
is usable.

The chat runs on the existing MCP tools, pinned to the trip. Every change is
held as a change card the user applies or discards, and applied changes can
be undone. On the desktop it is a floating window that can be moved and
resized; on the phone it is fullscreen. Search results show as place cards
that can go onto a day, weather lookups as a strip, proposed places as
ghost markers on the map with a before/after switch, and an answer can be
shared into the trip chat. Booking files can be dropped in and are read
into change cards.
2026-10-22 21:30:12 +00:00
Maurice 31859651d7 feat(help): rewrite the help center for v5 with a Tours section, new pictures in every language, and fix the tour planner bugs it surfaced
Every help screen checked against the v5 redesign. Tours is documented for
the first time, as a screen of its own with twelve screens under it, and
notifications get help of their own. All pictures are taken again on a
realistic demo trip, with short loops where a still cannot show the motion.
The English is plain and every language is written naturally rather than
word for word.

The switcher shows two levels at most, a screen's overview lists its
sub-screens as cards above its guides, each wiki page is linked once, and
the help sidebar is wider.

Fixes found along the way: Save to Collection keeps a tour's route, a track
dropped on the places column opens the Tours import, tour Share on desktop,
the unsaved tour draft lives on the account, a route click no longer adds
an end point, Trip places leaves the trip's tours out, Escape closes a
stage's day menu, confirms before destructive deletes, DWD warnings sharing
one id kept apart, a booking's date moves with it, and smaller fixes across
notifications, bookings, the day plan, stays, Atlas, Collections, Vacay and
the admin screens.
2026-10-22 22:29:35 +02:00
Maurice 78366a9024 fix(tours): long rides save, the horse route store stays bounded
A ride sends its full line with heights and every horse route section's
own line besides, about fifty bytes a point, so a ride along a few
thousand points of bridleway hit the API's 100kb body ceiling and the
save failed with a 413. Creating and editing a tour are now measured
against TOUR_WRITE_MAX_BYTES (2 MB), like the book and list file
routes; every other route, the tour copy among them, keeps 100kb.

The horse routes kept in memory are now bounded by section points as
well as by count (64 answers, 500k points), so panning a dense map can
no longer grow the server by hundreds of megabytes.

A search that found no connection can be asked again on the view whose
routes are shown, and a ride with a recorded time shows the horseshoe
beside its riding time instead of footprints.
2026-10-20 15:11:25 +02:00
Maurice 48d573c60d refactor(trips): leaving a trip tidies its journal link in the roster's own repository
The departure port needed a global binding that every suite building TripMembersModule without AppModule lacked. The roster's repository now deletes the leaver's row on the trip's journal link itself (leaveJournalLink), inside the transaction that removes the member: no edge into journey, no DI, no test stub, and the eight suites the stub touched are back as they were.

The schema inventory tests count the two new tables and their NOT NULL columns, the legacy step map lists the migration as post-legacy, and the TJL scenes give their users numbered names so cases sharing one database do not collide.
2026-10-18 20:02:09 +02:00
Maurice 889e88c362 refactor(trips): the journal member choice in a hook of its own, the test seed without an await loop
useTripJournalLink had grown past the 120-line limit for a function. The member list (candidates per journal, roles, who is new) is now useJournalMemberChoice. What loaded is kept with the journal it belongs to, so a new pick shows nothing until its own people are in, without resetting state by hand. The integration test adds its trip members with Promise.all.
2026-10-18 17:55:41 +02:00
Maurice 80b6739362 feat(journey): a trip can link a journal and open it to the members it picks
A trip can now show one journal from the Journey addon as its own: any
journal its owner picks, and one journal on as many trips as they like.
The trip members chosen for the link get into the journal as viewers or
editors, see it in their own journal list and follow it live, for as long
as the link holds them and they stay on the trip.

That access is never copied into journey_contributors. It is read where
journal access is read: the contributor repository answers with the
stronger of a direct role and a link role, the journal list and the live
update recipients take the link members in, and the photo access check
goes the same way. Every one of those reads starts from linkReach, which
only counts members still on the linking trip, so unlinking, deselecting
someone and their leaving the trip all end exactly what the link gave.
A member who leaves is also taken off the link, so coming back later does
not quietly reopen the journal.

Linking or changing the members takes the journal's owner and the new
journal_link trip permission (trip owner by default); unlinking takes
either. /api/trips/:tripId/journal answers the link as each member may see
it, /candidates says who a journal would be opened to for the first time,
and four MCP tools do the same over the same service. A trip:journal:changed
event tells the trip to look again.
2026-10-18 15:59:33 +02:00
Maurice 593f839592 fix(journey): a timed entry takes its place in the day by its time
A new entry always went to the end of its day, so writing up 1 am after
2 am left the night out of order, and changing an entry's time never moved
it. A timed entry now lands before the first entry of its day that starts
later, on create and when its time or day changes; untimed entries still
go last, and the hand-made order of the others stays as it was. A save
that leaves time and day untouched keeps an entry where it was dragged.

Closes #2679
2026-10-18 13:21:51 +02:00
Maurice c378722d63 test(auth): read the disabled MFA state through the ORM, formatted 2026-10-18 12:11:41 +02:00
Jens Kowalsky a36019b7e2 fix(auth): allow backup codes for TOTP recovery 2026-10-18 11:47:10 +02:00
Maurice 3d7e8be9aa fix(oidc): a sign-in links to an account by email only when the address was not set by the account itself
An SSO login that matched no account by its subject took the account with the
same email address and linked the provider identity to it. That address proves
nothing when the account typed it in itself: anyone could sign up with, or
switch their profile to, a colleague's address and receive the colleague's
first SSO login. The provider switch branch (#2110) relinked a row on any email
match without even asking for email_verified, and also for another subject of
the same provider.

users.email_self_asserted remembers an address the account set itself: signup
(not the first account) and an email change in the profile settings set it, an
admin entering a new address clears it, and accounts the provider creates start
without it. Existing accounts keep linking, except those whose address moved
away from the registered one without an admin editing it. A login by email now
needs a verified address and an unmarked account, and is refused for another
subject of the same issuer, with email_link_blocked and a translated message on
the login screen. An email change in the profile settings writes a
user.email_change audit row.

Refs #2110
2026-10-12 17:06:52 +02:00
Maurice 36b8baf259 fix(atlas): removing the last region keeps a country the user marked visited itself
Marking a region wrote its country with source 'manual', the same as ticking
the country off by hand, and removing the last region then took the country
away whatever its origin, Dawarich confirmations included. A country that
only comes in through a region is now stored as 'region', marking it visited
afterwards turns it into the user's own mark, and the cascade in unmarkRegion
only runs for a country without such a mark. Readers still see 'manual' for
those rows, so the country detail, the MCP resource and the plugin RPC answer
as before. Rows written before this change stay 'manual' and keep their
country.

REST, MCP and plugin RPC share the service. The atlas popup on desktop and
phone now asks the server whether the country is still marked before it
drops it from the map instead of guessing.

Refs #2597
2026-10-12 09:55:52 +02:00
Maurice d2e044869a fix(days): a day without a date keeps no date when it is moved, so it can go to the front of a dated trip
Reordering handed out the dates by position, so moving a day added without a
date to the front gave it the first date and took the date of the last dated
day. The undated row stayed at the end and the move looked like it did
nothing, by drag and by the arrows alike. The dates now go to the dated days
only, in their new order; the rule lives in shared so the server, the
planner's optimistic list and the MCP tool apply the same one.
2026-10-10 21:34:51 +02:00
Maurice 724c88c201 style: format server and shared with prettier
A mechanical prettier --write over server/src, server/tests, server/scripts
and shared/src with the workspaces' .prettierrc; nothing but layout and
import order changes, and every file keeps the same set of imports. shared
is formatted throughout and its format baseline is empty. The server
baseline keeps the 35 files that Prettier would grow past their lint:size
limit (they need a split first, which is not a formatting change), and the
generated entity files stay as their generator writes them.
2026-10-10 21:23:37 +02:00
Maurice 8187a423e2 fix(admin): an invite for a trip that does not exist answers 404 instead of an empty 201
createInvite returned { error: 'Trip not found', status: 404 } and the admin controller never looked at it: POST /api/admin/invites with an unknown trip_id answered 201 with an empty body and wrote an admin.invite_create audit row for resource 'undefined'. The service throws the DomainError now, so the route answers 404 { error: 'Trip not found' } and writes neither the invite nor the audit row.
2026-10-10 20:16:14 +02:00
Maurice fa0edfd714 test(server): suites record broadcasts on a FakeRealtimeService instead of mocking src/websocket
A hand-built service gets a FakeRealtimeService where it took a RealtimeService, a Nest testing module overrides the provider, and a suite over buildApp() spies on the container's instance (spyOnRealtime). The 167 vi.mock factories on src/websocket are gone, RealtimeService reads the socket registry in ws-state.ts directly, and the re-export stub src/websocket.ts is deleted. The facade's own suite now checks what reaches a socket instead of which module function it called.
2026-10-10 20:16:10 +02:00
Maurice 00cbc5aae3 test(server): src/config takes fixed test values from the setup file, so suites stop mocking it
tests/setup.ts mocks src/config once with tests/helpers/test-config.ts. A suite that needs other values calls overrideTestConfig(), and the setup file restores the defaults after every case. The 191 per-file factories are gone; config.test.ts takes the real module through vi.unmock, and the few suites that signed tokens with their own literal secret read it from TEST_CONFIG.
2026-10-10 20:16:10 +02:00
Maurice 93c2bf0740 test(places): the import and image error cases stub the DomainError the service throws now 2026-10-10 20:16:09 +02:00
Maurice e40bc71e9f refactor(plugins): the plugin RPC kit moves out of the plugins domain into src/nest-rpc
Every domain's *.rpc.ts was written against rpc-kit, rpc-params, rpc-errors
and PluginGuards, and every domain module imported PluginGuardsModule, all
of it inside src/nest/plugins, while PluginsRuntimeModule imports those same
domains so their handlers exist at boot. That made the plugins domain part
of every cycle. The kit imports no domain but the protocol tables and
permissions/addons for the guards, so it moves to src/nest-rpc beside
src/nest-mcp, with its tests, its README and its extraction rule; the
runtime module keeps its domain imports, which the boot-time coverage check
of the wire surface needs in any subset app.
2026-10-10 20:15:58 +02:00
Maurice 37af6f4ff9 test(server): seed bookings and places the way the services store them
The converted specs wrote accommodation_id as a plain id, while the
services store the legacy '<id>.0' text, and wrote read-only foreign key
twins instead of their relations. The files e2e schema gains the trip
columns the entity fills, and the idempotency spec seeds a real user.
2026-10-08 20:22:16 +02:00
Maurice 115f76ffae style(server): re-indent the user deletion assertions in the admin spec
The conversion left 23 assertions of the user deletion case at column 0 in the middle of its indented body.
2026-10-08 18:47:25 +02:00
Maurice 61598e070a test(server): seed the admin integration suite through the factories
Converts the raw prepare() fixtures and reads in the admin integration
suite to the ORM factories and readers. The user-deletion case that seeds
one row per foreign key pointing at the departing user now writes each of
them through insertRow and checks what survived through findRow; the
expiry timestamps that used datetime('now', ...) are computed with dbNow.
The OAuth client in that case uses the same string for its row id and its
public client_id, so the token and consent rows reference it the same way
whichever column the relation keys on.
2026-10-08 18:47:12 +02:00
Maurice cfa3d10dfa test(server): seed the trips integration suite through the factories
Converts the raw prepare() fixtures and reads in the trips integration
suite to the ORM factories and readers: archived trips, permission rows,
memberships, the copy's source rows (tagged places, assignments, to-dos,
the budget category order, private packing items) and every read of what a
request left behind go through insertRow, updateRows, findRow and findRows.
The copied place tags are counted per place through the place_tags
relation instead of a raw join.
2026-10-08 18:47:12 +02:00
Maurice 3b1d30b587 test(server): seed the places integration and trip members repository specs through the factories
Converts the raw prepare() fixtures and reads in the places integration
suite and the trip members repository spec to the ORM factories and
readers. Tags, categories and linked expenses are seeded through makeTag,
makeCategory and makeBudgetItem; the member rows the roster cases backdate
or point at an inviter are written through insertRow and updateRows. The
legacy UNION and join statements the roster reads are held to stay raw
behind allow markers.
2026-10-08 18:46:51 +02:00
Maurice 63bcf61a2b test(server): seed the system notice, packing e2e and synology specs through the factories
Converts the raw prepare() fixtures and reads in the system notices
integration suite, the packing e2e and the synology memories integration
suite to the ORM factories and readers.

The packing e2e moves from its hand-rolled tables to the migrated snapshot,
seeds its three users with the ids their session cookies are signed for,
and drops the raw canAccessTrip on its database mock, which nothing reads
any more. The synology suite now shares one ORM handle for the photo
registration helpers and the readers instead of declaring a second one
halfway down the file.
2026-10-08 18:46:50 +02:00
Maurice 2a9730d7c5 test(server): seed the demo repository, transports MCP and immich specs through the factories
Converts the raw prepare() fixtures and reads in the demo repository spec,
the transports MCP spec and the immich integration suite to the ORM
factories and readers.

Where a case compared a narrow SELECT with toEqual, it now picks the same
columns off the row the reader returns, so the comparison stays exact
rather than loosening to a partial match. The immich album link cases seed
their photos through addTripPhoto and addAlbumLink, which register the
provider asset the same way the raw INSERT OR IGNORE did.
2026-10-08 18:46:50 +02:00
Maurice 635170a14f test(server): seed the share integration and passkey specs through the factories
Converts the raw prepare() fixtures and reads in the share integration
suite and the passkey service spec to the ORM factories and readers.

The passkey credential helper takes a typed set of overrides instead of a
loose record, so a fixture can no longer hand the insert a column the
entity does not map. The two credentials the listing case backdates are
written with their created_at through insertRows, blobs as Buffers.
2026-10-08 18:46:49 +02:00
Maurice 855c4e3ba3 test(server): seed the collections, storage registry, transit, token and immich specs through the factories
Converts the raw prepare() fixtures and reads in the collections e2e, the
storage registry service spec, the transit MCP spec, the MCP token
repository spec and the immich memories integration suite to the ORM
factories and readers.

The collections e2e drops the raw canAccessTrip on its database mock, which
nothing reads any more. Reads that used to join trip_photos to trek_photos
in SQL resolve the photo row through the readers instead, and the orphan
trip photo of IMMICH-057 is still written with foreign keys switched off,
now through the ORM on the same connection.
2026-10-08 18:46:48 +02:00
Maurice e3982a8e2a test(server): seed the repository, MCP and e2e specs through the factories
Converts the raw prepare() fixtures and reads in the next set of specs to
the ORM factories and readers: the password reset, webauthn, dawarich,
budget, to-do and vacay repository specs, the journey, settings, addons and
share MCP specs, the plugin POI and operator egress specs, the feeds,
storage stats and query helper services, and the packing, collections,
day reorder, leg mode, upload context and trip access integration suites.

The notifications and google quota e2e suites now seed their users through
makeUser with pinned ids, and the notifications suite moves from hand-rolled
tables to the migrated snapshot. Legacy statements that serve as a parity
oracle and the raw rows the primary-relation spike holds toObject() against
stay raw, each with an allow marker naming why.
2026-10-08 18:46:47 +02:00
Maurice 894a5ee781 test(server): seed the small leftover specs through the repositories
Converts the raw prepare() reads and writes in the share, airtrail, files,
todo, dev-link, vacay, tours, bucket list, unit-of-work and plugin guard
specs to the ORM factories and readers, so they go through the entities the
app uses instead of hand-written SQLite.

The vacay e2e moves from hand-rolled tables to the migrated snapshot, with
its user pinned to the id its session cookie is signed for. Statements that
are raw by nature keep the allow marker with the reason: the legacy oracle
of the bucket list parity spec, the PRAGMA probe of the trek_photos
migration, the unmapped probe table of the route import rollback, the
behind-the-ORM reads of the entity insert probes, and seedUser, which also
serves the hand-rolled users table of createTempDb. Comments that quoted a
prepare() call are reworded so the ratchet no longer counts them.
2026-10-08 18:46:47 +02:00
Maurice 58cdaf828c test(server): seed the days integration, OIDC and share service specs through the factories
The days integration suite, the OIDC service suite and the share service
suite write their fixtures and read back their effects through the ORM
factories: invite tokens through makeInviteToken, user column edits
through updateRows, bookings, stays, packing items and messages through
insertRow on their entities. Two more database mocks drop their dead
canAccessTrip/isOwner stand-ins.

The OIDC identity-linking cases keep their raw SELECT * of the user row,
marked: they assert that no column of the table moved, including any the
entity might not map.
2026-10-08 18:46:38 +02:00
Maurice a3781ed27b test(server): seed the collab, budget, MCP and OAuth token specs through the factories
The collab and budget e2e suites, the MCP integration suite, the budget
MCP tools and the OAuth token repository suite seed and read through the
ORM factories. The collab e2e suite moves off its hand-rolled CREATE
TABLE set onto the migrated snapshot, the way the factories guide asks of
a suite that converts: an entity reads and writes every mapped column,
which a trimmed table does not have. Its users are seeded with the ids
the session cookies are signed for.

The recursive chain statement in the token suite stays raw, marked as the
legacy oracle collectChainIds is held to.
2026-10-08 18:46:37 +02:00
Maurice 2cf853e504 test(server): seed the atlas and notes tools, day removal, doc sync config and plugin runtime specs through the factories
The atlas and notes MCP tool suites, day removal, the doc sync config
service and the plugin runtime conversion suite build their fixtures and
read back what a call left through the ORM factories. Helpers that used
to answer synchronously off the handle (dayRows, range, the reservation
and boundary readers) are async now and every caller awaits them; a
reader that answered undefined for a gone row still does, so the
toBeUndefined/toBeDefined checks keep their meaning.

The one statement left raw rewinds sqlite_sequence, the engine's own
autoincrement table, which no entity maps; it is marked.
2026-10-08 18:46:37 +02:00