mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-27 06:47:07 +00:00
a6f96107f4
A lease was "ours" when its free-text label and the account matched, and the
label was the agent's display name, so names carried correctness: two
unregistered Claude Code sessions on one account both held as "claude-code",
and two stateless remote MCP clients shared their bare client name.
A lease is now held under a WORKER ID plus the account (lead rulings on the
item); the label is display only.
- Format: "wk_" + 32 lowercase hex (128 random bits), minted by the CLIENT.
The server checks the shape (400 invalid_worker_id) and always matches it
together with the account; a worker id with no signed-in user is refused.
- Store: one predicate, leaseOurs, shared by claim, release and the
next/ready foreign filter, COALESCEd so its negation is exact. A
worker-bound lease is ours iff worker id + account match; a name-only one
keeps the label + account rule, and a worker refreshing its own name-only
lease binds it. A name-only caller never matches a worker-bound lease,
even under the same label (the collision fixed). Migrations SQLite 142 /
PG 112 add items.lease_worker_id (NUL-excluded: format-validated in the
store before any SQL).
- Server: the worker id comes from the body, then X-Pad-Worker, then a
?worker_id query, then the remote MCP caller. The claim answers with the
caller's own worker_id; reads and the 409 show only the SHORT id
("wk_3f2a"); a name-only caller colliding under its own name is told how to
get a worker id.
- CLI: `pad session register` mints the id into the session's registry
record, keeps it when the same process re-registers (a /clear: same pid
and start token) and mints a new one for a new process; older records get
one lazily; $PAD_WORKER_ID overrides; X-Pad-Worker is sent on every call;
--worker-id on claim, release, project next and ready (the local-stdio MCP
path for worker_id).
- MCP: the remote default holder label is the bare client name (dropping
BUG-2772's "#xxxxxxxx" suffix); the worker id is derived from the account
and the MCP session; an explicit `worker_id` (pad_item claim/release,
pad_project next/ready) overrides it and is the resume path across
connections. ToolSurfaceVersion 0.97 (additive). The catalog text pays for
itself: tools/list is 4 bytes smaller (the stale holder text is fixed and
the ttl text trimmed).
- Docs: a claim/release section in docs/api.md (it had none), the
docs/mcp.md Leases paragraph and changelog, SKILL.md, CLI help. The web UI
renders no lease today; the TS type gains the short worker for a future
badge.
Tests: store worker-id rules; server (two workers under one name, account
scoping, the name-only 409 and its message, adopting one's own name-only
lease, 400s, next agreeing with the claim across a matrix); MCP (bare-name
holder, the claim's worker_id, resume via worker_id in claim and next,
malformed refused, session-derived ids stable and distinct); CLI (the id
survives a re-register by the same process, a new process gets a new one,
lazy mint, PAD_WORKER_ID, SetWorkerID). The two CLI rules each kill a
mutant.
Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
40 lines
1.4 KiB
Go
40 lines
1.4 KiB
Go
package models
|
|
|
|
// A lease worker id (TASK-3613) is the opaque identity an agent session holds
|
|
// item leases under, separate from its display name: two agents both called
|
|
// "implementer" on one account get two ids, so they cannot share a lease. The
|
|
// client mints it ("wk_" + 128 random bits as 32 lowercase hex) and reuses it
|
|
// for the life of its session; the server only checks the shape and always
|
|
// matches it together with the authenticated account (lead ruling).
|
|
|
|
const workerIDPrefix = "wk_"
|
|
|
|
// workerIDHexLen is 128 bits in hex.
|
|
const workerIDHexLen = 32
|
|
|
|
// ValidWorkerID reports whether s is a well-formed worker id. The shape is
|
|
// strict (lowercase hex only), which is also what lets the column skip the
|
|
// NUL triggers: an accepted id cannot contain one.
|
|
func ValidWorkerID(s string) bool {
|
|
if len(s) != len(workerIDPrefix)+workerIDHexLen || s[:len(workerIDPrefix)] != workerIDPrefix {
|
|
return false
|
|
}
|
|
for _, c := range s[len(workerIDPrefix):] {
|
|
if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ShortWorkerID is the display form ("wk_3f2a"): enough to tell two holders
|
|
// apart in a conflict message or an item's detail, never the full id, which
|
|
// is the credential-like half of the lease identity (lead ruling: the full id
|
|
// never reaches the UI). An invalid id yields "".
|
|
func ShortWorkerID(s string) string {
|
|
if !ValidWorkerID(s) {
|
|
return ""
|
|
}
|
|
return s[:len(workerIDPrefix)+4]
|
|
}
|