hor-bank-id · git:20260905.5a04969 · 2026-09-05 · sha256 aee0fd23e58f4e09
hor-bank-id git:20260905.5a04969A
Immutable. This exact content never changes and is served at /api/v1/blob/aee0fd23e58f4e09.
---
name: hor-bank-id
description: Allocate an exclusive, collision-free row-id prefix for a requester inside one backend repository, so two writers never pick the same id. Use it before writing an explicit id into a seeder or a test that creates its own rows.
---
# hor-bank-id
Hand this skill a **requester id** and it returns an **id prefix** you may use, exclusively, for every row you create in this repository — in any table, in any seeder or test — for as long as this project exists.
## Why
Several writers touch the same backend repository over time: a human team may split work by hand, and an agent building one feature after another writes into the same tables. When two of them pick the same explicit row `id`, whichever runs its seeder last silently overwrites or collides with the other's data. `hor-bank-id` gives each requester its own slice of the id space, once.
**Being serial does not remove the need.** A seeder written for feature A is still in the tree when feature B writes its own, and both are loaded together on the next database refresh. What has to be exclusive is the id space across time, not across concurrent processes.
## The id shape
Every explicit id is an 8-digit integer, split into two parts.
```
1 0 0 0 0 0 0 1
└──┬──┘ └────┬────┘
prefix free (yours alone)
(100-999) (00000-99999)
```
- **The first 3 digits (100-999, 900 values)** are the prefix this skill hands out. One requester, one prefix, forever
- **The last 5 digits (00000-99999)** are yours to use however you like, in whatever order is convenient
- **A prefix is not scoped to one table.** Once you hold `137`, you may use `13700001` in `users` and `13700001` in `reservations` — different tables have independent primary keys
900 prefixes comfortably exceeds the number of features any one project realistically produces. Overflow is not handled.
## The requester id
Whatever uniquely names the caller, chosen by the caller:
- a human working by hand picks their own name (`alice`), or a per-feature name for more than one slice
- **an orchestrator driving one feature at a time** passes the feature's `id`, once per feature, and hands the prefix it gets back to every agent working in that repository. **The agents themselves never call this skill** — several units of one feature run at once, and each asking for itself would queue them behind one another's `mkdir` for no gain. In Hora Kit that orchestrator is `/hora-build`'s own main session
The same requester id always gets back the same prefix. Asking twice, or retrying after a crash, is always safe.
## State
Both live directly under the backend repository's own root — never under the outer app repository's `.hora/`, which belongs to `/hora` and means something unrelated there.
```
<backend-repo>/.hora/id-bank.json the registry
<backend-repo>/.hora/id-bank.lock/ the lock (a directory — mkdir is atomic on POSIX)
```
`id-bank.json` is a flat object, requester id to prefix, as a string so a leading `1` is never mistaken for an octal digit:
```json
{
"sign-up": "100",
"verify-two-factor": "101",
"alice": "102"
}
```
## Allocating (the normal call)
1. Try `mkdir <backend-repo>/.hora/id-bank.lock`.
2. If it fails because the directory already exists, wait 1 second and retry. **Stop retrying after 5 attempts (5 seconds total)** and go to "A lock that will not clear" — do not remove the lock yourself here.
3. Once `mkdir` succeeds, you hold the lock. Read `id-bank.json` (treat a missing file as `{}`).
4. If the requester id is already a key, that value is the answer. Go to step 6.
5. Otherwise, pick the lowest integer in `100..999` not already used as a value, add `{ "<requester id>": "<that number>" }`, and write the whole file back (rewrite it whole — a partial write would corrupt the JSON for the next reader).
6. `rmdir <backend-repo>/.hora/id-bank.lock` to release the lock, then return the prefix.
**Never skip the lock**, even to only read the file — a reader running concurrently with a writer's rewrite can observe a half-written file.
## A lock that will not clear
Reaching the retry limit means another writer is either still working or died mid-update. From outside these look identical, so **never remove the lock yourself to force through.** Report the failure instead:
- **An orchestrator stops the whole session** and states plainly: "the lock `hor-bank-id` uses did not clear, so this session is ending. Starting it again will clear it automatically and continue" (see "Clearing a stale lock"). This should be rare — the lock is taken once per feature, by the main session, for the few seconds one allocation takes. In Hora Kit the command to start again is `/hora`
- **A human running this by hand** may wait and retry, or run the clearing step below themselves
## Clearing a stale lock
A lock still standing at the very start of a fresh run cannot belong to anything still alive — nothing in this project holds it across separate invocations. Clearing it is therefore safe at that moment, and only then.
```
rm -rf <backend-repo>/.hora/id-bank.lock
```
An orchestrator runs this, unconditionally, as its very first action against the backend row on any invocation; in Hora Kit that is `/hora-build`. A human recovering from the failure above runs the same command by hand.
## Using the prefix once you have it
Combine it with your own 5 digits when you write an explicit id — in a seeder, or inside a test that creates its own fixture. Do not derive ids from anything but your own prefix, and do not read or reason about another requester's rows.