@push.rocks/smartdb 5.4.0 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartdb",
3
- "version": "5.4.0",
3
+ "version": "5.5.0",
4
4
  "private": false,
5
5
  "description": "A MongoDB-compatible embedded database server with wire protocol support, backed by a high-performance Rust engine.",
6
6
  "exports": {
@@ -15,10 +15,10 @@
15
15
  "@api.global/typedserver": "^8.4.6",
16
16
  "@design.estate/dees-element": "^2.2.4",
17
17
  "@git.zone/tsbuild": "^4.4.2",
18
- "@git.zone/tsbundle": "^2.11.4",
19
- "@git.zone/tsrust": "^1.10.3",
18
+ "@git.zone/tsbundle": "^2.13.0",
19
+ "@git.zone/tsrust": "^1.10.4",
20
20
  "@git.zone/tstest": "^4.0.0",
21
- "@types/node": "26.2.0",
21
+ "@types/node": "26.4.1",
22
22
  "mongodb": "^7.5.0"
23
23
  },
24
24
  "dependencies": {
package/readme.hints.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # smartdb hints
2
2
 
3
+ ## Single-Node Concern Profile, Namespace Clock, and Deadlines (5.5.0)
4
+
5
+ - `rustdb-commands/src/concerns.rs` is the sole owner of `readConcern`, `writeConcern`, and `maxTimeMS` validation. It runs in the router before authentication, sessions, permits, or gates. The profile is stated for one voting node: fsynced acknowledgement satisfies `w: 1`, `w: "majority"`, and `j: true`; `w: N > 1` is `UnsatisfiableWriteConcern` (100), tag sets are `UnknownReplWriteConcern` (79), cluster-time read concerns and misplaced fields are `InvalidOptions` (72), malformed deadlines are `BadValue` (2). `j: true` on `MemoryStorageAdapter` is `BadValue` through `StorageAdapter::journaled_writes()`. The driver sends a transaction's read concern on whichever statement starts the transaction, so a transaction-starting insert may carry one. Never accept a concern by widening an allowlist; every accepted value must map to a guarantee the engine actually provides.
6
+ - `rustdb-commands/src/clock.rs` (`CommandContext::namespace_clock`) is the publication clock. Every in-process publication that changes visible namespace contents records `publish_namespace` while it still holds the namespace write lock (point writes, transaction commit completion, `create`, `drop`, `renameCollection`, `$out`, `$merge`, oplog revert) or `publish_database` (`dropDatabase`, in-process durable replacement through `invalidate_database_runtime`, and cross-process publication observed through `invalidate_database_runtime_for_epoch`). `$out` publishes right after it drops the old contents and again after the refill, so a failed refill still invalidates pinned snapshots. Index DDL does not change documents and does not publish. Adding a new mutation path without a publication is a snapshot-consistency bug.
7
+ - Transactions pin `TransactionState::snapshot_sequence` when they materialize their first namespace. `transactions::load_transaction_docs_locked` requires the caller to hold the namespace write lock (insert/update/delete already do); `load_transaction_docs` acquires it for readers (`find`, `count`, `distinct`). A namespace whose `last_publication` exceeds the pinned sequence fails with `SnapshotUnavailable` (246); the router labels it `TransientTransactionError` so `withTransaction` restarts. This turns lazy per-namespace materialization into one database-wide snapshot without a second data copy; Storage VNext MVCC read timestamps replace the materialized copy itself.
8
+ - Sessions retain `last_outcome` (`TransactionOutcome::Committed`/`Aborted` per transaction number) through `SessionEngine::finish_transaction_if_bound`. `commitTransaction`/`abortTransaction` without an active binding replay that outcome: repeated commit is `ok`, abort-after-commit is `TransactionCommitted` (256), commit-after-abort is `NoSuchTransaction` (251), and an older `txnNumber` on start is `TransactionTooOld` (225). Outcomes are process-local; after a crash the outcome of an in-flight commit is discoverable only from the data, which is why callers must journal their own attempts inside the transaction.
9
+ - `rustdb-commands/src/deadline.rs` holds the `maxTimeMS` deadline in a tokio task-local set by `CommandRouter::route_with_response_policy`. Checks are cooperative: admission, `deadline::wait_bounded` inside `acquire_write_lock`/`acquire_maintenance_write_gate`, `wait_until_deadline` around the maintenance read lease, `check()` after materialization in readers, `check_every` in insert/delete loops, and one final `check()` before `apply_write_batch_with_completion` in commit. Handler futures are not cancellation-safe, so never wrap a handler in `tokio::time::timeout`; add a check at the next safe point instead. `SMARTDB_TEST_OPERATION_DELAY=<command>:<ms>` (test-support builds only) injects a pre-dispatch delay for deterministic expiry tests.
10
+ - Error labels are derived from the response code in `attach_error_labels` so pre-dispatch rejections and handler errors label identically: transaction statements with 112/246/251 carry `TransientTransactionError`; `commitTransaction` with 50 or 64 carries `UnknownTransactionCommitResult`.
11
+
3
12
  ## Storage VNext A0 Committed Log
4
13
 
5
14
  - `rustdb-kernel` is a fixture-only, non-serving A0 durability slice. It is not linked into the released `rustdb` binary and provides no BSON, CRUD, MVCC, concern, deadline, migration, replication, or availability behavior.
package/readme.md CHANGED
@@ -354,7 +354,7 @@ Legacy v0 JSON collections are converted into hidden sibling staging directories
354
354
 
355
355
  Persisted users also carry a random principal identity and a monotonic generation. SmartDB reloads and resolves that identity for every authenticated command, so password or role changes take effect immediately and stale sockets are rejected. Deleting and recreating the same username creates a different principal; an old connection cannot inherit the replacement user's authority. Cross-process user updates are serialized through the persisted users-file lock.
356
356
 
357
- Single-node transactions are supported through official MongoDB driver sessions. `find`, `count`, `distinct`, `insert`, `update`, `findAndModify`, and `delete` use the transaction snapshot and buffered write set; `commitTransaction` applies that write set with conflict checks, and `abortTransaction` discards it. After applicable authentication, authorization, and allocation-policy checks, collection, database, and index DDL, user-management mutations, and aggregate pipelines ending in `$out` or `$merge` reject transaction envelopes with `OperationNotSupportedInTransaction` (code 263) before session or transaction creation, maintenance gates, or mutation. Live logical sessions remain resumable across socket disconnects. Bounded background cleanup aborts expired transactions, removes expired sessions, and releases publication leases; explicit `endSessions` and `killSessions` do the same for their active transactions.
357
+ Single-node transactions are supported through official MongoDB driver sessions. `find`, `count`, `distinct`, `insert`, `update`, `findAndModify`, and `delete` use the transaction snapshot and buffered write set; `commitTransaction` applies that write set with conflict checks, and `abortTransaction` discards it. The snapshot is database-wide: the first namespace a transaction materializes pins a publication sequence on the namespace clock, and any later namespace that was published after that sequence fails closed with `SnapshotUnavailable` (code 246) and the `TransientTransactionError` label so the driver restarts the transaction on fresh state; write-write conflicts surface at commit as `WriteConflict` (code 112) with the same label. Each session remembers the outcome of its last transaction number: a repeated `commitTransaction` replays `ok`, `abortTransaction` after a commit reports `TransactionCommitted` (code 256), `commitTransaction` after an abort reports `NoSuchTransaction` (code 251), and starting a transaction with an older number reports `TransactionTooOld` (code 225). After applicable authentication, authorization, and allocation-policy checks, collection, database, and index DDL, user-management mutations, and aggregate pipelines ending in `$out` or `$merge` reject transaction envelopes with `OperationNotSupportedInTransaction` (code 263) before session or transaction creation, maintenance gates, or mutation. Live logical sessions remain resumable across socket disconnects. Bounded background cleanup aborts expired transactions, removes expired sessions, and releases publication leases; explicit `endSessions` and `killSessions` do the same for their active transactions.
358
358
 
359
359
  ### Durable Database Publication Holds
360
360
 
@@ -497,6 +497,7 @@ await client.db('admin').command({ usersInfo: 'reader' });
497
497
  | `port` | `number` | Actual bound port while running; otherwise the configured port (TCP mode) |
498
498
  | `host` | `string` | Configured host (TCP mode) |
499
499
  | `socketPath` | `string \| undefined` | Socket path (socket mode) |
500
+ | `processId` | `number \| undefined` | Operating-system pid of the spawned Rust engine while it runs as a child process (for crash and kill tests) |
500
501
  | `getMetrics()` | `Promise<ISmartDbMetrics>` | Server metrics (db/collection counts, sessions, transactions, auth, uptime) |
501
502
  | `getOpLog(params?)` | `Promise<IOpLogResult>` | Query oplog entries with optional filters |
502
503
  | `getOpLogStats()` | `Promise<IOpLogStats>` | Aggregate oplog statistics |
@@ -999,22 +1000,56 @@ than a claim about exact allocator heap usage.
999
1000
 
1000
1001
  ### Read and Write Concerns and Wire Deadlines
1001
1002
 
1002
- SmartDB 5.x does not implement MongoDB `readConcern` or `writeConcern`
1003
- semantics. A command containing either top-level field is rejected with
1004
- `InvalidOptions` (code 72) before authentication, session creation, transaction
1005
- preparation, publication-gate acquisition, or database mutation. Callers must not
1006
- interpret a successful SmartDB 5.x response as a majority or replica-backed
1007
- acknowledgement. The exact `writeConcern: { w: 0 }` envelope used by the official
1008
- driver for `endSessions`, and the equivalent envelope on `killSessions`, are
1009
- accepted only to preserve best-effort session cleanup; neither is a database-write
1010
- acknowledgement.
1011
-
1012
- SmartDB 5.x also does not enforce MongoDB wire `maxTimeMS` or `maxCommitTimeMS`
1013
- deadlines. A command containing either top-level field is rejected at the same
1014
- early boundary with `InvalidOptions` (code 72). The official driver transmits a
1015
- transaction's `maxCommitTimeMS` as `maxTimeMS` on `commitTransaction`, which is
1016
- therefore rejected rather than silently ignored. These wire fields are distinct
1017
- from the fail-stop `timeoutMs` option on SmartDB's TypeScript management methods.
1003
+ SmartDB serves every database from exactly one voting node and states its
1004
+ concern profile in those terms instead of ignoring or blanket-rejecting the
1005
+ fields:
1006
+
1007
+ - **Write concern.** Every acknowledged write is fsynced to the write-ahead log
1008
+ and the data file before the response is sent, so `w: 1`, `w: "majority"`
1009
+ (the majority of one voter is that voter), and `j: true` are all satisfied by
1010
+ the same durable commit. `w: 0` is acknowledged the same way whenever a
1011
+ response is expected; the exact `writeConcern: { w: 0 }` `endSessions` and
1012
+ `killSessions` envelopes still run without a transport response. `wtimeout`
1013
+ bounds a replication wait that does not exist on one node and has no
1014
+ additional effect. `w: N` for `N > 1` fails with `UnsatisfiableWriteConcern`
1015
+ (code 100), tag-set modes fail with `UnknownReplWriteConcern` (code 79), and
1016
+ `j: true` fails with `BadValue` (code 2) on the in-memory storage backend,
1017
+ which does not journal. Write concern is accepted only on commands that
1018
+ write (including DDL, user management, `commitTransaction`, and
1019
+ `abortTransaction`) and is rejected with `InvalidOptions` (code 72) inside a
1020
+ multi-statement transaction, where it belongs on the commit.
1021
+ - **Read concern.** `local`, `available`, `majority`, and `linearizable` all
1022
+ observe the durably committed state, because nothing is acknowledged before
1023
+ it is durable and reads never observe uncommitted transaction buffers.
1024
+ `snapshot` is accepted only on the first statement of a multi-document
1025
+ transaction and is served by the database-wide transaction snapshot
1026
+ described below. Read concern is accepted on `find`, `count`, `distinct`,
1027
+ and `aggregate` only, and `afterClusterTime`, `atClusterTime`, and
1028
+ `afterOpTime` are rejected with `InvalidOptions` because a single node issues
1029
+ no cluster time.
1030
+ - **Deadlines.** `maxTimeMS` attaches a cooperative deadline to the command.
1031
+ It is checked at admission, during namespace-lock and maintenance-gate
1032
+ waits, before `find`, `count`, and `distinct` materialize documents, between
1033
+ documents in `insert` and `delete` batches, and immediately before a
1034
+ transaction commit is handed to storage; expiry fails with `MaxTimeMSExpired`
1035
+ (code 50). Database-permit waits keep their fixed internal timeout, and a
1036
+ single storage step or an in-progress `update` batch runs to completion
1037
+ before the next check, so a write that expires after its first documents
1038
+ were published reports the expiry while those documents remain durable,
1039
+ exactly like an interrupted MongoDB batch. The official driver transmits a
1040
+ transaction's `maxCommitTimeMS` as `maxTimeMS` on `commitTransaction`; a
1041
+ commit that expires before its batch is handed to storage publishes nothing,
1042
+ records the transaction as aborted, and carries the
1043
+ `UnknownTransactionCommitResult` label; the driver's commit retry then sees
1044
+ `NoSuchTransaction` with `TransientTransactionError` and restarts the whole
1045
+ transaction. `getMore` rejects `maxTimeMS` with `BadValue` because SmartDB
1046
+ has no awaitData cursors, and a literal `maxCommitTimeMS` field is rejected
1047
+ with `InvalidOptions`.
1048
+
1049
+ Callers may therefore rely on a successful SmartDB response as a durable,
1050
+ single-node acknowledgement. They must not read it as a replica-backed
1051
+ acknowledgement: majority semantics across several voters arrive with
1052
+ replication.
1018
1053
 
1019
1054
  ### Unsupported Administrative Commands
1020
1055
 
package/readme.plan.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Status: canonical direction, approved 2026-08-17. Reread before starting engine or
4
4
  managed-service work.
5
5
 
6
- Baseline: `@push.rocks/smartdb` 5.3.2. SmartDB is a Rust database engine with a
6
+ Baseline: `@push.rocks/smartdb` 5.5.0. SmartDB is a Rust database engine with a
7
7
  TypeScript lifecycle facade, a MongoDB wire-protocol command surface, file and
8
8
  memory storage, authentication, transactions, resource fencing, and official
9
9
  MongoDB Node.js driver integration.
@@ -18,12 +18,22 @@ Current execution status:
18
18
  - strict latest-per-key runtime WAL recovery and post-publication point-write
19
19
  fencing were released in 5.3.2; crash-safe ordinary DDL, debug-server security,
20
20
  and general crash/soak qualification remain separate required 5.x slices
21
- - the current Pending VNext slice adds a non-serving, fixture-only A0 committed-log
22
- kernel with one static timeline, contiguous LSNs, exact retry receipts, bounded
23
- change-proportional appends, strict pair recovery, and ambiguity fencing
21
+ - 5.4.0 released the non-serving, fixture-only A0 committed-log kernel with one
22
+ static timeline, contiguous LSNs, exact retry receipts, bounded
23
+ change-proportional appends, strict pair recovery, and ambiguity fencing; the
24
+ follow-up A1/MVCC/rollover/checkpoint work is parked unreleased on the
25
+ `storage-vnext-a1-wip` branch
24
26
  - A0 has no BSON document substrate, MVCC, checkpoint, segment rollover, OpenRaft
25
- integration, wire serving, concern, deadline, migration, or availability claim;
26
- Phase 3B and the earlier Phase 0/1 gates have not passed
27
+ integration, wire serving, migration, or availability claim; Phase 3B and the
28
+ earlier Phase 0/1 gates have not passed
29
+ - 5.5.0 serves the truthful single-node concern profile on the legacy engine:
30
+ fsync-backed `w: 1`/`majority`/`j` acknowledgement, committed-state reads for
31
+ `local`/`available`/`majority`/`linearizable`, database-wide transaction
32
+ snapshots pinned by the namespace publication clock with fail-closed
33
+ `SnapshotUnavailable` retries, cooperative `maxTimeMS` deadlines, and
34
+ per-session idempotent commit/abort outcomes; unsatisfiable `w: N > 1`, tag
35
+ sets, cluster-time read concerns, and journaled writes on the memory backend
36
+ remain rejected
27
37
 
28
38
  ## Product objective
29
39
 
@@ -136,7 +146,10 @@ and reproducible baseline measurements.
136
146
  ## Phase 1: truthful single-node behavior
137
147
 
138
148
  - stop over-advertising MongoDB version, wire behavior, and batch limits
139
- - reject ignored concerns, options, commands, and successful administrative stubs
149
+ - serve the single-node concern profile truthfully and reject every concern,
150
+ option, command, or administrative stub whose guarantee one node cannot
151
+ provide (completed 2026-09-02: concerns, deadlines, database-wide transaction
152
+ snapshots, idempotent transaction outcomes)
140
153
  - completed slice (2026-08-25): validate OP_MSG required flags, CRC-32C
141
154
  checksums, bounded framing, and exact no-response admission and execution
142
155
  - preserve complete BSON type and value identity for document and index keys
@@ -219,10 +232,12 @@ Gates:
219
232
  - add configurable service classes and fair scheduling
220
233
  - add only features selected by the certified compatibility profile
221
234
 
222
- Semantic work may proceed after Phase 2. Retryable outcomes, transaction publication,
223
- and durable index publication depend on Storage VNext. Unsupported local concerns
224
- remain rejected until Storage VNext implements them; majority concerns remain
225
- rejected until replication implements them.
235
+ Semantic work may proceed after Phase 2. Crash-durable retryable outcomes,
236
+ transaction publication, and durable index publication move onto Storage VNext.
237
+ The single-node concern profile is served today: every acknowledged write is
238
+ durable before the response, reads observe committed state, and transactions
239
+ observe one database-wide snapshot. Multi-voter majority acknowledgement and
240
+ cluster-time read concerns remain rejected until replication implements them.
226
241
 
227
242
  Gate: bounded-memory scans at maximum declared scale, published p99 latency and
228
243
  capacity curves, proven tenant isolation, and zero certified-profile mismatches.
@@ -256,7 +271,8 @@ logged state.
256
271
  - replicate the Storage VNext log across three replicas in separate failure domains
257
272
  - add terms, quorum commit, snapshot installation, log catch-up, and membership
258
273
  changes
259
- - implement truthful local and majority concerns
274
+ - extend the single-node concern profile to multi-voter majority acknowledgement
275
+ and cluster-time read concerns
260
276
  - publish accurate `hello` topology and election metadata
261
277
  - add causal times, retryable outcomes, idempotent transaction commits, and
262
278
  failover-safe session behavior
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartdb',
6
- version: '5.4.0',
6
+ version: '5.5.0',
7
7
  description: 'A MongoDB-compatible embedded database server with wire protocol support, backed by a high-performance Rust engine.'
8
8
  }
@@ -389,6 +389,18 @@ export function getRustDbAllocatorEnv(
389
389
  */
390
390
  export class RustDbBridge extends EventEmitter {
391
391
  private bridge: plugins.smartrust.RustBridge<TSmartDbCommands>;
392
+
393
+ /**
394
+ * Operating-system process id of the spawned Rust engine while it runs as a
395
+ * child process of this Node.js process. Undefined before spawn, after
396
+ * termination, or when the bridge is connected to an external process.
397
+ */
398
+ public get processId(): number | undefined {
399
+ const transport = (this.bridge as unknown as {
400
+ transport?: { childProcess?: { pid?: number } | null };
401
+ }).transport;
402
+ return transport?.childProcess?.pid;
403
+ }
392
404
  private terminationConfirmed = true;
393
405
  private terminationPromise: Promise<void> | undefined;
394
406
  private terminationFailure: unknown;
@@ -387,6 +387,11 @@ export class SmartdbServer {
387
387
  /**
388
388
  * Check if the server is running
389
389
  */
390
+ /** Process id of the spawned Rust engine, when it runs as a child process. */
391
+ get processId(): number | undefined {
392
+ return this.bridge.processId;
393
+ }
394
+
390
395
  get running(): boolean {
391
396
  return this.isRunning;
392
397
  }