Let requesters choose the lease ID
I maintainer di solito rispondono entro 1 giorno
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 45/100
- Tipo di issue
- Funzionalità
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- typescript
- Ambito
- backend-api-design, cli, distributed-systems
Direzione di ricerca
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.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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_LEASEfor 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…orad-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 IDad-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_TAKENrefusal emitslease.rejectedwith reasonlease-id-taken. - A request without
leaseIdgets 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 optionalleaseId, 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 isBAD_REQUEST.- The lease record gains
idChosenByRequester: boolean. The registry writes it at grant. A state file written before this field existed loads it asfalse. - New error code
LEASE_ID_TAKEN: HTTP 409, CLI exit 13, details{ leaseId }. - CLI
simlock leasegains--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.rejectedgains the reasonlease-id-taken. Bothdocs/EVENTS.mdanddocs/internal/EVENTS.mdlist 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.createLeaseuses the request'sleaseIdwhen given andlse_<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_TAKENrefusal emitslease.rejectedwith reasonlease-id-taken, like every other admission refusal. - Idempotency. A replay compares
leaseIdas well as the device request. A mismatch, including one side missing, isIDEMPOTENCY_CONFLICT. - Where
leaseIdtravels. It is a request option besideidempotencyKey, not part of the device request.lease.requestedandlease.rejectedrequestSpec, thestatuswaitingspecand the stored request'srequeststay as today.LeaseLifecycle.grantpasses it tocreateLease. - 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.rejectedwith reasonlease-id-taken, as its own already-leased refusal does today. The gateway dispatcher passesleaseIdto 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 forownerId. 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 forwardedleaseIdare named as today. - Gateway rebuild.
rebuildFromWorkernames a reported lease bare only whenidChosenByRequesteris true and its ID matches theleaseIdpattern. 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_TAKENis 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.createLeaseandbeginRelease. 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 andrequestIdForLeaseare 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'sselfInitiatedReleases, 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,#addReportedand missing-tracking change as "Same bare ID on two workers" says.#removedByEventis unchanged.
Rules in play
safety.md: wire input is a claim. Bound and validateleaseIdbefore 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-idflag 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.
- Lingua principale
- TypeScript
- Stelle
- 15
- Fork
- 1
- Merge medio
- 8h 23m
- PR unite (30g)
- 136
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di callstackincubator/simlock
-
bug:new
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
callstackincubator/simlock#424 ·
I maintainer di solito rispondono entro 1 giorno
-
bug:new
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
callstackincubator/simlock#422 ·
I maintainer di solito rispondono entro 1 giorno
-
bug:new
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
callstackincubator/simlock#420 ·
I maintainer di solito rispondono entro 1 giorno
-
bug:new
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
callstackincubator/simlock#350 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Flaky e2e: gateway-fleet reads a worker's capacity before the worker has left startingForse già presa @V3RON l’ha presa 2 giorni fa. Apertabug:ready flaky-test
Difficoltà 4/5 1-2 giorni Idoneità per principianti 48/100
callstackincubator/simlock#414 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di callstackincubator/simlock
Issue simili
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 82/100
siyuan-note/siyuan#20313 ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 92/100
alunduil/projects-v2-sync#14 ·
-
Service process inherits the caller's cwd at first use, holding that folder open on Windows (EBUSY)Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
-
DevTools page styles leak into the host app in developmentForse già presa @onmax l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
nuxt-modules/better-auth#567 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno