{"owner":"taubyte","repo":"tau","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md"],"skills":{"AGENTS.md":"# AGENTS.md\n\nWorking notes for anyone — human or agent — changing this codebase. Rules here exist\nbecause getting them wrong produced a real bug, not because they sound tidy.\n\n## Designing around kvdb\n\n`core/kvdb` is a **CRDT key-value store** (go-ds-crdt), not a database. It replicates\nbetween nodes and merges concurrent writes with **last-write-wins per key**. There are no\ntransactions, no compare-and-swap, and no cross-key atomicity. Design for that or lose\ndata.\n\n### 1. One key per entry. Never a contended key.\n\nThe single most important rule. If two nodes can write the same key with different\ncontent, one write is silently discarded.\n\n```\nBAD   /lookup/org/{provider}/{owner}          → account_id\nGOOD  /lookup/org/{provider}/{owner}/{account_id} → linked-at\n```\n\nIn the bad layout two nodes claiming the same namespace both write one key and LWW picks a\nwinner — the loser vanishes with no error. In the good layout they write **different**\nkeys, so nothing is lost.\n\nSame rule kills read-modify-write on a collection. Never store a CBOR slice or map that\ncallers append to: reader A and reader B both read `[x]`, write `[x,y]` and `[x,z]`, and\none entry disappears. Split the collection into one key per element.\n\n`services/accounts/paths.go` states this convention and the layouts follow it — lookup\nindexes, passkey sub-collections, all one key per entry.\n\n### 2. Put the discriminator in the key path.\n\nIf an entry belongs to a `(provider, owner, account)` tuple, all three go in the path. Any\npart you leave out of the key is a part two writers can collide on.\n\nCorollary: keys are also your index. `List(ctx, prefix)` is the only query mechanism, so\nlay paths out so the prefix scans you need are cheap and the ones you don't need are\nimpossible.\n\n### 3. Make conflict visible, then resolve it deterministically.\n\nRule 1 means a genuine conflict — two accounts legitimately claiming one namespace — shows\nup as two entries rather than one silently winning. That is the point. Do not try to\nprevent it with a lock you do not have; resolve it on read:\n\n- scan the prefix\n- order by a stable value carried **in the entry** (a timestamp), with a second key\n  (the id) breaking ties for a total order\n- take the first\n\nEvery node then computes the same answer from the same replicated state, with no\ncoordination and no dependence on write ordering or clock skew between nodes mattering to\ncorrectness. `services/accounts/account.go`'s `lookupIDBySlug` does exactly this.\n\n### 4. Read-then-write guards are UX, not correctness.\n\nChecking \"is this already claimed?\" before writing is worth doing — it gives a clear error\nin the overwhelmingly common uncontended case. It guarantees nothing under concurrency,\nbecause another node can write between your read and your write. Never let correctness\ndepend on one. Rule 3 is what makes the outcome safe.\n\n### 5. Deletes are writes too.\n\nA delete is LWW against a concurrent write to the same key. A delete racing a re-create can\nlose. If ordering matters, carry it in the value and resolve on read.\n\n### 6. Only subscribed instances replicate.\n\nA kvdb replicates to instances that are **open and subscribed**. A write acknowledged by a\nsingle node can die with that node if no other holder had the database open. Anything doing\nload/unload must keep claimants co-loaded during active writes and barrier on acknowledgement\nfrom more than one holder. See `pkg/kvdb` and the hoarder replication path.\n\n### 7. Byte order is your only sort order.\n\n`List` returns keys in byte order. To scan chronologically, zero-pad the numeric segment to\nfixed width so lexicographic order matches numeric order — `pkg/raft/storage.go:40` and\n`pkg/raft/queue.go:60` pad indices to 20 digits for this reason. Unpadded numbers sort\n`1, 10, 2`.\n\n### 8. Key naming: path segments, not compound words.\n\nUse `/lookup/account/slug/{slug}`, not `/lookup/account_slug/{slug}`. Segments are what the\nprefix scanner understands; an underscore is invisible to it and forecloses scanning the\nintermediate level later.\n\n### 9. Normalise before keying.\n\nAnything case-insensitive in the real world (email, provider namespaces) must be\ncanonicalised before it becomes part of a key, or `Acme` and `acme` become two entries for\none thing and every uniqueness property built on that key quietly fails. The store\nlowercases emails in several places; do the same for any new identifier.\n\n### Checklist\n\n- [ ] Can two nodes write this exact key with different content? If yes, redesign.\n- [ ] Am I appending to a stored collection? If yes, split it into keys.\n- [ ] Is every part of the entry's identity in the key path?\n- [ ] If a conflict happens anyway, does every node resolve it the same way?\n- [ ] Does correctness rest on a read-then-write guard? It must not.\n- [ ] Are numeric key segments zero-padded to fixed width?\n- [ ] Are case-insensitive identifiers normalised before keying?\n"},"files":{"AGENTS.md":"# AGENTS.md\n\nWorking notes for anyone — human or agent — changing this codebase. Rules here exist\nbecause getting them wrong produced a real bug, not because they sound tidy.\n\n## Designing around kvdb\n\n`core/kvdb` is a **CRDT key-value store** (go-ds-crdt), not a database. It replicates\nbetween nodes and merges concurrent writes with **last-write-wins per key**. There are no\ntransactions, no compare-and-swap, and no cross-key atomicity. Design for that or lose\ndata.\n\n### 1. One key per entry. Never a contended key.\n\nThe single most important rule. If two nodes can write the same key with different\ncontent, one write is silently discarded.\n\n```\nBAD   /lookup/org/{provider}/{owner}          → account_id\nGOOD  /lookup/org/{provider}/{owner}/{account_id} → linked-at\n```\n\nIn the bad layout two nodes claiming the same namespace both write one key and LWW picks a\nwinner — the loser vanishes with no error. In the good layout they write **different**\nkeys, so nothing is lost.\n\nSame rule kills read-modify-write on a collection. Never store a CBOR slice or map that\ncallers append to: reader A and reader B both read `[x]`, write `[x,y]` and `[x,z]`, and\none entry disappears. Split the collection into one key per element.\n\n`services/accounts/paths.go` states this convention and the layouts follow it — lookup\nindexes, passkey sub-collections, all one key per entry.\n\n### 2. Put the discriminator in the key path.\n\nIf an entry belongs to a `(provider, owner, account)` tuple, all three go in the path. Any\npart you leave out of the key is a part two writers can collide on.\n\nCorollary: keys are also your index. `List(ctx, prefix)` is the only query mechanism, so\nlay paths out so the prefix scans you need are cheap and the ones you don't need are\nimpossible.\n\n### 3. Make conflict visible, then resolve it deterministically.\n\nRule 1 means a genuine conflict — two accounts legitimately claiming one namespace — shows\nup as two entries rather than one silently winning. That is the point. Do not try to\nprevent it with a lock you do not have; resolve it on read:\n\n- scan the prefix\n- order by a stable value carried **in the entry** (a timestamp), with a second key\n  (the id) breaking ties for a total order\n- take the first\n\nEvery node then computes the same answer from the same replicated state, with no\ncoordination and no dependence on write ordering or clock skew between nodes mattering to\ncorrectness. `services/accounts/account.go`'s `lookupIDBySlug` does exactly this.\n\n### 4. Read-then-write guards are UX, not correctness.\n\nChecking \"is this already claimed?\" before writing is worth doing — it gives a clear error\nin the overwhelmingly common uncontended case. It guarantees nothing under concurrency,\nbecause another node can write between your read and your write. Never let correctness\ndepend on one. Rule 3 is what makes the outcome safe.\n\n### 5. Deletes are writes too.\n\nA delete is LWW against a concurrent write to the same key. A delete racing a re-create can\nlose. If ordering matters, carry it in the value and resolve on read.\n\n### 6. Only subscribed instances replicate.\n\nA kvdb replicates to instances that are **open and subscribed**. A write acknowledged by a\nsingle node can die with that node if no other holder had the database open. Anything doing\nload/unload must keep claimants co-loaded during active writes and barrier on acknowledgement\nfrom more than one holder. See `pkg/kvdb` and the hoarder replication path.\n\n### 7. Byte order is your only sort order.\n\n`List` returns keys in byte order. To scan chronologically, zero-pad the numeric segment to\nfixed width so lexicographic order matches numeric order — `pkg/raft/storage.go:40` and\n`pkg/raft/queue.go:60` pad indices to 20 digits for this reason. Unpadded numbers sort\n`1, 10, 2`.\n\n### 8. Key naming: path segments, not compound words.\n\nUse `/lookup/account/slug/{slug}`, not `/lookup/account_slug/{slug}`. Segments are what the\nprefix scanner understands; an underscore is invisible to it and forecloses scanning the\nintermediate level later.\n\n### 9. Normalise before keying.\n\nAnything case-insensitive in the real world (email, provider namespaces) must be\ncanonicalised before it becomes part of a key, or `Acme` and `acme` become two entries for\none thing and every uniqueness property built on that key quietly fails. The store\nlowercases emails in several places; do the same for any new identifier.\n\n### Checklist\n\n- [ ] Can two nodes write this exact key with different content? If yes, redesign.\n- [ ] Am I appending to a stored collection? If yes, split it into keys.\n- [ ] Is every part of the entry's identity in the key path?\n- [ ] If a conflict happens anyway, does every node resolve it the same way?\n- [ ] Does correctness rest on a read-then-write guard? It must not.\n- [ ] Are numeric key segments zero-padded to fixed width?\n- [ ] Are case-insensitive identifiers normalised before keying?\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# AGENTS.md\n\nWorking notes for anyone — human or agent — changing this codebase. Rules here exist\nbecause getting them wrong produced a real bug, not because they sound tidy.\n\n## Designing around kvdb\n\n`core/kvdb` is a **CRDT key-value store** (go-ds-crdt), not a database. It replicates\nbetween nodes and merges concurrent writes with **last-write-wins per key**. There are no\ntransactions, no compare-and-swap, and no cross-key atomicity. Design for that or lose\ndata.\n\n### 1. One key per entry. Never a contended key.\n\nThe single most important rule. If two nodes can write the same key with different\ncontent, one write is silently discarded.\n\n```\nBAD   /lookup/org/{provider}/{owner}          → account_id\nGOOD  /lookup/org/{provider}/{owner}/{account_id} → linked-at\n```\n\nIn the bad layout two nodes claiming the same namespace both write one key and LWW picks a\nwinner — the loser vanishes with no error. In the good layout they write **different**\nkeys, so nothing is lost.\n\nSame rule kills read-modify-write on a collection. Never store a CBOR slice or map that\ncallers append to: reader A and reader B both read `[x]`, write `[x,y]` and `[x,z]`, and\none entry disappears. Split the collection into one key per element.\n\n`services/accounts/paths.go` states this convention and the layouts follow it — lookup\nindexes, passkey sub-collections, all one key per entry.\n\n### 2. Put the discriminator in the key path.\n\nIf an entry belongs to a `(provider, owner, account)` tuple, all three go in the path. Any\npart you leave out of the key is a part two writers can collide on.\n\nCorollary: keys are also your index. `List(ctx, prefix)` is the only query mechanism, so\nlay paths out so the prefix scans you need are cheap and the ones you don't need are\nimpossible.\n\n### 3. Make conflict visible, then resolve it deterministically.\n\nRule 1 means a genuine conflict — two accounts legitimately claiming one namespace — shows\nup as two entries rather than one silently winning. That is the point. Do not try to\nprevent it with a lock you do not have; resolve it on read:\n\n- scan the prefix\n- order by a stable value carried **in the entry** (a timestamp), with a second key\n  (the id) breaking ties for a total order\n- take the first\n\nEvery node then computes the same answer from the same replicated state, with no\ncoordination and no dependence on write ordering or clock skew between nodes mattering to\ncorrectness. `services/accounts/account.go`'s `lookupIDBySlug` does exactly this.\n\n### 4. Read-then-write guards are UX, not correctness.\n\nChecking \"is this already claimed?\" before writing is worth doing — it gives a clear error\nin the overwhelmingly common uncontended case. It guarantees nothing under concurrency,\nbecause another node can write between your read and your write. Never let correctness\ndepend on one. Rule 3 is what makes the outcome safe.\n\n### 5. Deletes are writes too.\n\nA delete is LWW against a concurrent write to the same key. A delete racing a re-create can\nlose. If ordering matters, carry it in the value and resolve on read.\n\n### 6. Only subscribed instances replicate.\n\nA kvdb replicates to instances that are **open and subscribed**. A write acknowledged by a\nsingle node can die with that node if no other holder had the database open. Anything doing\nload/unload must keep claimants co-loaded during active writes and barrier on acknowledgement\nfrom more than one holder. See `pkg/kvdb` and the hoarder replication path.\n\n### 7. Byte order is your only sort order.\n\n`List` returns keys in byte order. To scan chronologically, zero-pad the numeric segment to\nfixed width so lexicographic order matches numeric order — `pkg/raft/storage.go:40` and\n`pkg/raft/queue.go:60` pad indices to 20 digits for this reason. Unpadded numbers sort\n`1, 10, 2`.\n\n### 8. Key naming: path segments, not compound words.\n\nUse `/lookup/account/slug/{slug}`, not `/lookup/account_slug/{slug}`. Segments are what the\nprefix scanner understands; an underscore is invisible to it and forecloses scanning the\nintermediate level later.\n\n### 9. Normalise before keying.\n\nAnything case-insensitive in the real world (email, provider namespaces) must be\ncanonicalised before it becomes part of a key, or `Acme` and `acme` become two entries for\none thing and every uniqueness property built on that key quietly fails. The store\nlowercases emails in several places; do the same for any new identifier.\n\n### Checklist\n\n- [ ] Can two nodes write this exact key with different content? If yes, redesign.\n- [ ] Am I appending to a stored collection? If yes, split it into keys.\n- [ ] Is every part of the entry's identity in the key path?\n- [ ] If a conflict happens anyway, does every node resolve it the same way?\n- [ ] Does correctness rest on a read-then-write guard? It must not.\n- [ ] Are numeric key segments zero-padded to fixed width?\n- [ ] Are case-insensitive identifiers normalised before keying?\n","category":"root","tokens":1228}]}