52oqj99unu0kvnn1vnmrso0o0sfcq3ke · 139 rows
| id | content_hash | title | description | design | acceptance_criteria | notes | status | priority | issue_type | assignee | estimated_minutes | created_at | created_by | owner | updated_at | closed_at | closed_by_session | external_ref | spec_id | compaction_level | compacted_at | compacted_at_commit | original_size | sender | ephemeral | wisp_type | pinned | is_template | mol_type | work_type | source_system | metadata | source_repo | close_reason | event_kind | actor | target | payload | await_type | await_id | timeout_ns | waiters | hook_bead | role_bead | agent_state | last_activity | role_type | rig | due_at | defer_until | no_history | started_at | is_blocked |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| tarantool-etcd-00h | 87a689118190cb32273ba6702ecc3ed8356613cdcb11f3ac36c222d880d66e79 | Multi-instance support | Roadmap P2. Run tarantool-etcd as a multi-instance deployment (beyond single-node). Relates to existing replica-route and write-forwarding issues (tarantool-etcd-lko, -kbc, -7ey) but tracks the broader 'several instances' story: cluster membership presentation, endpoint discovery, and client-visible multi-member behavior. | Roadmap-only item; partially adjacent to replica-route issues. | open | 2 | feature | NULL | NULL | 2026-05-20T06:55:25Z | Eugene Blikh | bigbes@gmail.com | 2026-05-20T06:55:25Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-070 | 6acbf4a75e199fd9fc37cbfa4584a578ca4024d8454ea401c97fac60b9ba7614 | iproto-verify: etcd_watch_cancel vs Watch.Watch-cancel | Add Lua testkit verification for etcd_watch_cancel (mirrors gRPC Watch.Watch-cancel). Successful path: assert response shape matches the gRPC response message after MsgPack round-trip. Error path: at least one error case asserting {code, message} matches app/etcd/errors.lua catalog. Exercise at least one non-default option if applicable. Goes in test/iproto_parity_test.lua under group iproto/parity/watch. Call shape: local resp = etcd_watch_cancel(req). | Verification-only. Wrapper exists at app/etcd/api.lua. Epic tarantool-etcd-w8q. | open | 3 | task | NULL | NULL | 2026-05-19T14:53:58Z | Eugene Blikh | bigbes@gmail.com | 2026-05-19T14:53:58Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-0am | 990386cb32b67cb5f216b1036ed262949e3a759d465d6cfae199d5b3a29156dd | --max-txn-ops not recursive and not configurable | etcd's checkTxnRequest/checkRequestOp recurse into nested txns with a shrinking maxTxnOps-opc budget (v3rpc/key.go), so a deeply nested txn cannot exceed the total. app/etcd/txn.lua:235-244 validate_request checks only TOP-LEVEL counts against a hardcoded MAX_TXN_OPS = 128 (txn.lua:210). A nested txn tree can therefore carry far more than 128 ops. No role-config knob. The top-level limit and the error message match etcd exactly, so this is narrow: make check recursive with a shrinking budget, add a max_txn_ops option. | A nested txn exceeding the total budget is rejected with etcd's message; limit configurable via roles_cfg; test covers the nested overflow. | open | 3 | bug | NULL | NULL | 2026-07-17T13:20:51Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T13:20:51Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-0gn | cbaefab1a77485501e199172ef8f5e6c582f9aae5354c5d6ef8410b2c38c291b | Implement Watch extras: filters, progress_notify (periodic), fragment | WatchCreateRequest fields filters (NOPUT/NODELETE), progress_notify periodic notify, and fragment (large-response splitting) are declared in pb/defs.lua but ignored by create_watcher/grpc.lua/json.lua. On-demand RequestProgress already works. Implement the missing three. | closed | 2 | feature | Eugene Blikh | NULL | 2026-05-20T06:35:53Z | Eugene Blikh | bigbes@gmail.com | 2026-05-20T06:49:57Z | 2026-05-20T06:49:57Z | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | Implemented filters (NOPUT/NODELETE), periodic progress_notify, and gRPC fragment; fixed packed repeated-enum decode bug in lib/protobuf; added Lua + Go conformance tests | 0 | NULL | NULL | NULL | 0 | 2026-05-20T06:35:59Z | 0 | |||||||||||||||||||||||
| tarantool-etcd-0gv | b9d3c59f8e7df072d263e07416056e3b25b412b9f8d2c1c1bbcffd1d0ca91810 | iproto-verify: etcd_user_change_password vs Auth.UserChangePassword | Add Lua testkit verification for etcd_user_change_password (mirrors gRPC Auth.UserChangePassword). Successful path: assert response shape matches the gRPC response message after MsgPack round-trip. Error path: at least one error case asserting {code, message} matches app/etcd/errors.lua catalog. Exercise at least one non-default option if applicable. Goes in test/iproto_parity_test.lua under group iproto/parity/auth. Call shape: local resp = etcd_user_change_password(req). | Verification-only. Wrapper exists at app/etcd/api.lua. Epic tarantool-etcd-w8q. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:10Z | Eugene Blikh | bigbes@gmail.com | 2026-05-19T14:54:10Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-0us | bc2b961d826c01422901d943b0a374e659925e4a505a60526acd56b5c0a13bab | iproto-verify: etcd_maintenance_status vs Maintenance.Status | Add Lua testkit verification for etcd_maintenance_status (mirrors gRPC etcdserverpb.Maintenance.Status). Same test pattern as verify-only tasks: positive path plus at least one error path, response shape matches gRPC, error codes from app/etcd/errors.lua. For UNIMPLEMENTED-by-design endpoints (Cluster.MemberAdd/Remove/Update/Promote), assert the wrapper returns the canonical UNIMPLEMENTED error consistent with gRPC rather than skipping. Goes in test/iproto_parity_test.lua under group iproto/parity/maintenance. | Epic tarantool-etcd-w8q. Blocked on the matching impl issue. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:33Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:37Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 1 | |||||||||||||||||||||||
| tarantool-etcd-0uw | 0d8c71e8a752102df90c233ba610d1de22ea1a0f26c7ca03acdfc64a64b9f649 | iproto-verify: etcd_txn vs KV.Txn | Add Lua testkit verification for etcd_txn (mirrors gRPC KV.Txn). Successful path: assert response shape matches the gRPC response message after MsgPack round-trip. Error path: at least one error case asserting {code, message} matches app/etcd/errors.lua catalog. Exercise at least one non-default option if applicable. Goes in test/iproto_parity_test.lua under group iproto/parity/kv. Call shape: local resp = etcd_txn(req). | Verification-only. Wrapper exists at app/etcd/api.lua. Epic tarantool-etcd-w8q. | open | 3 | task | NULL | NULL | 2026-05-19T14:53:54Z | Eugene Blikh | bigbes@gmail.com | 2026-05-19T14:53:54Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-16d | c3ed04edeb2dc3f191d8cd287f502242a4fe98be2442a5343e1a416695f6572a | Implement Maintenance.Snapshot RPC (streaming DB snapshot) | The etcd v3 Maintenance.Snapshot RPC streams a full backend snapshot to the client (server-streaming: SnapshotResponse chunks carry remaining_bytes, blob, and a trailing SHA256 hash; etcdctl/clientv3 reassemble into a bolt .db file). Today it is deliberately stubbed: json.lua:13 documents /v3/maintenance/snapshot as 501, and there is no handler in maintenance.lua nor a routes entry in defs.lua (Maintenance methods top out at MoveLeader [5]). What's needed: - Decide what a 'snapshot' means for a Tarantool-backed store. Real etcd ships a bolt DB; we'd need to either (a) produce an etcd-compatible bolt snapshot the upstream tooling can restore, or (b) define our own snapshot format (likely a box snapshot / dump of the etcd_* spaces + revision sequence) and document the divergence. (a) is a large undertaking; (b) is the pragmatic path but breaks 'etcdctl snapshot restore' interop. - Wire the server-streaming method: add SnapshotRequest/SnapshotResponse schemas to defs.lua, a routes entry, a streaming handler in maintenance.lua, and stream support on both wires (gRPC HTTP/2 + JSON gateway chunked). - Emit remaining_bytes + blob chunks + final hash per the etcd contract. Document the chosen approach + any divergence in docs/TODO.md and (if it can't fully pass conformance) add to harness/gaps.go. | Cross-wire streaming: gRPC server-streaming + JSON gateway chunked POST. Mirror existing streaming patterns (Watch). Acceptance: etcdctl snapshot save succeeds against our server, OR documented divergence if we ship a non-bolt format. | open | 2 | feature | NULL | NULL | 2026-05-20T05:47:59Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:36Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-1dk | 81922160bc9d8c07f4a217860426f67f73aa04c7a651a51683c3eb686d1d0d97 | Extract tarantool-http2 bindings into a standalone rock | Roadmap P0 blocker. The nghttp2/HTTP-2/gRPC bindings under lib/nghttp2/ are currently vendored inside tarantool-etcd. Extract them (FFI bindings, session, server, gRPC layer) into a separately versioned, installable rock so they can be reused and released independently. Includes rockspec, source repo (sourcecraft.dev), and rewiring tarantool-etcd to consume it via .rocks/. Covers all related decoupling work. | Roadmap-only item; was not tracked in beads before. | open | 0 | feature | NULL | NULL | 2026-05-20T06:55:21Z | Eugene Blikh | bigbes@gmail.com | 2026-05-20T06:55:21Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-1kp | 5cf23f484926a3e4bf453927a82a75a629ba2397db6094408d8d080208fa4f3f | /health is a hardcoded constant — can never report unhealthy | etcd reports unhealthy on an active alarm or no leader, supports ?exclude=NOSPACE, and implements the probe as a real v3 linearizable range with its own timeout. app/etcd/json.lua:477-483 ALWAYS returns 200 {"health":"true","reason":""}. The body SHAPE matches 3.5 exactly (string-typed health, reason, no errors field), and the query string is split off (lib/http1/server.lua:230) so ?exclude= parses as a path match but is never interpreted. Impact: a leaderless rs3 node reports healthy. Any load balancer or k8s probe pointed at /health gets a liveness signal that cannot fail — arguably worse than having no endpoint, since it actively asserts health. | /health returns health=false with a reason when the node has no leader; a self-probe range backs the answer; ?exclude= parsed; test covers the leaderless case. | open | 2 | bug | NULL | NULL | 2026-07-17T13:21:25Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T13:21:25Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-1qa | 2cabbe0c40b6d7a881e5a0c5c93427d243b627c4662a16a40db478e29ffd07c7 | Compact(Physical=true) is silently a no-op | etcd's Compact accepts a Physical flag; on real etcd Physical=true blocks until the underlying bbolt compaction physically reclaims space. tarantool-etcd accepts the flag but ignores it (app/etcd/compact.lua + app/etcd/grpc.lua wiring). Single-node Tarantool compacts synchronously, so the client-observable contract ('no historical revision below the compact point is recoverable after the response returns') is already met regardless of Physical. Hence: probably leave as-is, not blocking conformance — TestKVCompactTable/Physical passes by covering the same end-state. Documented divergence in docs/TODO.md:95. Migrated from the (now-deleted) tests/conformance/GAPS.md Gap 5. | Deliberate divergence, low priority. Only act if a client needs to distinguish physical reclamation timing; would require modeling an async compaction step that doesn't exist single-node. | open | 4 | task | NULL | NULL | 2026-05-20T11:59:03Z | Eugene Blikh | bigbes@gmail.com | 2026-05-20T11:59:03Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-1rs | b3af9b9653f2aefab78554f22ffe2433f695a04c58411920ae4c1915f6a71c5b | No ErrWatcherDuplicateID; watch ids are globally scoped, so two streams collide | etcd rejects a reused or negative watch id with created=true, canceled=true, cancel_reason='duplicate watch ID' (mvcc/watcher.go). app/etcd/watch.lua:331 does 'watchers[watch_id] = w' with no in-use check, and the registry (watch.lua:42-44) is MODULE-GLOBAL rather than per-stream. So two different client connections that both choose the same custom watch id collide — stream A's fiber can drain stream B's events. Cross-connection data leak between unrelated clients, not just a protocol nicety. Route-level: grpc.lua:448. | Key the registry by (stream, watch_id) rather than watch_id alone, and reject in-use/negative ids with the etcd-shaped cancel. The global registry may have other consumers — check before re-keying. | Two streams using the same custom watch id do not interfere; reused id on one stream returns the duplicate-ID cancel; negative id rejected; test covers the cross-stream case. | open | 2 | bug | NULL | NULL | 2026-07-17T13:20:16Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T13:20:16Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | ||||||||||||||||||||||
| tarantool-etcd-1sh | 520b57f1eb27bf42f4c3733f9fae40e71be0eefb27b0c553d2a6ce2cdda00172 | KV.RangeStream RPC (etcd 3.7) — server-streaming chunked Range | etcd 3.7 adds KV.RangeStream: a server-streaming variant of Range that delivers a large result set as a sequence of RangeStreamResponse chunks instead of buffering the whole RangeResponse. Lowers latency and bounds server-side buffering memory. Current state: proto/etcdserverpb/rpc.proto ALREADY vendors the RPC (line 25) and RangeStreamResponse (line 1239), and app/etcd/pb/gen/etcdserverpb/rpc_pb.lua has the generated RangeStreamResponse descriptor (line 183). What is missing is the route entry in app/etcd/pb/routes.lua and a handler — nothing dispatches it today. Scope: add the route with stream = true, implement a chunked range in app/etcd/kv.lua that reuses the existing iteration path but emits N kvs per chunk, wire the gRPC server-streaming send path, and decide the JSON gateway shape (upstream JSONPb streams newline-delimited {"result": {...}} objects, same as Watch). | Chunk boundary policy needs a decision: fixed kv count vs byte budget. Upstream chunks by count. The whole stream must read at a single revision — take the revision once up front and pass it down, same as a paginated Range, so a concurrent write cannot make chunks inconsistent with each other. | Conformance test drives KV.RangeStream against both backends on both wires and gets identical chunk contents (chunk boundaries may differ); result set spanning multiple chunks is consistent at one revision; docs/TODO.md updated. | open | 2 | feature | NULL | NULL | 2026-07-17T11:29:51Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T11:29:51Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | ||||||||||||||||||||||
| tarantool-etcd-1tw | 1ada235b108fc8da8897b617fbfe9c85aa4a8ae37f8c06a6ba13ee3dbad59c40 | Documentation | Roadmap P1. Produce user- and operator-facing documentation: deployment/config (role config blocks), supported etcd surface + known divergences, JSON gateway, replication/replica routes, TLS setup, metrics. Beyond the in-tree CLAUDE.md/AGENTS.md developer notes and LDoc. | Roadmap-only item; not tracked before. | open | 1 | task | NULL | NULL | 2026-05-20T06:55:24Z | Eugene Blikh | bigbes@gmail.com | 2026-05-20T06:55:24Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-216 | 28369b3161fd83e9748fd8cd2732132d9fb63d890439e3733282f22dfc57b524 | iproto-verify: etcd_maintenance_hash vs Maintenance.Hash | Add Lua testkit verification for etcd_maintenance_hash (mirrors gRPC etcdserverpb.Maintenance.Hash). Same test pattern as verify-only tasks: positive path plus at least one error path, response shape matches gRPC, error codes from app/etcd/errors.lua. For UNIMPLEMENTED-by-design endpoints (Cluster.MemberAdd/Remove/Update/Promote), assert the wrapper returns the canonical UNIMPLEMENTED error consistent with gRPC rather than skipping. Goes in test/iproto_parity_test.lua under group iproto/parity/maintenance. | Epic tarantool-etcd-w8q. Blocked on the matching impl issue. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:37Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:37Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 1 | |||||||||||||||||||||||
| tarantool-etcd-234 | 6a864d040222a5c7b7ec4e61daed84bfd693cc407841df0f2101755949bfd026 | LeaseGrantRevoke perf regression — revalidate before chasing | LeaseGrantRevoke regressed from 2 243 ops/s to 1 223 ops/s against the previous bench baseline (docs/BENCH.md "Refresh — 2026-05-14, post select+count Range fast path", section "Known issues — open"). Bench code on this leaf is unchanged; revalidate with a second run before chasing the cause — bench noise on this leaf is plausible. | 1. Re-run BenchmarkLeaseGrantRevoke on the same hardware (Apple M4 Pro per BENCH.md), 5 s/leaf, fresh subprocess per leaf. 2. If still ~1 223 ops/s, bisect across the commits between the 2 243 and 1 223 measurements (git log between BENCH.md baselines). 3. If back at ~2 243, close as noise. | Source: docs/BENCH.md section "Known issues — open" near line 244. | open | 3 | bug | NULL | NULL | 2026-05-19T13:34:48Z | Eugene Blikh | bigbes@gmail.com | 2026-05-19T13:34:48Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | ||||||||||||||||||||||
| tarantool-etcd-2al | 5f1b8211e76acdf580cf1191135c8a84be0bfb1453bf403bd5b92027addf4dbf | iproto-verify: etcd_user_grant_role vs Auth.UserGrantRole | Add Lua testkit verification for etcd_user_grant_role (mirrors gRPC Auth.UserGrantRole). Successful path: assert response shape matches the gRPC response message after MsgPack round-trip. Error path: at least one error case asserting {code, message} matches app/etcd/errors.lua catalog. Exercise at least one non-default option if applicable. Goes in test/iproto_parity_test.lua under group iproto/parity/auth. Call shape: local resp = etcd_user_grant_role(req). | Verification-only. Wrapper exists at app/etcd/api.lua. Epic tarantool-etcd-w8q. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:10Z | Eugene Blikh | bigbes@gmail.com | 2026-05-19T14:54:10Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-2pa | 898453b613dd144bd8b1f5db13f2cefd2835dd7aea0af008be3f485229f5f9e1 | iproto-verify: etcd_role_delete vs Auth.RoleDelete | Add Lua testkit verification for etcd_role_delete (mirrors gRPC Auth.RoleDelete). Successful path: assert response shape matches the gRPC response message after MsgPack round-trip. Error path: at least one error case asserting {code, message} matches app/etcd/errors.lua catalog. Exercise at least one non-default option if applicable. Goes in test/iproto_parity_test.lua under group iproto/parity/auth. Call shape: local resp = etcd_role_delete(req). | Verification-only. Wrapper exists at app/etcd/api.lua. Epic tarantool-etcd-w8q. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:13Z | Eugene Blikh | bigbes@gmail.com | 2026-05-19T14:54:13Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-35y | 7207410b4edf93dd7a43975af03141bbb6704d601638ce99ebd06a4f81273c21 | Hash/HashKV not wire-compatible with real etcd (cross-member corruption detection broken) | Maintenance.Hash and Maintenance.HashKV compute a CRC32 over Tarantool's reconstructed logical key/value/revision/lease view (app/etcd/maintenance.lua fold/fold_keyspace_now/fold_keyspace_at), NOT over bbolt page bytes the way real etcd does. The hashed inputs are semantically equivalent, but the byte layout being checksummed is entirely Tarantool's, so the resulting hash will never equal a real etcd member's hash for identical data. Impact: works for same-instance 'did my data change between rev A and B' checks, but is useless for etcd's actual purpose — cross-member corruption detection, where the corruption checker compares hashes across peers and expects identical members to agree. A Tarantool node and a real etcd node always disagree; the conformance matrix only passes because both Tarantool cells share the same folding scheme. This is a deliberate divergence. Documented in the maintenance.lua module header (lines 8-10). Tracking here so it is not mistaken for a bug. Achieving true compatibility would require emulating bbolt's physical page encoding, which is likely out of scope. | Divergence, not a defect. The HashKV revision-error contract (compacted->revision_compacted, future->future_revision) IS correct. Cross-reference docs/TODO.md deliberate-divergences section. | open | 3 | task | NULL | NULL | 2026-05-20T06:31:01Z | Eugene Blikh | bigbes@gmail.com | 2026-05-20T06:31:01Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-3c2 | 1b24f6a27bf95dcbb79ff9d253fcaff82918a95ecf2d9d694cb3f41471f944d8 | iproto-verify: etcd_member_update vs Cluster.MemberUpdate | Add Lua testkit verification for etcd_member_update (mirrors gRPC etcdserverpb.Cluster.MemberUpdate). Same test pattern as verify-only tasks: positive path plus at least one error path, response shape matches gRPC, error codes from app/etcd/errors.lua. For UNIMPLEMENTED-by-design endpoints (Cluster.MemberAdd/Remove/Update/Promote), assert the wrapper returns the canonical UNIMPLEMENTED error consistent with gRPC rather than skipping. Goes in test/iproto_parity_test.lua under group iproto/parity/cluster. | Epic tarantool-etcd-w8q. Blocked on the matching impl issue. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:24Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:37Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 1 | |||||||||||||||||||||||
| tarantool-etcd-3co | a4e494d731f91d11cb4d3d65e1578c1cd2f5b09229e3f00bd1dc036e36ee209b | iproto-verify: etcd_role_revoke_permission vs Auth.RoleRevokePermission | Add Lua testkit verification for etcd_role_revoke_permission (mirrors gRPC Auth.RoleRevokePermission). Successful path: assert response shape matches the gRPC response message after MsgPack round-trip. Error path: at least one error case asserting {code, message} matches app/etcd/errors.lua catalog. Exercise at least one non-default option if applicable. Goes in test/iproto_parity_test.lua under group iproto/parity/auth. Call shape: local resp = etcd_role_revoke_permission(req). | Verification-only. Wrapper exists at app/etcd/api.lua. Epic tarantool-etcd-w8q. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:16Z | Eugene Blikh | bigbes@gmail.com | 2026-05-19T14:54:16Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-3dk | f26e1791677ec8877ef162eeaf8e523b01b1fea8528cb4a62fd6ac2867bba7e8 | iproto-verify: etcd_compact vs KV.Compact | Add Lua testkit verification for etcd_compact (mirrors gRPC KV.Compact). Successful path: assert response shape matches the gRPC response message after MsgPack round-trip. Error path: at least one error case asserting {code, message} matches app/etcd/errors.lua catalog. Exercise at least one non-default option if applicable. Goes in test/iproto_parity_test.lua under group iproto/parity/kv. Call shape: local resp = etcd_compact(req). | Verification-only. Wrapper exists at app/etcd/api.lua. Epic tarantool-etcd-w8q. | open | 3 | task | NULL | NULL | 2026-05-19T14:53:55Z | Eugene Blikh | bigbes@gmail.com | 2026-05-19T14:53:55Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-3hm | 2af875528846ea9fa87eb57f137dd3dc975608bbec8597c643bec1408ecb15d0 | --max-request-bytes not enforced (uncapped gRPC body accumulation) | etcd rejects requests over 1.5 MiB with InvalidArgument 'etcdserver: request is too large'. gRPC: lib/nghttp2/grpc.lua:499-524 on_data_chunk accumulates stream.data_buf UNCAPPED — a large stream is a memory-growth vector, not just a conformance gap. JSON: only a generic 4 MiB HTTP cap returning a bare 413 text body (lib/http1/server.lua:53,183-184,353-354), not the etcd error shape. No such error constructor exists in app/etcd/errors.lua. The fragmentation half of the 3.3 feature IS implemented (app/etcd/watch.lua:36-40). | Request >1.5 MiB returns the etcd-shaped InvalidArgument on both wires; gRPC accumulation is capped and aborts early rather than buffering; limit configurable; error constructor added. | open | 2 | bug | NULL | NULL | 2026-07-17T13:20:50Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T13:20:50Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-3sp | d73f28d5913d8a498b9e6a563791b85cc55ae47cfd49f87852ec3b9c8a37e6b2 | LeaseKeepAlive fsyncs WAL on every renew | On btrfs SSD bench (2026-05-19 disk run), LeaseKeepAlive/n_X drops from 4 234 ops/s (tmpfs) to 207 ops/s on tarantool while etcd holds at ~7 400 ops/s in both configs. p50 goes from 224 µs (tmpfs) to 4 548 µs (disk) — exactly one btrfs fsync. Root cause at app/etcd/lease.lua:191: box.space.leases:update(id, {{'=', 'expiry_time', now + lease.ttl}}) The leases space is is_sync=true so every keep-alive does a full WAL append + fsync. Etcd's KeepAlive is an in-memory TTL bump with no disk write. Fix sketch: split expiry into an in-memory map (refreshed on every KeepAlive) and persist only on grant/revoke. The expiry fiber should read from the in-memory map. Trade-off: leases may extend further into the future than the on-disk record after a crash, which is fine — clients have to re-keep-alive after reconnect anyway. See docs/BENCH.md Linux x86_64 disk section (Open follow-ups). | GOAL: lease keep-alive must not touch the WAL. Mirror etcd's split — durable state = {lease record, granted TTL, attached keys}; ephemeral leader-local state = the expiry deadline (refreshed on every keep-alive, never persisted). === STATE MODEL === Add a module-level in-memory map in app/etcd/lease.lua: local deadlines = {} -- [lease_id] = monotonic deadline (clock.monotonic()+ttl) This is leader-local. Followers never keep-alive (the gRPC write gate routes LeaseKeepAlive to the leader), and only the leader runs the expiry fiber and can revoke. So the countdown is structurally a leader concern. The `leases` space (schema.lua:97-118) stays is_sync=true and keeps its {id, ttl, expiry_time, granted_ttl} format. The `expiry_time` FIELD and its TREE index (schema.lua:113) become vestigial — written once at grant, never the source of liveness again. Leave them to avoid a space migration; an optional follow-up can drop the field+index. Nothing reads expiry_time after this change. === HANDLER CHANGES (app/etcd/lease.lua) === 1. grant() ~L70: after `box.space.leases:insert(...)`, seed `deadlines[id] = now + ttl`. Insert still persists (durable, required). 2. keepalive() ~L191: REPLACE box.space.leases:update(id, {{'=', 'expiry_time', now + lease.ttl}}) with deadlines[id] = clock.monotonic() + lease.ttl Keep the `box.space.leases:get(id)` existence check above it (cheap read, no fsync) — it supplies lease.ttl and preserves the NOT_FOUND-raises contract the gRPC stream handler depends on (see L169-171). 3. revoke() ~L150: after `box.space.leases:delete(id)`, clear `deadlines[id] = nil`. 4. time_to_live() ~L236: REPLACE `lease.expiry_time` read with `deadlines[id]`. If deadlines[id] is nil (lease loaded but not yet rebuilt), fall back to `clock.monotonic() + lease.granted_ttl`. remaining = max(0, floor(dl - now)). 5. expiry_loop() L287-338: REPLACE the `box.space.leases.index.expiry_time` scan (L302-309) with a scan of `deadlines`: collect ids where `deadline <= now`. Plain full-table scan is fine (numeric compares in Lua, runs every 500ms); a min-heap (etcd's leaseExpiredNotifier shape) is the optional optimization if lease counts get large. revoke() already nils the map entry on success. === REBUILD ON PROMOTE === Add lease.M.rebuild_deadlines(): clears `deadlines`, then for each tuple in box.space.leases:pairs() sets deadlines[tuple.id] = clock.monotonic() + tuple.ttl Call it in the RW branch of the box.watch('box.status', ...) callback in app/roles/etcd.lua (L341-353), immediately before lease.start_expiry(). This resets every inherited lease's deadline to a full-TTL grace period on the new leader — exactly etcd's failover behavior. === LATENT BUG THIS ALSO FIXES === expiry_time is currently computed as clock.monotonic()+ttl and REPLICATED. clock.monotonic() is process-local (relative to boot), so the persisted value is meaningless on any other node. Today a promoted replica's fiber scans inherited expiry_time values from a different monotonic timeline — could expire leases instantly or never. Rebuild-on-promote with the new leader's own clock removes this entirely. | - keepalive issues ZERO WAL writes: box.info.lsn delta == 0 across N successive M.keepalive() calls on the same lease. - BenchmarkLeaseKeepAlive/n_* on btrfs SSD (work.lab.local, real-disk run) recovers from 207 ops/s to within ~2x of etcd (~4000+ ops/s, near the tmpfs baseline). Re-run: TMPDIR=$HOME/bench-data go test -run=^$ \ -bench=^BenchmarkLeaseKeepAlive$ -benchtime=5s ./bench/ - Lease still expires correctly: grant short TTL, no keep-alive, attached keys are deleted by the fiber after TTL elapses; keep-alive before TTL prevents it. - TimeToLive reflects the latest keep-alive (remaining resets to ~ttl after a renew). - Promote rebuilds deadlines: a freshly-promoted leader expires inherited leases using its own monotonic clock (no instant-expiry, no never-expiry). - All existing lease conformance cells (tarantool, tarantool_json, tarantool_rs3) and Lua lease tests pass. | TESTS (add Lua-side tests pinning the fix per the project's regression rule): - no-WAL: assert box.info.lsn unchanged across keepalive calls. - liveness: keepalive refreshes deadline (TimeToLive resets, lease survives). - promote rebuild: use the box.cfg{read_only=true}...{read_only=false}+ box.ctl.promote() in-process follower-simulation pattern (see role_test.lua group role/write_gates_in_process) to verify rebuild_deadlines repopulates. - cross-node: optionally exercise via the replicaset harness that a promoted replica expires an inherited lease. Conformance already covers cross-wire correctness; this is about the Lua unit pins + the bench recovery number. DOCS: update docs/BENCH.md Linux x86_64 disk section once re-benched; remove the LeaseKeepAlive "bug" annotation from the headline table. SCOPE NOTE: leave the expiry_time field + index in place (no migration). Only the keepalive write path and the fiber's liveness source change. | closed | 2 | bug | Eugene Blikh | NULL | 2026-05-19T17:33:47Z | Eugene Blikh | bigbes@gmail.com | 2026-05-20T12:53:33Z | 2026-05-20T12:53:33Z | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | keep-alive now bumps a leader-local in-memory deadline map (zero WAL on renew); leases space written only on grant/revoke; rebuild_deadlines on promote. Lua regression tests + TestLease conformance pass. Disk re-bench on btrfs SSD still pending to replace pre-fix BENCH.md numbers. | 0 | NULL | NULL | NULL | 0 | 2026-05-20T12:46:48Z | 0 | ||||||||||||||||||||
| tarantool-etcd-40s | ead9d8881e362dcd5ca4281953373c9694eda30e348db3ecc62fd3c38305316b | iproto-verify: etcd_lease_leases vs Lease.LeaseLeases | Add Lua testkit verification for etcd_lease_leases (mirrors gRPC Lease.LeaseLeases). Successful path: assert response shape matches the gRPC response message after MsgPack round-trip. Error path: at least one error case asserting {code, message} matches app/etcd/errors.lua catalog. Exercise at least one non-default option if applicable. Goes in test/iproto_parity_test.lua under group iproto/parity/lease. Call shape: local resp = etcd_lease_leases(req). | Verification-only. Wrapper exists at app/etcd/api.lua. Epic tarantool-etcd-w8q. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:02Z | Eugene Blikh | bigbes@gmail.com | 2026-05-19T14:54:02Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-4b6 | a21c1b53991947f5c9851d56fe61a624617e31e589fc020c0a1b0d35444734b5 | Nested-txn duplicate-key detection is flat, not recursive | etcd's checkIntervals recursively rejects duplicate puts across nesting levels and puts intersecting delete ranges (ErrDuplicateKey), with the then/else carve-out. app/etcd/txn.lua:214-233 check_duplicate scans ONE FLAT op list. Nested EXECUTION is otherwise fully correct (txn.lua:182-198; auth recurses at auth.lua:321-322) — only validation fails to recurse. TestTxnNested passes because it tests execution, not validation. Impact: a nested txn with conflicting puts on the same key is accepted where etcd rejects it, giving order-dependent results. | Duplicate put across nesting levels rejected with ErrDuplicateKey shape; put intersecting a delete range rejected; then/else carve-out preserved; test covers the nested case. | open | 2 | bug | NULL | NULL | 2026-07-17T13:20:51Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T13:20:51Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-4nq | a36cb86be380c6352323fd213dcca281596f76fb5814751f3e520bdfb3bca3e4 | No /livez or /readyz probe endpoints | etcd 3.4/3.5 add k8s-style /livez and /readyz with ?verbose and ?exclude=; /readyz includes a learner check. Zero hits for 'livez|readyz' repo-wide; both 404 via app/etcd/json.lua:632. Add alongside the hand-served /health route table. Pairs naturally with the /health self-probe work — do them together. | /livez and /readyz served with etcd-shaped bodies; ?verbose supported; tests cover both. | open | 3 | feature | NULL | NULL | 2026-07-17T13:21:26Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T13:21:26Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-4nr | 54ecc2a70763771b850026ebc04dd6a36b3c0fdb078a641e138d98b3c334dbaa | iproto-impl: etcd_maintenance_defragment vs Maintenance.Defragment | Wrap the existing handler maintenance.defragment (already invoked by gRPC/JSON via defs.lua route etcdserverpb.Maintenance.Defragment) as a Lua global etcd_maintenance_defragment callable via iproto CALL. Mechanical change in app/etcd/api.lua: add rawset(_G, 'etcd_maintenance_defragment', wrap('maintenance', 'defragment')) under the appropriate service block; append 'etcd_maintenance_defragment' to the funcs table so box.schema.func.create plus guest grant runs at role apply; document the wrapper in the module-level LDoc comment. If Maintenance.Defragment returns UNIMPLEMENTED today, the wrapper still gets added so the iproto caller observes the same UNIMPLEMENTED error gRPC would return. Election.Observe is server-streaming over gRPC; iproto has no stream primitive, so propose a polling shape (observe_create/_pull/_cancel) mirroring the Watch precedent at api.lua:81-83. | etcd_maintenance_defragment is callable from net.box:call('etcd_maintenance_defragment', {req}) with no Lua error before handler dispatch. Successful request shape produces same successful response shape as gRPC. box.schema.func.create plus guest grant idempotent on role re-apply. | Epic tarantool-etcd-w8q. Handler at app/etcd/maintenance.lua. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:34Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:36Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | ||||||||||||||||||||||
| tarantool-etcd-4y9 | f06c275959d16e0105fc6079ad51b3fc76f4073f72e0025384ebedc3ddc01161 | iproto-impl: etcd_election_resign vs Election.Resign | Wrap the existing handler election.resign (already invoked by gRPC/JSON via defs.lua route v3electionpb.Election.Resign) as a Lua global etcd_election_resign callable via iproto CALL. Mechanical change in app/etcd/api.lua: add rawset(_G, 'etcd_election_resign', wrap('election', 'resign')) under the appropriate service block; append 'etcd_election_resign' to the funcs table so box.schema.func.create plus guest grant runs at role apply; document the wrapper in the module-level LDoc comment. If Election.Resign returns UNIMPLEMENTED today, the wrapper still gets added so the iproto caller observes the same UNIMPLEMENTED error gRPC would return. Election.Observe is server-streaming over gRPC; iproto has no stream primitive, so propose a polling shape (observe_create/_pull/_cancel) mirroring the Watch precedent at api.lua:81-83. | etcd_election_resign is callable from net.box:call('etcd_election_resign', {req}) with no Lua error before handler dispatch. Successful request shape produces same successful response shape as gRPC. box.schema.func.create plus guest grant idempotent on role re-apply. | Epic tarantool-etcd-w8q. Handler at app/etcd/election.lua. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:52Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:36Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | ||||||||||||||||||||||
| tarantool-etcd-4yb | ebc303ee9bbf602076c20e76824b17f16abbe8eb6df6cd55b32c47f62bf85b81 | Learner: raftpb + InternalRaftRequest proto schemas | Add gogoproto schemas to our pure-Lua pb engine for the internal wire types the log carries: raftpb.Message, raftpb.Entry, raftpb.HardState, raftpb.ConfState, raftpb.ConfChange/ConfChangeV2, raftpb.Snapshot/SnapshotMetadata, and etcdserverpb.InternalRaftRequest (the command envelope inside each normal Entry — wraps Put/Range/DeleteRange/Txn/Compaction/LeaseGrant/LeaseRevoke/Authenticate/AuthEnable/AuthUserAdd/RoleGrantPermission/etc). Vendor the .proto under proto/ and regenerate via just gen-proto. These are etcd-INTERNAL protos (raft/raftpb/raft.proto, server/etcdserver/etcdserverpb/raft_internal.proto) — note the proto2-vs-proto3 and gogoproto-extension caveats; verify our parser handles them or strip annotations like we do for the public protos. | Watch the cdata-int64 footgun on term/index/lease_id fields (memory feedback_pb_cdata_int64_primitives). raft_internal.proto pulls in etcdserverpb rpc.proto messages we already have. | open | 2 | task | NULL | NULL | 2026-05-20T06:45:15Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:36Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-50g | 9ac78e7144a952bc4f60bc5066817e0c5fc22a141911d263cb1dcbf71563381d | iproto-verify: etcd_member_add vs Cluster.MemberAdd | Add Lua testkit verification for etcd_member_add (mirrors gRPC etcdserverpb.Cluster.MemberAdd). Same test pattern as verify-only tasks: positive path plus at least one error path, response shape matches gRPC, error codes from app/etcd/errors.lua. For UNIMPLEMENTED-by-design endpoints (Cluster.MemberAdd/Remove/Update/Promote), assert the wrapper returns the canonical UNIMPLEMENTED error consistent with gRPC rather than skipping. Goes in test/iproto_parity_test.lua under group iproto/parity/cluster. | Epic tarantool-etcd-w8q. Blocked on the matching impl issue. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:19Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:37Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 1 | |||||||||||||||||||||||
| tarantool-etcd-584 | 6dc9ac8fa6d51da02d14d688bcfb11d64010ca9fd1c2d4497a9ebcca4fc713a9 | iproto-verify: etcd_delete_range vs KV.DeleteRange | Add Lua testkit verification for etcd_delete_range (mirrors gRPC KV.DeleteRange). Successful path: assert response shape matches the gRPC response message after MsgPack round-trip. Error path: at least one error case asserting {code, message} matches app/etcd/errors.lua catalog. Exercise at least one non-default option if applicable. Goes in test/iproto_parity_test.lua under group iproto/parity/kv. Call shape: local resp = etcd_delete_range(req). | Verification-only. Wrapper exists at app/etcd/api.lua. Epic tarantool-etcd-w8q. | open | 3 | task | NULL | NULL | 2026-05-19T14:53:54Z | Eugene Blikh | bigbes@gmail.com | 2026-05-19T14:53:54Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-5ya | 9b560100fb333c692102636077dcdb38c0f9ff7d17a59383c291505c5fd395ec | Lock rejects lease=0 instead of auto-granting a session lease | etcd's v3lock/lock.go auto-grants a fresh 60s orphaned session lease when lease==0. app/etcd/lock.lua:154-157 returns 'etcdserver: lease is not provided'. Everything else about Lock matches (key format, queue order, unlock-by-anyone, lease-expiry release, both wires). Judgment call: test/lock_test.lua:37-40 actively PINS the divergent rejection, so someone chose this deliberately at some point — but it is recorded nowhere. Decide and record: either auto-grant to match etcd, or document the divergence and keep the test as the pin. | Either Lock(lease=0) auto-grants a default-TTL lease and the test is updated, or docs/TODO.md records the divergence with its rationale. | open | 3 | bug | NULL | NULL | 2026-07-17T13:21:26Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T13:21:26Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-5zx | 89ee417ec71ddfded73ee7497c3e7938ddf799b0994f05df2b0d46281d429ce6 | iproto-impl: etcd_maintenance_hash vs Maintenance.Hash | Wrap the existing handler maintenance.hash (already invoked by gRPC/JSON via defs.lua route etcdserverpb.Maintenance.Hash) as a Lua global etcd_maintenance_hash callable via iproto CALL. Mechanical change in app/etcd/api.lua: add rawset(_G, 'etcd_maintenance_hash', wrap('maintenance', 'hash')) under the appropriate service block; append 'etcd_maintenance_hash' to the funcs table so box.schema.func.create plus guest grant runs at role apply; document the wrapper in the module-level LDoc comment. If Maintenance.Hash returns UNIMPLEMENTED today, the wrapper still gets added so the iproto caller observes the same UNIMPLEMENTED error gRPC would return. Election.Observe is server-streaming over gRPC; iproto has no stream primitive, so propose a polling shape (observe_create/_pull/_cancel) mirroring the Watch precedent at api.lua:81-83. | etcd_maintenance_hash is callable from net.box:call('etcd_maintenance_hash', {req}) with no Lua error before handler dispatch. Successful request shape produces same successful response shape as gRPC. box.schema.func.create plus guest grant idempotent on role re-apply. | Epic tarantool-etcd-w8q. Handler at app/etcd/maintenance.lua. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:37Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:36Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | ||||||||||||||||||||||
| tarantool-etcd-60x | b21cef8f1637e804633f1c652b78bb5b4424883973d65867bc58080466b810e5 | iproto-verify: etcd_lease_timetolive vs Lease.LeaseTimeToLive | Add Lua testkit verification for etcd_lease_timetolive (mirrors gRPC Lease.LeaseTimeToLive). Successful path: assert response shape matches the gRPC response message after MsgPack round-trip. Error path: at least one error case asserting {code, message} matches app/etcd/errors.lua catalog. Exercise at least one non-default option if applicable. Goes in test/iproto_parity_test.lua under group iproto/parity/lease. Call shape: local resp = etcd_lease_timetolive(req). | Verification-only. Wrapper exists at app/etcd/api.lua. Epic tarantool-etcd-w8q. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:01Z | Eugene Blikh | bigbes@gmail.com | 2026-05-19T14:54:01Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-6js | 6d5e7ff300f1cf1c4ed13261a9a4d20f53c41d1e464d039968328c85fc381326 | Learner: apply loop — InternalRaftRequest -> existing handlers | Drive committed log entries into our storage. For each committed normal Entry: decode etcdserverpb.InternalRaftRequest, dispatch each command to the existing app-side handler via the *_internal (no-own-transaction) variants — put_internal/delete_range_internal, txn, lease grant/revoke, compact, auth mutations — inside one box.begin()/commit() per entry so applied_index advances atomically with the data. CRITICAL: our revision counter must adopt the entry's mod_revision/main-revision rather than allocating locally (revision alignment), so local reads return etcd-consistent revisions and our Watch subsystem (kv_history) reconstructs correct events for downstream clients. Skip/ignore command types we don't model yet, logging a warning, until coverage is complete. | Revision alignment is the same hard constraint the watch-mirror epic flagged (tarantool-etcd-zws) — but here the authoritative revision is in the log entry, not a Range response. Reuse the on_commit broadcast path (memory feedback_tarantool_trigger_broadcast) so local watchers wake correctly. | open | 2 | task | NULL | NULL | 2026-05-20T06:45:16Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:37Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 1 | |||||||||||||||||||||||
| tarantool-etcd-6lj | d4341bb5465f48998fc359076cde70c55d1e60ea8e549e16b8e29211154a84b3 | iproto-verify: etcd_watch_create vs Watch.Watch-create | Add Lua testkit verification for etcd_watch_create (mirrors gRPC Watch.Watch-create). Successful path: assert response shape matches the gRPC response message after MsgPack round-trip. Error path: at least one error case asserting {code, message} matches app/etcd/errors.lua catalog. Exercise at least one non-default option if applicable. Goes in test/iproto_parity_test.lua under group iproto/parity/watch. Call shape: local resp = etcd_watch_create(req). | Verification-only. Wrapper exists at app/etcd/api.lua. Epic tarantool-etcd-w8q. | open | 3 | task | NULL | NULL | 2026-05-19T14:53:56Z | Eugene Blikh | bigbes@gmail.com | 2026-05-19T14:53:56Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-6zn | 6c540ee1d9aff95fd81e3a781e0e27252675c8c85405333479a596dbab013193 | Learner: MemberAddAsLearner handshake + cluster version negotiation | Join flow: the cluster must know about us before it streams. Either the operator runs MemberAddAsLearner (etcdctl member add --learner) with our advertise_peer_url and we react, or we self-register. Implement the member-side handshake: respond correctly to the cluster's version probe (/version, X-Server-Version), expose our peer URL, and accept the assigned member ID + cluster ID so transport headers match. Track cluster_version and refuse to start if it's outside a supported etcd range. Until promoted (out of v1 scope), report ourselves as a learner in any membership view. | MemberAddAsLearner is already implemented on OUR server side (cluster.lua) for the inverse direction; here we are the member being added to a foreign cluster. Cluster-ID mismatch is the most common silent-failure mode. | open | 2 | task | NULL | NULL | 2026-05-20T06:45:18Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:36Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-70j | 7fc065ea5ee00fb1ca4be14474b96cab64a446406a541f3874fd9adff86e3279 | iproto-verify: etcd_member_promote vs Cluster.MemberPromote | Add Lua testkit verification for etcd_member_promote (mirrors gRPC etcdserverpb.Cluster.MemberPromote). Same test pattern as verify-only tasks: positive path plus at least one error path, response shape matches gRPC, error codes from app/etcd/errors.lua. For UNIMPLEMENTED-by-design endpoints (Cluster.MemberAdd/Remove/Update/Promote), assert the wrapper returns the canonical UNIMPLEMENTED error consistent with gRPC rather than skipping. Goes in test/iproto_parity_test.lua under group iproto/parity/cluster. | Epic tarantool-etcd-w8q. Blocked on the matching impl issue. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:28Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:37Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 1 | |||||||||||||||||||||||
| tarantool-etcd-75u | 3b4a89d5020a6940cb1cb47fd3dc0b31b89ea640b8c4805c0c787a42b113d130 | Replica route: write handling (reject vs proxy-to-leader) | Define what happens when a local client issues a write (Put/DeleteRange/Txn-with-mutations/Lease grant) while in replica mode. Two modes from the parent design: - mirror (read-only): reject writes with an etcd-shaped error (or NotLeader/redirect) so clients fail fast. Reads served locally. - proxy: forward the write to the upstream leader via clientv3, then block until the resulting event arrives over our Watch tail before returning (read-your-writes), so the local view is consistent with what the client just wrote. Work: - Gate all mutating handlers (kv.put/delete_range, txn with mutations, lease grant/revoke) on replica mode. - Implement the reject path (mirror) and the proxy path (proxy) including the wait-for-watch-echo on proxy. - Pick the error code for the reject path consistent with etcd (likely a clear PermissionDenied/NotLeader-style message). | Proxy path read-your-writes: forward, capture upstream response revision, wait until watch tail applies >= that revision, then return. Keep v1 simple — mirror/reject may ship first, proxy as a follow-up. | open | 2 | task | NULL | NULL | 2026-05-20T06:17:56Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:37Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 1 | |||||||||||||||||||||||
| tarantool-etcd-7e5 | 75e8819d6b6368f1f582dd0fddb210ddbab88c3d8434376cf22eda69ab8aa33b | Malformed Txn compare enum leaves a dangling box transaction — poisons the JSON keep-alive connection | app/etcd/txn.lua:280 runs evaluate_compares AFTER box.begin() (:278) but OUTSIDE the pcall/rollback guard (:285-289). pb.json.decode accepts an out-of-range Compare.target (e.g. {"target":99}), which raises unknown_compare_target (txn.lua:70) and leaves box.is_in_txn() == true. The JSON gateway serves keep-alive requests on ONE per-connection fiber (lib/http1/server.lua:495), so every subsequent request on that connection then fails with INTERNAL 'Operation is not permitted when there is an active transaction'. One malformed request kills the connection permanently. gRPC self-heals (fresh fiber per request, lib/nghttp2/grpc.lua:551). etcd returns InvalidArgument and the connection is unaffected. This is the one finding in the sweep that was reproduced in-process rather than argued from reading. Tarantool EE config.etcd clients ride this gateway. | Validate compare target/result enums before box.begin, or move evaluate_compares + revision.next inside the pcall-with-rollback. Belt and braces: a box.rollback safety net in the wire adapters so no handler can ever leak a transaction into the next request on a pooled fiber. | POST /v3/kv/txn with {"target":99} returns InvalidArgument and the SAME connection serves the next request normally; box.is_in_txn() is false after the error; a Lua test pins it; the adapter-level rollback net is covered. | open | 1 | bug | NULL | NULL | 2026-07-17T13:19:07Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T13:19:07Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | ||||||||||||||||||||||
| tarantool-etcd-7ey | be7b04f2cbf862eb22cfe43df8812d0bcb20c7b247f8edc485850c3fb44cbe34 | Replica route: connect as read-only replica to an existing etcd cluster | Add a mode where this server attaches to an existing/real etcd cluster as a read-only replica (mirror), serving reads locally while transparently keeping in sync with the upstream etcd. Motivation: lets the Tarantool store act as a read scale-out / local cache in front of a real etcd deployment, and is a stepping stone toward migration (shadow the production cluster, compare, then cut over). Design questions to resolve: - Sync mechanism: use etcd's Watch (clientv3) from revision 0 to stream the full keyspace + tail live, persisting events into our etcd_* spaces and advancing our revision counter to match upstream. Need an initial Range snapshot (or Snapshot RPC) for bootstrap, then Watch with progress notifications + compaction handling (ErrCompacted -> re-snapshot). - Revision alignment: our global revision sequence must track upstream's mod_revision exactly so reads return etcd-consistent revisions. This constrains the local revision allocator while in replica mode. - Write handling: reject writes locally with a redirect/error, OR proxy them to the upstream leader and wait for the change to come back over the watch (read-your-writes). Pick one; proxying is closer to etcd's learner/proxy semantics. - Lease/Watch/Auth: leases and auth state also need mirroring or pass-through. Scope what's in v1 (KV-only read replica) vs later. - Config: new role config block (e.g. replica.upstream_endpoints, replica.mode = mirror|proxy, credentials). Likely a multi-phase epic; this ticket captures the umbrella. Split bootstrap-snapshot, watch-tail-sync, revision-alignment, and write-proxying into child issues once the design is settled. | Reuses clientv3 Watch semantics we already understand from conformance work (ErrCompacted=11, progress notify). Consider relation to the Tarantool-native replicaset path (tarantool_rs3) — this is the opposite direction (follow a foreign etcd, not our own Raft). | open | 2 | feature | NULL | NULL | 2026-05-20T05:48:15Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:36Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-7kb | 85033a35f894073794d9015ec94b0b8854abfbcee2245a2eb1683814a67c68c3 | iproto-impl: etcd_member_add vs Cluster.MemberAdd | Wrap the existing handler cluster.member_add (already invoked by gRPC/JSON via defs.lua route etcdserverpb.Cluster.MemberAdd) as a Lua global etcd_member_add callable via iproto CALL. Mechanical change in app/etcd/api.lua: add rawset(_G, 'etcd_member_add', wrap('cluster', 'member_add')) under the appropriate service block; append 'etcd_member_add' to the funcs table so box.schema.func.create plus guest grant runs at role apply; document the wrapper in the module-level LDoc comment. If Cluster.MemberAdd returns UNIMPLEMENTED today, the wrapper still gets added so the iproto caller observes the same UNIMPLEMENTED error gRPC would return. Election.Observe is server-streaming over gRPC; iproto has no stream primitive, so propose a polling shape (observe_create/_pull/_cancel) mirroring the Watch precedent at api.lua:81-83. | etcd_member_add is callable from net.box:call('etcd_member_add', {req}) with no Lua error before handler dispatch. Successful request shape produces same successful response shape as gRPC. box.schema.func.create plus guest grant idempotent on role re-apply. | Epic tarantool-etcd-w8q. Handler at app/etcd/cluster.lua. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:19Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:36Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | ||||||||||||||||||||||
| tarantool-etcd-7mr | 480b25fdf5ff5cf67d194ef1d37dbc6ca998a14ea12872f13fe75ebb09d6ee12 | UserAdd options.no_password decoded and dropped | etcd stores a passwordless user, refuses Authenticate for them, and returns ErrNoPasswordUser from UserChangePassword. app/etcd/auth.lua:462-478 never reads req.options (grep: 0 hits). The main-line --no-password path COINCIDENTALLY works (no chap-sha1 digest is stored, so auth fails anyway) — but no_password=true SENT WITH a password stores the password and lets the user authenticate, which is the actual bug. user_change_password (auth.lua:498-507) has no ErrNoPasswordUser guard. Low practical impact today: the CN-auth path this feature exists for needs TLS, which we do not have (tarantool-etcd-nbs). | no_password=true drops any supplied password and blocks Authenticate; UserChangePassword returns ErrNoPasswordUser; test pins the with-password case. | open | 3 | bug | NULL | NULL | 2026-07-17T13:21:28Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T13:21:28Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-828 | 07dec43fc47708505ca97b18aaf13e621ccca512a7a74fe5c56e9fc9f0feea68 | iproto-impl: etcd_member_promote vs Cluster.MemberPromote | Wrap the existing handler cluster.member_promote (already invoked by gRPC/JSON via defs.lua route etcdserverpb.Cluster.MemberPromote) as a Lua global etcd_member_promote callable via iproto CALL. Mechanical change in app/etcd/api.lua: add rawset(_G, 'etcd_member_promote', wrap('cluster', 'member_promote')) under the appropriate service block; append 'etcd_member_promote' to the funcs table so box.schema.func.create plus guest grant runs at role apply; document the wrapper in the module-level LDoc comment. If Cluster.MemberPromote returns UNIMPLEMENTED today, the wrapper still gets added so the iproto caller observes the same UNIMPLEMENTED error gRPC would return. Election.Observe is server-streaming over gRPC; iproto has no stream primitive, so propose a polling shape (observe_create/_pull/_cancel) mirroring the Watch precedent at api.lua:81-83. | etcd_member_promote is callable from net.box:call('etcd_member_promote', {req}) with no Lua error before handler dispatch. Successful request shape produces same successful response shape as gRPC. box.schema.func.create plus guest grant idempotent on role re-apply. | Epic tarantool-etcd-w8q. Handler at app/etcd/cluster.lua. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:28Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:36Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | ||||||||||||||||||||||
| tarantool-etcd-8ss | 848bb73c6cac418f63bae8d196061ccb5bca2ddd670d7fc794f0dde560c28cab | Example 2: 3-node EE replicaset self-hosting its own etcd config (file → etcd migration) | Add examples/tarantool-ee-self-hosted-etcd: boot a 3-instance EE replicaset (election failover) from a local cluster-config.yaml where every node runs app.roles.etcd (replicated KV store, read_pref=any). Seed the cluster's OWN etcd store with that config, then rolling-restart each instance to bootstrap from TT_CONFIG_ETCD_* (no --config). Document the cold-boot circularity: self-hosted etcd-config is HA under rolling restart but needs the local file retained as a cold-boot seed. Justfile + README + cluster-config.yaml. | VERIFIED end-to-end on EE 3.7.0 (up-file/seed/migrate/probe/reload all pass; migrate idempotent). Root cause of the user's failure: EE config.etcd source pins to Endpoints[0] without failover, so endpoints must be LEADER-FIRST (not peers-first). Also fixed: leader-migrated-last via per-step recompute, _wait-healthy gate, and a pipefail bug (grep -vx on last node exited 1 after work was done). Example complete. | closed | 2 | feature | Eugene Blikh | NULL | 2026-05-20T11:16:17Z | Eugene Blikh | bigbes@gmail.com | 2026-05-20T11:38:01Z | 2026-05-20T11:38:01Z | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | Example written and verified end-to-end on Tarantool EE 3.7.0. | 0 | NULL | NULL | NULL | 0 | 2026-05-20T11:16:28Z | 0 | ||||||||||||||||||||||
| tarantool-etcd-8t0 | 2f5cdad421dff2592e814db34d268bb5c422c47dc77c20b2e22988fec572c1a4 | iproto-verify: etcd_lease_keepalive vs Lease.LeaseKeepAlive | Add Lua testkit verification for etcd_lease_keepalive (mirrors gRPC Lease.LeaseKeepAlive). Successful path: assert response shape matches the gRPC response message after MsgPack round-trip. Error path: at least one error case asserting {code, message} matches app/etcd/errors.lua catalog. Exercise at least one non-default option if applicable. Goes in test/iproto_parity_test.lua under group iproto/parity/lease. Call shape: local resp = etcd_lease_keepalive(req). | Verification-only. Wrapper exists at app/etcd/api.lua. Epic tarantool-etcd-w8q. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:00Z | Eugene Blikh | bigbes@gmail.com | 2026-05-19T14:54:00Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-8zm | a1dc390eef73855b175634aefef89a6b7dd681297ac7b19050c21873bd3076d4 | iproto-verify: etcd_role_get vs Auth.RoleGet | Add Lua testkit verification for etcd_role_get (mirrors gRPC Auth.RoleGet). Successful path: assert response shape matches the gRPC response message after MsgPack round-trip. Error path: at least one error case asserting {code, message} matches app/etcd/errors.lua catalog. Exercise at least one non-default option if applicable. Goes in test/iproto_parity_test.lua under group iproto/parity/auth. Call shape: local resp = etcd_role_get(req). | Verification-only. Wrapper exists at app/etcd/api.lua. Epic tarantool-etcd-w8q. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:14Z | Eugene Blikh | bigbes@gmail.com | 2026-05-19T14:54:14Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 | |||||||||||||||||||||||
| tarantool-etcd-929 | a77b3b4bd7e4dc3f8e1d31816ef2336c068d67c8537564fdbbb577303e2eab52 | iproto-verify: etcd_maintenance_alarm vs Maintenance.Alarm | Add Lua testkit verification for etcd_maintenance_alarm (mirrors gRPC etcdserverpb.Maintenance.Alarm). Same test pattern as verify-only tasks: positive path plus at least one error path, response shape matches gRPC, error codes from app/etcd/errors.lua. For UNIMPLEMENTED-by-design endpoints (Cluster.MemberAdd/Remove/Update/Promote), assert the wrapper returns the canonical UNIMPLEMENTED error consistent with gRPC rather than skipping. Goes in test/iproto_parity_test.lua under group iproto/parity/maintenance. | Epic tarantool-etcd-w8q. Blocked on the matching impl issue. | open | 3 | task | NULL | NULL | 2026-05-19T14:54:31Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:37Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 1 | |||||||||||||||||||||||
| tarantool-etcd-95d | aa79fa3309127feb7818d5e2f3e8e9ef4bf8df4546546f627a0dd6de608e9dc2 | [epic] Performance | Roadmap P0 blocker — umbrella for throughput/latency work toward Go-etcd-level performance. Existing point-fixes (nghttp2 send-path rewrite, lease fsync, lease grant/revoke regression) hang under this; the C-protobuf path (tarantool-etcd-cpp) is the other major lever. Tracks profiling-driven optimization across the gRPC/JSON request paths. | Roadmap-only umbrella; children are pre-existing perf issues. | open | 0 | epic | NULL | NULL | 2026-05-20T06:55:37Z | Eugene Blikh | bigbes@gmail.com | 2026-07-17T14:27:36Z | NULL | NULL | 0 | NULL | NULL | NULL | 0 | 0 | 0 | <binary> | 0 | NULL | NULL | NULL | 0 | NULL | 0 |