Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

Let requesters choose the lease ID

Đang mở
#410 3 bình luận 0 reaction 1 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@V3RON đang làm issue này rồi.

Từ ngày 6/10/2026.

  • #423 của @V3RON — đang mở

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
45/100
Loại issue
Tính năng
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
typescript

Hướng nghiên cứu

Start by tracing the lease request through the modules listed in the technical spec, especially src/leasing/lease-request-book.ts, src/leasing/wait-queue.ts, and the gateway dispatcher and fleet coordinator. Then compare the single-host and gateway flows against the contract and behavior sections. Done means the completion conditions hold for both paths, including ID conflicts, renewals, releases, and requests without a supplied ID.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

feature:ready

Request: none

Problem

agent-device has its own leasing engine. When it leases a device through simlock, simlock makes a new lease ID. To link the two leases, agent-device has to store a mapping from its lease ID to simlock's.

Who it is for

agent-device, and any other requester that already has its own lease ID and wants simlock to use it. This is temporary. It lasts while agent-device runs its own leasing engine. Later agent-device moves to simlock leases and this need goes away.

Outcome

A requester can send its own lease ID with a lease request. The lease simlock grants has exactly that ID, on a single host and through a gateway. No mapping is needed. A request without an ID works as today.

How it works for the user

flowchart TD
  R[lease request] --> H{leaseId given?}
  H -- no --> G[ID generated as today]
  H -- yes --> V{1–64 ASCII letters, digits, - and _, first a letter or digit?}
  V -- no --> B[BAD_REQUEST]
  V -- yes --> I{same idempotency key seen before?}
  I -- "yes, same leaseId" --> RP[replay the first answer]
  I -- "yes, other or no leaseId" --> IC[IDEMPOTENCY_CONFLICT]
  I -- no --> Q{requester already holds a lease or a waiting request?}
  Q -- yes --> RA[REQUESTER_ALREADY_LEASED, as today]
  Q -- no --> U{an active lease or a waiting request holds this ID?}
  U -- yes --> T[LEASE_ID_TAKEN]
  U -- no --> L[lease granted with exactly this ID]

Before:

$ simlock lease --platform ios --lease-id ad-7f3a
error: unknown option --lease-id

After:

$ simlock lease --platform ios --lease-id ad-7f3a
leased ad-7f3a ...
$ simlock lease --platform ios --lease-id ad-7f3a   # from another requester, while the first is held
error LEASE_ID_TAKEN: lease ID ad-7f3a is already in use
$ echo $?
13

The same field is on the HTTP API (POST /v1/lease-requests, body field leaseId), the client (requestLease({ leaseId })) and the MCP lease tool.

Examples

leaseId sent Today After
none lse_<uuid>, or <worker>.lse_<uuid> through a gateway same as today
myid BAD_REQUEST (unknown field) lease myid, through a gateway too
ad-7f3a_01 BAD_REQUEST lease ad-7f3a_01
lse_123 BAD_REQUEST lease lse_123
"" BAD_REQUEST BAD_REQUEST
64 characters BAD_REQUEST lease with that ID
65 characters BAD_REQUEST BAD_REQUEST
w1.myid (a dot) BAD_REQUEST BAD_REQUEST: the dot is kept for gateway IDs
my id (a space) BAD_REQUEST BAD_REQUEST
-s, --help, _x (first not a letter or digit) BAD_REQUEST BAD_REQUEST
ząb (not ASCII) BAD_REQUEST BAD_REQUEST
MyID while myid is active — lease MyID: case matters
myid while a lease myid is active — LEASE_ID_TAKEN
myid while a request for myid is waiting — LEASE_ID_TAKEN
idempotency key K first with myid, retried with other or none — IDEMPOTENCY_CONFLICT
idempotency key K first with none, retried with myid — IDEMPOTENCY_CONFLICT
requester R holds a lease and asks again with any leaseId — REQUESTER_ALREADY_LEASED, as today

What could go wrong

  • Two requesters pick the same ID. The second is refused with LEASE_ID_TAKEN. Unlikely, since caller IDs are expected to be unique.
  • Through a gateway, a local client on a worker could already hold the same ID. That worker refuses the gateway request with LEASE_ID_TAKEN, and a fleet lease list could show the ID twice. Accepted: caller IDs are expected to be unique.
  • Right after a gateway restart, two workers could each hold a lease with the same caller-chosen ID. The first one reported keeps the ID; the other expires at its TTL. Accepted here; reviewed in #412.
  • If simlock fails to write a settled request to disk, its ID stays taken until the daemon restarts. Accepted: the same failure already blocks the requester today.
  • A gateway that has just lost its routing table answers UNKNOWN_LEASE for a renew or release until it rebuilds from the workers. The window is short.
  • A requester that breaks its uniqueness guarantee and sends an ID again after its lease ended gets a new lease with it. Simlock does not defend against this: retries, timers and recovery aimed at the old lease could reach the new one.

Words used

  • lease ID: the name of one lease, e.g. lse_3f2a… or ad-7f3a. Renew and release name a lease by it.
  • caller-chosen ID: a lease ID the requester sent in leaseId, instead of one simlock generated. The requester guarantees it is unique for all time: it names one lease and is never sent again after that lease ends.
  • active lease: a lease that is granted and not yet released or expired.
  • waiting request: a lease request that was accepted but has no device yet. A daemon restart fails every waiting request, which frees its ID.
  • idempotency key: an optional key on a request. A retry with the same key gets the first answer instead of a second lease.
  • gateway: a simlock that hands out devices from several machines (workers). Today it names a lease <worker>.<worker lease ID>.
  • routing table: the gateway's in-memory list of which worker holds which lease. It is a cache: workers own the truth, and the gateway rebuilds the table from them.

Non-goals

  • Mapping agent-device leases to simlock leases in any stored form.
  • Checking a caller-chosen ID against leases that local clients hold on other workers.
  • Detecting the same caller-chosen ID on two workers after a gateway restart beyond what Behaviour says (#412).
  • Letting a caller choose the ID of a lease request (req_…), a device or any other record.
  • Migrating agent-device to simlock leases.
  • Remembering IDs already used, or defending against a requester that sends one again.

Completion conditions

  • A lease request with leaseId: "ad-7f3a" is granted a lease with ID ad-7f3a, on a single host and through a gateway.
  • Renew and release by that ID work, on a single host and through a gateway.
  • A second request for an ID held by an active lease or a waiting request fails with LEASE_ID_TAKEN.
  • Every rejected input in Examples answers BAD_REQUEST.
  • A LEASE_ID_TAKEN refusal emits lease.rejected with reason lease-id-taken.
  • A request without leaseId gets an ID exactly as today.

Open questions

None.

Decisions

  • ADR 0020 — A requester may choose its lease id, and the gateway passes it through bare (#418): narrows ADR 0005 §16 and decision 5 so a caller-chosen ID crosses the gateway with no worker prefix, routed by a rebuildable cache.

Technical spec

Modules touched
flowchart LR
  CLI[cli *] --> CL[simlock-client *]
  MCP[mcp *] --> D
  HTTP[http *] --> D
  CL --> D[daemon dispatcher *]
  D --> LA[leasing: acquisition coordinator, request book, lifecycle *]
  LA --> REG[core registry *]
  LA --> BUS[bus: lease.rejected reason *]
  D --> EC[daemon error-code *]
  D --> GW[gateway: dispatcher, fleet coordinator, lease index *]
  GW --> CL
  CON[contract: operations, schemas, errors, protocol *] -.-> CL
  CON -.-> HTTP

* = changed.

sequenceDiagram
  participant C as caller
  participant G as gateway
  participant W as worker
  C->>G: lease.request leaseId=myid
  G->>G: myid in its leases or its queue? → LEASE_ID_TAKEN
  G->>W: lease.request leaseId=myid, noWait
  alt worker holds myid (lease or waiting request)
    W-->>G: LEASE_ID_TAKEN
    G-->>C: LEASE_ID_TAKEN
  else granted
    W-->>G: lease myid, idChosenByRequester=true
    G-->>C: lease myid
  end
Contract and event changes
  • lease.request (contract and HTTP body) gains optional leaseId, matching ^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$: ASCII only, first character a letter or digit, 1–64 characters, case-sensitive. Anything else is BAD_REQUEST.
  • The lease record gains idChosenByRequester: boolean. The registry writes it at grant. A state file written before this field existed loads it as false.
  • New error code LEASE_ID_TAKEN: HTTP 409, CLI exit 13, details { leaseId }.
  • CLI simlock lease gains --lease-id <id>.
  • The gateway–worker protocol version goes up by one. A worker on the old version is incompatible, as for every earlier bump.
  • lease.rejected gains the reason lease-id-taken. Both docs/EVENTS.md and docs/internal/EVENTS.md list it. No other event changes; lease.* payloads already carry the lease ID.
Behaviour
  • One check per host. On a single host, the clash check runs once: in the acquisition coordinator's serialized admission, against the registry's active leases and its open lease-request records. Registry.createLease uses the request's leaseId when given and lse_<uuid> otherwise.
  • Order of checks. The existing one-lease-per-requester check runs first. The ID check runs after it, in the same admission.
  • Waiting requests hold the ID. The lease request record stores leaseId, so the ID is held while the request waits. A restart fails every waiting request, as today, which frees its ID.
  • The refusal is an event. A LEASE_ID_TAKEN refusal emits lease.rejected with reason lease-id-taken, like every other admission refusal.
  • Idempotency. A replay compares leaseId as well as the device request. A mismatch, including one side missing, is IDEMPOTENCY_CONFLICT.
  • Where leaseId travels. It is a request option beside idempotencyKey, not part of the device request. lease.requested and lease.rejected requestSpec, the status waiting spec and the stored request's request stay as today. LeaseLifecycle.grant passes it to createLease.
  • Uniqueness is the requester's. Simlock keeps no record of used IDs. It does not detect an ID sent again after its lease ended.
  • Gateway clash check. The gateway runs the clash check against its lease index and its own open requests before it queues. Its refusal emits lease.rejected with reason lease-id-taken, as its own already-leased refusal does today. The gateway dispatcher passes leaseId to the fleet coordinator, which forwards it to the worker.
  • Gateway lease ID. When the gateway forwarded a leaseId, the gateway lease ID is that forwarded value, with no <worker>. prefix. The gateway never takes the ID from the worker's echo, as it already does for ownerId. A worker grant whose lease ID differs from the forwarded value is not entered in the index: the gateway releases that worker lease, logs a warning naming the worker, and treats the attempt as failed, so the request goes back to the gateway queue as after an unreachable worker. Grants without a forwarded leaseId are named as today.
  • Gateway rebuild. rebuildFromWorker names a reported lease bare only when idChosenByRequester is true and its ID matches the leaseId pattern. Otherwise it is prefixed as today; a flagged lease whose ID does not match is prefixed and logged as a warning.
  • Same bare ID on two workers. The index never overwrites a bare entry that belongs to another worker. On rebuild, the first entry stays; the gateway logs a warning naming both workers and does not route to the second lease, which expires at its TTL. On a grant, the gateway releases the new worker lease and answers LEASE_ID_TAKEN. Tracking a missing bare entry counts only reports from its own worker, so another worker's lease with the same ID never resets it.
  • Gateway forwarding errors. A worker answer of LEASE_ID_TAKEN is passed to the caller. The gateway does not try another worker.
  • Unknown gateway lease. A renew or release for a lease ID not in the gateway's lease index answers UNKNOWN_LEASE, as today. There is no on-demand refresh.
Other code on the same state
  • Registry.createLease and beginRelease. Release removes the lease from the snapshot in the same commit, so the clash check no longer sees the ID once release starts. Unchanged.
  • Lease request book (src/leasing/lease-request-book.ts). Replay changes as above. Retention, pruning and requestIdForLease are unchanged.
  • Wait queue (src/leasing/wait-queue.ts). It already refuses a second request from the same requester. That check runs first; the ID check runs after it, in the same admission.
  • Lease startup (src/leasing/lease-startup.ts). It fails every open request on restart before admission opens, so a restart frees every waiting request's ID. Unchanged.
  • Code that assumes a lease ID never comes back: the lease health monitor's recovery guard, the expiry scheduler, doctor --fix, HTTP lease notices, the daemon's selfInitiatedReleases, the registry's unknown lease fields, the client's #deliveredLeaseLost, and the MCP session's #announcedLost. The requester's uniqueness guarantee keeps that assumption true. Unchanged.
  • Gateway lease index (src/gateway/lease-index.ts). add, #addReported and missing-tracking change as "Same bare ID on two workers" says. #removedByEvent is unchanged.
Rules in play
  • safety.md: wire input is a claim. Bound and validate leaseId before it is used or stored, and do not trust a worker's echo of it.
  • architecture.md: one place enforces a rule. There is one clash check per process: the coordinator on a host, and the fleet coordinator on a gateway.
  • testing.md: every test fails for the right reason.
Tests

Seams: e2e/http-api.test.ts (single host, fake driver) and e2e/gateway-fleet.test.ts (gateway and workers) for behaviour a caller sees. Where those cannot reach: src/cli/index.test.ts (CLI flag), src/mcp/session.test.ts (MCP), src/leasing/lease-startup.test.ts (restart), src/core/registry.test.ts (state file), src/gateway/fleet-coordinator.test.ts and src/gateway/lease-index.test.ts (worker echoes, rebuild, two workers).

  • a lease request with leaseId gets a lease with exactly that ID
  • a lease request without leaseId gets an lse_ ID as today
  • renew and release by a caller-chosen ID work
  • each rejected input from Examples answers BAD_REQUEST
  • a 64-character leaseId is accepted
  • MyID and myid are two different IDs
  • a second request for an ID held by an active lease fails with LEASE_ID_TAKEN, HTTP 409
  • a second request for an ID held by a waiting request fails with LEASE_ID_TAKEN
  • a requester that already holds a lease gets REQUESTER_ALREADY_LEASED, not LEASE_ID_TAKEN, even when it repeats its own lease ID
  • a LEASE_ID_TAKEN refusal emits lease.rejected with reason lease-id-taken
  • lease.requested and lease.rejected requestSpec, and the status waiting spec, carry no leaseId
  • a retry with the same idempotency key and the same leaseId replays the first answer
  • a retry with the same idempotency key and a different or missing leaseId fails with IDEMPOTENCY_CONFLICT
  • a retry with the same idempotency key that adds a leaseId the first call did not have fails with IDEMPOTENCY_CONFLICT
  • after a daemon restart, an ID held only by a waiting request is accepted again
  • a state file without idChosenByRequester loads every lease as false
  • the CLI --lease-id flag sends leaseId and exits 13 on LEASE_ID_TAKEN
  • the MCP lease tool passes leaseId through
  • through a gateway, a caller-chosen ID comes back with no worker prefix
  • through a gateway, a request without leaseId gets <worker>.lse_ as today
  • through a gateway, renew and release by a caller-chosen ID reach the right worker
  • through a gateway, a second request for an ID held by a gateway lease fails with LEASE_ID_TAKEN, emits lease.rejected with reason lease-id-taken, and is not forwarded
  • through a gateway, a second request for an ID held by a waiting gateway request fails with LEASE_ID_TAKEN and is not forwarded
  • through a gateway, a worker's LEASE_ID_TAKEN is passed to the caller and no other worker is tried
  • a worker grant whose lease ID differs from the forwarded leaseId is released on the worker, never enters the gateway index, and the request is queued again
  • after a gateway restart, a caller-chosen lease is rebuilt under its bare ID
  • a rebuilt lease flagged idChosenByRequester whose ID does not match the leaseId pattern is prefixed and logged
  • the gateway answers UNKNOWN_LEASE for a renew of an ID missing from its index
  • when two workers report the same caller-chosen ID on rebuild, the first stays routed, a warning names both workers, and renew of that ID reaches the first worker
  • a grant for a bare ID the index maps to another worker is released on the new worker and answers LEASE_ID_TAKEN, and the first entry stays routed
  • a second worker reporting the same bare ID does not reset the first entry's missing count

Written by an agent.

Ngôn ngữ chính
TypeScript
Star
15
Fork
1
Merge trung bình
8 giờ 23 phút
Pull request đã merge (30 ngày)
136

Chuẩn bị môi trường

Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của callstackincubator/simlock

Tất cả issue của callstackincubator/simlock

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.