Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f46ccbae1c | |||
| 772fad18b4 | |||
| 8199774405 | |||
| 66ba06875e |
@@ -119,10 +119,14 @@ interpreted.
|
|||||||
`describe_store` advertises ONLY what is implemented: `relational_edges_supported=False`,
|
`describe_store` advertises ONLY what is implemented: `relational_edges_supported=False`,
|
||||||
`atomic_supersede_supported=False`, `transaction_supported=False`,
|
`atomic_supersede_supported=False`, `transaction_supported=False`,
|
||||||
`optimistic_locking_supported=True`, `filterable_metadata_fields=[]`,
|
`optimistic_locking_supported=True`, `filterable_metadata_fields=[]`,
|
||||||
**`sortable_chunk_fields=[{"name": "updated_at"}]`** (the ONLY globally-sortable field;
|
**`sortable_chunk_fields=[{"name": "updated_at", "type": "timestamp"}]`** (the ONLY
|
||||||
gates `scan`'s sort at the bifrost dispatch `_validate_scan_sort` AND Worldtree's #349
|
globally-sortable field; gates `scan`'s sort at the bifrost dispatch `_validate_scan_sort`
|
||||||
person-prime Branch-A `"updated_at" in caps.sort_fields_supported` — advertising it is
|
AND Worldtree's #349 person-prime Branch-A `"updated_at" in caps.sort_fields_supported` —
|
||||||
what lights up turn-1 durable-fact injection).
|
advertising it is what lights up turn-1 durable-fact injection). Both `name` AND `type`
|
||||||
|
are REQUIRED by the bifrost `handshake_response` `SortableChunkField` schema
|
||||||
|
(`additionalProperties:false`) — omitting `type` fails wire-schema validation and breaks
|
||||||
|
the ENTIRE handshake (memory + affect bind), not just the sort; `type` is advisory-only
|
||||||
|
(the wire never interprets it).
|
||||||
(`transaction_supported` is the bifrost **wire-level** multi-op transaction
|
(`transaction_supported` is the bifrost **wire-level** multi-op transaction
|
||||||
capability — NOT our internal SQLite transactions, which we use for atomic
|
capability — NOT our internal SQLite transactions, which we use for atomic
|
||||||
batches.) The client gates the gated verbs off these.
|
batches.) The client gates the gated verbs off these.
|
||||||
@@ -145,6 +149,20 @@ interpreted.
|
|||||||
`updated_at desc`), matching bifrost's cross-pagination conformance negative. The sort
|
`updated_at desc`), matching bifrost's cross-pagination conformance negative. The sort
|
||||||
field is indexed (`json_extract(record_json, '$.updated_at')`) so the read stays within
|
field is indexed (`json_extract(record_json, '$.updated_at')`) so the read stays within
|
||||||
person-prime's 500 ms fail-open budget.
|
person-prime's 500 ms fail-open budget.
|
||||||
|
- **Cursor is v1-provisional (KNOWN DEVIATION — offset, not snapshot).** The cursor is a
|
||||||
|
bare integer offset into the re-derived global order. This is CORRECT and conformant for
|
||||||
|
the **single-page** person-prime call (`cursor=None`), which is the only shipped consumer.
|
||||||
|
It **diverges from bifrost's protocol snapshot-cursor contract on multi-page continuation**:
|
||||||
|
the dispatch engine (`bifrost.memory` scan branch) drops the `sort` arg on a cursor
|
||||||
|
continuation because "the cursor's snapshotted order is authoritative", and maps
|
||||||
|
`ScanCursorExpired → 410`. Our offset cursor (a) does NOT snapshot the order — a page taken
|
||||||
|
after a concurrent write can duplicate/drop rows relative to the first page (heid-bug-hunt
|
||||||
|
2026-07-15, all 3 arms), and (b) never raises `ScanCursorExpired`. The `global_before_paginate`
|
||||||
|
/ cursor test asserts **static-store** behavior only. The durable/conformant fix is to adopt
|
||||||
|
the reference `InMemoryMemoryStore`'s snapshot-cursor semantics (opaque token + frozen ordered
|
||||||
|
id-list + TTL + `ScanCursorExpired`); DEFERRED pending bifrost-dev's ruling on the conformance
|
||||||
|
gap (scan/cursor has NO conformance coverage today, so a non-snapshot cursor passes). Routed
|
||||||
|
to bifrost-dev 2026-07-15.
|
||||||
|
|
||||||
## Concurrency
|
## Concurrency
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "ratatoskr"
|
name = "ratatoskr"
|
||||||
version = "0.20.13"
|
version = "0.20.14"
|
||||||
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
|
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
@@ -132,7 +132,10 @@ def _validate_injection(record: dict) -> None:
|
|||||||
raise InvalidArguments("injection_source only valid for injected_context origin")
|
raise InvalidArguments("injection_source only valid for injected_context origin")
|
||||||
|
|
||||||
|
|
||||||
_SORTABLE_CHUNK_FIELDS: list[dict] = [{"name": "updated_at"}]
|
# bifrost handshake_response SortableChunkField requires BOTH name + type
|
||||||
|
# (additionalProperties:false); omitting `type` fails wire-schema validation and
|
||||||
|
# breaks the whole handshake. `type` is advisory-only (the wire never interprets it).
|
||||||
|
_SORTABLE_CHUNK_FIELDS: list[dict] = [{"name": "updated_at", "type": "timestamp"}]
|
||||||
_SORTABLE_FIELD_NAMES = frozenset(f["name"] for f in _SORTABLE_CHUNK_FIELDS)
|
_SORTABLE_FIELD_NAMES = frozenset(f["name"] for f in _SORTABLE_CHUNK_FIELDS)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,15 @@ def test_fresh_db_advertises_v1_caps_and_schema():
|
|||||||
assert caps["atomic_supersede_supported"] is False
|
assert caps["atomic_supersede_supported"] is False
|
||||||
assert caps["transaction_supported"] is False
|
assert caps["transaction_supported"] is False
|
||||||
assert caps["filterable_metadata_fields"] == []
|
assert caps["filterable_metadata_fields"] == []
|
||||||
|
# bifrost handshake_response SortableChunkField requires BOTH name + type
|
||||||
|
# (additionalProperties:false) — omitting `type` fails wire-schema validation and
|
||||||
|
# breaks the ENTIRE Bifrost bind (regression guard: the deploy-breaker of 2026-07-15).
|
||||||
|
scf = caps["sortable_chunk_fields"]
|
||||||
|
assert scf == [{"name": "updated_at", "type": "timestamp"}]
|
||||||
|
for entry in scf:
|
||||||
|
assert set(entry) == {"name", "type"} # required exactly, no extra keys
|
||||||
|
assert isinstance(entry["name"], str) and entry["name"]
|
||||||
|
assert isinstance(entry["type"], str) and entry["type"]
|
||||||
# tables + vec index queryable
|
# tables + vec index queryable
|
||||||
store._conn.execute("SELECT * FROM memory_chunks")
|
store._conn.execute("SELECT * FROM memory_chunks")
|
||||||
store._conn.execute("SELECT * FROM memory_idempotency")
|
store._conn.execute("SELECT * FROM memory_idempotency")
|
||||||
|
|||||||
Reference in New Issue
Block a user