[CON-902] Correct the Key-Value Store reference against the live product - #13
Open
0xsergen wants to merge 3 commits into
Open
[CON-902] Correct the Key-Value Store reference against the live product#130xsergen wants to merge 3 commits into
0xsergen wants to merge 3 commits into
Conversation
Audited kv-reference.md against qn 0.6.0, the kv/rest/v1 REST API, and
qnLib inside `qn stream test-filter`. 10 confirmed defects. Every claim
in the file was executed.
Silent failures. Each of these returns a 2xx and gives nothing usable:
- `GET /sets` destructured `{ keys }`. The envelope is
`{ code, msg, data, cursor }`. There is no `keys` field.
- `GET /sets/{key}` destructured `{ value }`. The value is at
`data.value`.
- `contains` destructured `{ contains }`. The API returns
`data.exists`. Every membership check reported "not a member",
including for real members. An allowlist built on this example fails
silently and permanently. This was the worst of the ten.
- `PATCH /lists/{key}` ignores snake_case fields and still returns
200 "List updated successfully". Nothing is written.
- qnLib is the mirror image: it needs snake_case, and camelCase is the
silent no-op. Documented the casing rule on both layers.
- Every qnLib write helper returns the string "OK", including on a
no-op. "OK" is truthy, so it is not a success signal. Documented.
Wrong semantics:
- "Create or overwrite a list" is false. `qnUpsertList` merges. An
agent resetting a watchlist accumulates instead. Documented
`remove_items`, which was also missing.
- "Ordered lists" is false. Lists sort lexicographically at both the
REST and qnLib layers.
Limits table: all three rows were wrong. Boundary-probed to the exact
value.
- Max key length: 255 characters, not 256 bytes.
- Max value size: 800,000 characters, not 64 KB. The old figure was
about 12x too small, so agents chunked data needlessly.
- Max list size: the 10,000-item cap does not exist. A list grew to
10,503 with no error. The real cap is 1,500 items per write, and it
is the one that returns 400.
Coverage:
- 21 qnLib helpers are exposed and 9 were documented. Added `qnGetList`,
`qnGetAllLists`, `qnContainsListItem`, the `...Value` aliases, and
`getAccountId` / `signPayload` / `validatePayload`. `qnGetList` is
the important one: the file gave no way to read a list from inside a
filter.
- Added the four missing CLI commands: `kv set bulk`, `kv list ls`,
`kv list remove-item`, `kv list update`.
Verification: 38/38 assertions pass (12 REST, 18 qnLib, 8 CLI). All
test resources used the `con901-` prefix and were deleted in session.
Set and list counts returned to 31 and 14.
Version bumps for the three plugin manifests land in the CON-905 PR,
the last of this set to merge.
CON-901 asks for a Test: line on every example. Added five, each
asserting the response envelope, which is what the three worst defects
in this file got wrong. Envelope shape is the right thing to assert
here: it is identical for every reader, while key counts are not.
Re-verified live in this session:
- GET /sets/{key}: data is { key, value }, no top-level value.
- POST /sets: HTTP 201 with { code: 200, msg: 'Key value stored',
data: null }. This confirms the envelope code is independent of the
HTTP status.
- GET /sets: top-level code, msg, data, cursor, and data is an array.
No keys field.
- GET /lists/{key}/contains/{item}: data is { exists: true } for a
member and { exists: false } for a non-member. No contains field.
- Limits, boundary-probed again to the exact value: key 255 gives 201
and 256 gives 400; value 800,000 gives 201 and 800,001 gives 400;
a 1,500-item write gives 200 and 1,501 gives 400 with
"total of addItems and removeItems is 1501, max allowed is 1500".
Also re-confirmed, unchanged: GET /lists returns data as an object
holding keys, unlike GET /sets; GET /lists/{key} returns data as
{ items }; a snake_case add_items returns 200 and writes nothing,
while addItems writes and merges rather than replaces; and a list
seeded zebra, mango, apple reads back apple, mango, zebra.
The qnLib and CLI examples carry no Test: line. Both operate on the
reader's own store, so the only assertable value is this account's
key count, which no other reader can compare against.
All probe resources used the con901-audit-kv-20260904 prefix and were
deleted in session. Sets and lists returned to 31 and 14, with zero
con901 leftovers.
Version bumps for the three plugin manifests land in the CON-905 PR,
the last of this set to merge.
Two gaps found on the 2026-09-04 re-verification pass. Both are the same
defect class as D1 through D3: a reader destructures a field that is not
there, and gets undefined instead of an error.
The REST envelope is documented above the CLI section, so a reader
reasonably carries it downward. It does not hold. With `-o json` the CLI
uses no single envelope, and two of the five reads put the payload at the
top level with no `data` wrapper at all:
qn kv set list {data: [{key, value}], cursor}
qn kv set get {value}
qn kv list ls {data: {keys: [...]}, cursor}
qn kv list get {data: {items: [...]}, cursor}
qn kv list contains {exists}
Note `keys` for sets and `items` for lists. Note also that `.data.exists`
on `qn kv list contains` is undefined, which is falsy, so a membership
check written that way always reports "not a member". That is exactly the
D3 failure, reached through the CLI instead of REST.
Second gap: every delete command needs `--yes` when no terminal is
attached. Without it the command exits non-zero with "operation requires
confirmation" and deletes nothing. An agent scripting its own cleanup
fails silently against that. Found by hitting it during this audit's own
resource cleanup.
Also recorded: `qn kv set get` on a missing key exits non-zero with
"Error: not found." It does not return an empty value.
Verified live against 31 sets and 14 lists. The probe key
con901-audit-kv-20260904-shape was created and deleted in the same
session. Final sweep: 31 sets, 14 lists, zero con901 leftovers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of the CON-901 reference audit. Covers CON-902:
kv-reference.md.Every claim in the file was executed against the live account. 12 defects found and fixed. Post-edit re-run: 38 of 38 passing (12 REST, 18
qnLib, 8 CLI).Method
kv/rest/v1, live HTTP withx-api-key.qnLibhelpers run insideqn stream test-filteronethereum-mainnetblock 17811625.qn kvCLI, against resources this audit created.The worst one first
The
containsexample destructured a field that does not exist.The real response is
{ code, msg, data: { exists: true|false } }.containsisundefined, which is falsy. So every membership check reported "not in the list", including for items that were in it, with a 200 and no error. An allowlist built on that example fails open or fails closed depending on which side of the branch the caller put the allow. Either way, silently, forever.What was wrong
GET /sets{ keys }{ code, msg, data: [{key, value}], cursor }. There is nokeysGET /sets/{key}{ value }data.valuecontains{ contains }{ data: { exists } }. See abovePATCH /lists/{key}addItems/removeItems.add_itemsreturns200 "List updated successfully"and writes nothingqnLibcasingqnUpsertList(k, {add_items})works;{addItems}returns"OK"and writes nothingqnLibwrite returns"OK", including on the no-op in 4 and 5. It is truthy, so anifguard passes on a write that did nothingqnUpsertListmerges.['a','b']thenadd_items: ['c']gives['a','b','c']. An agent resetting a watchlist accumulates insteadqnLibsurfaceqnGetListwas missing, so the file documented no way to read a list from inside a filterThe limits table was wrong in all three rows
400 key exceeds maximum length of 255 charactersaddItems+removeItemstogether. A list grew to 10,503 items with no errorThe 64 KB figure was roughly 12 times too small, so an agent chunked data it did not need to chunk. The 10,000 figure named a limit that does not exist, while hiding the per-request one that does, which is the one that actually returns 400.
Note the scope of the 1,500 cap. It is write-only.
qnContainsListItemswith 1,501 items returns 1,501 booleans.Two more defects, found on re-verification
Both are the same shape as D1 through D3: a reader destructures a field that is not there and gets
undefinedinstead of an error.D11. The CLI uses no single JSON envelope. The REST envelope is documented directly above the CLI section, so a reader carries it downward. It does not hold.
qn kv set list{ data: [{key, value}], cursor }qn kv set get{ value }qn kv list ls{ data: { keys: [...] }, cursor }qn kv list get{ data: { items: [...] }, cursor }qn kv list contains{ exists }Note
keysfor sets againstitemsfor lists. Note especially that.data.existsonqn kv list containsisundefined, which is falsy. That is exactly the D3 failure, reached through the CLI instead of REST.D12. Every
deletecommand needs--yeswhen no terminal is attached. Without it it exits non-zero withoperation requires confirmationand deletes nothing. This matters for agents specifically: one scripting its own cleanup leaves the resource in place. Found by hitting it during this audit's own cleanup.Also recorded:
qn kv set geton a missing key exits non-zero withError: not found.It does not return an empty value.Checked and found correct, so not changed:
qn kv list listis the canonical subcommand withlsas its alias.Test: lines
6 added, covering 6 of the 7 runnable blocks. They assert the three response envelopes, the three probed limits, and the five CLI shapes above.
The one block without a
Test:line is theqnLibexample. It runs inside a Streams filter, and it is asserted instreams-reference.mdinstead, in PR #14.Left unfixed
qnGetSetreturnsnullfor a missing key instead of throwing. Recorded as fact. Whether that is intended is not for this file to decide.Resources
All keys were named
con901-*and deleted in the same session. A final sweep ofGET /setsandGET /listsshows 31 sets, 14 lists, zerocon901leftovers. Nothing this audit did not create was written to.Worth filing upstream
PATCH /lists/{key}accepts unknown body fields and returns200 "List updated successfully"without writing. Unknown fields should 400.qnLibwrite helpers return"OK"on a no-op, for the same reason.qn kv list --helpcalls lists "ordered string collections". They are lexicographically sorted.qnLibtakes snake_case option keys while REST takes camelCase for the same operation.Version bump
None here. The build-web3 patch bump to 1.0.3 lands in the CON-905 PR, which is the last of the three to merge.
Related: CON-906 and CON-905 PRs cover the other four files from the same audit pass.