-
-
Notifications
You must be signed in to change notification settings - Fork 3
FIX-FORWARD PR #284: register the archive source_uid migration, add fresh-install + upgrade + import tests #289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ### Fixed | ||
|
|
||
| - Revised PR #230: fixed upgrade path broken on every existing install by removing source/source_id from INDEX_SCHEMA and changing migration guard from has_column to index_exists; also added _get_remote branch to a2a_import so remote-configured installs don't silently write history to local archive |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ### Fixed | ||
|
|
||
| - Registered the `archive_index_source_uid` migration (step 3) in `_ARCHIVE_INDEX` so the `source`/`source_id` columns and `idx_archive_source_uid` partial unique index are created on fresh installs and applied on upgrade; also extended `ArchiveStore.record()` to accept `source` and `source_id` so `a2a_import` can write tagged rows |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -105,10 +105,17 @@ | |||||
| ``POST /a2a/send`` ``{"from", "body", "thread"?, "reply_to"?, "refs"?, "blocks"?}`` -> send receipt | ||||||
| ``refs``: optional list (<=8) of ``{"kind": doc|report|spec|log, "title", "uri", "sha256"?, "doc_id"?, "version"?, "for"?, "summary"?}`` | ||||||
| ``blocks``: optional list of arbitrary objects (no schema validation); when present, ``body`` must be non-empty | ||||||
| <<<<<<< HEAD | ||||||
| ``GET /a2a/messages`` ``?thread=&since=&limit=&fields=&format=`` -> ``{"messages": [...]}`` (``fields=id,sender,body`` projects keys; ``format=ndjson`` emits one message per line; ``since`` is an epoch timestamp in seconds, not a message id; values below 1e9 return 400) | ||||||
| ``GET /a2a/stream`` ``?thread=&since=`` -> SSE stream (text/event-stream); ``since`` is an epoch timestamp in seconds, not a message id; values below 1e9 return 400 | ||||||
| ``GET /a2a/threads`` ``?principal=`` -> ``{"threads": [...]}`` thread list for the principal, ordered by latest activity desc; each entry has ``thread``, ``kind``, ``participants``, ``last_message: {id, ts, from, body_preview}`` (see notes below) | ||||||
| ``GET /a2a/threads/{thread}/messages`` ``?before=&after=&limit=`` -> ``{"thread", "messages": [...]}`` cursor-paginated message envelope, oldest-first | ||||||
| ======= | ||||||
| ``POST /a2a/import`` (admin) ``{"source": str, "defer_index"?: bool, "messages": [{"from": str, "thread": str, "body": str, "ts": float, "source_id": str, "reply_to_source_id"?: str|null, "blocks"?: [...], "refs"?: [...]}]}`` -> ``{"imported": n, "skipped": n, "first_id": int|null, "last_id": int|null}`` | ||||||
| Batch-import historical chat history onto the bus (taOSmd #211 Q3a). Idempotent on ``(source, source_id)``: re-runs skip existing rows. ``ts`` is preserved as the archive timestamp, not import time. ``reply_to_source_id`` resolves to the imported archive id of the referenced message (same source); unresolvable -> 400, no write. ``defer_index=true`` archives only and defers vector embedding; run ``taosmd reindex --agent <from>`` afterwards to back-fill vectors from the archive. | ||||||
| ``GET /a2a/messages`` ``?thread=&since=&limit=&fields=&format=`` -> ``{"messages": [...]}`` (``fields=id,sender,body`` projects keys; ``format=ndjson`` emits one message per line) | ||||||
| ``GET /a2a/stream`` ``?thread=&since=`` -> SSE stream (text/event-stream) | ||||||
| >>>>>>> 04e6b07af77bb30d797e0867a2647196c18b7c28 | ||||||
|
Comment on lines
+108
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔴 Critical | ⚡ Quick win Unresolved merge conflict markers are committed in this file. Lines 108, 113, and 118 contain Both sides carry content that must survive. The HEAD side documents Resolve the conflict by keeping the union of both sides. 🐛 Proposed resolution-<<<<<<< HEAD
+``POST /a2a/import`` (admin) ``{"source": str, "defer_index"?: bool, "messages": [{"from": str, "thread": str, "body": str, "ts": float, "source_id": str, "reply_to_source_id"?: str|null, "blocks"?: [...], "refs"?: [...]}]}`` -> ``{"imported": n, "skipped": n, "first_id": int|null, "last_id": int|null}``
+ Batch-import historical chat history onto the bus (taOSmd `#211` Q3a). Idempotent on ``(source, source_id)``: re-runs skip existing rows. ``ts`` is preserved as the archive timestamp, not import time. ``reply_to_source_id`` resolves to the imported archive id of the referenced message (same source); unresolvable -> 400, no write. ``defer_index=true`` archives only and defers vector embedding; run ``taosmd reindex --agent <from>`` afterwards to back-fill vectors from the archive.
``GET /a2a/messages`` ``?thread=&since=&limit=&fields=&format=`` -> ``{"messages": [...]}`` (``fields=id,sender,body`` projects keys; ``format=ndjson`` emits one message per line; ``since`` is an epoch timestamp in seconds, not a message id; values below 1e9 return 400)
``GET /a2a/stream`` ``?thread=&since=`` -> SSE stream (text/event-stream); ``since`` is an epoch timestamp in seconds, not a message id; values below 1e9 return 400
``GET /a2a/threads`` ``?principal=`` -> ``{"threads": [...]}`` thread list for the principal, ordered by latest activity desc; each entry has ``thread``, ``kind``, ``participants``, ``last_message: {id, ts, from, body_preview}`` (see notes below)
``GET /a2a/threads/{thread}/messages`` ``?before=&after=&limit=`` -> ``{"thread", "messages": [...]}`` cursor-paginated message envelope, oldest-first
-=======
-``POST /a2a/import`` (admin) ``{"source": str, "defer_index"?: bool, "messages": [{"from": str, "thread": str, "body": str, "ts": float, "source_id": str, "reply_to_source_id"?: str|null, "blocks"?: [...], "refs"?: [...]}]}`` -> ``{"imported": n, "skipped": n, "first_id": int|null, "last_id": int|null}``
- Batch-import historical chat history onto the bus (taOSmd `#211` Q3a). Idempotent on ``(source, source_id)``: re-runs skip existing rows. ``ts`` is preserved as the archive timestamp, not import time. ``reply_to_source_id`` resolves to the imported archive id of the referenced message (same source); unresolvable -> 400, no write. ``defer_index=true`` archives only and defers vector embedding; run ``taosmd reindex --agent <from>`` afterwards to back-fill vectors from the archive.
-``GET /a2a/messages`` ``?thread=&since=&limit=&fields=&format=`` -> ``{"messages": [...]}`` (``fields=id,sender,body`` projects keys; ``format=ndjson`` emits one message per line)
-``GET /a2a/stream`` ``?thread=&since=`` -> SSE stream (text/event-stream)
->>>>>>> 04e6b07af77bb30d797e0867a2647196c18b7c28Run the following script to confirm no other conflict markers remain and to check whether these lines sit inside the module docstring or in executable code: #!/bin/bash
# Description: Find committed merge-conflict markers across the repository.
rg -nP '^(<{7}|={7}|>{7})(\s|$)' --type=py
# Description: Confirm every Python file still parses.
fd -e py --exec python -m py_compile {}🤖 Prompt for AI Agents |
||||||
| ``GET /a2a/channels`` -> ``{"channels": [...]}`` | ||||||
| ``GET /a2a/members`` ``?channel=<name>`` -> ``{"members": [...]}`` | ||||||
| ``POST /tasks`` ``{"title", "body"?, "project"?, "assignee"?, "priority"?, "depends_on"?: [...], "created_by"}`` -> task object | ||||||
|
|
@@ -830,6 +837,7 @@ def _is_admin_route(method: str, path: str) -> bool: | |||||
| "/a2a/admin/delete-channel", | ||||||
| "/a2a/admin/rename-channel", | ||||||
| "/a2a/admin/supersede-message", | ||||||
| "/a2a/import", | ||||||
| ) | ||||||
|
|
||||||
| def _check_admin_token(self) -> bool: | ||||||
|
|
@@ -1029,6 +1037,8 @@ def _dispatch(self, method: str) -> None: | |||||
| self._handle_pending_resolve() | ||||||
| elif method == "POST" and path == "/a2a/send": | ||||||
| self._handle_a2a_send() | ||||||
| elif method == "POST" and path == "/a2a/import": | ||||||
| self._handle_a2a_import() | ||||||
| elif method == "GET" and path == "/a2a/channels": | ||||||
| self._handle_a2a_channels() | ||||||
| elif method == "GET" and path == "/a2a/members": | ||||||
|
|
@@ -1608,6 +1618,26 @@ def _handle_a2a_send(self) -> None: | |||||
| ) | ||||||
| self._send_json(200, result) | ||||||
|
|
||||||
| def _handle_a2a_import(self) -> None: | ||||||
| if not self._check_admin_token(): | ||||||
| return | ||||||
| body = self._read_json_body() | ||||||
| source = body.get("source") | ||||||
| defer_index = body.get("defer_index", False) | ||||||
| messages = body.get("messages") | ||||||
| if not isinstance(source, str) or not source: | ||||||
| raise _BadRequest("'source' (non-empty string) is required") | ||||||
| if not isinstance(messages, list) or not messages: | ||||||
| raise _BadRequest("'messages' (non-empty list) is required") | ||||||
| if not isinstance(defer_index, bool): | ||||||
| raise _BadRequest("'defer_index' must be a boolean when provided") | ||||||
| result = runner.run( | ||||||
| service.a2a_import( | ||||||
| source, messages, defer_index=defer_index, data_dir=data_dir, | ||||||
| ) | ||||||
| ) | ||||||
| self._send_json(200, result) | ||||||
|
|
||||||
| def _handle_a2a_messages(self, qs: dict) -> None: | ||||||
| thread = (qs.get("thread") or [None])[0] | ||||||
| since_raw = (qs.get("since") or [None])[0] | ||||||
|
|
@@ -2232,7 +2262,7 @@ def serve(host: str = DEFAULT_HOST, port: int = DEFAULT_PORT, data_dir=None) -> | |||||
| print("Endpoints: GET /health, GET /version, POST /ingest, POST /ingest/batch, GET|POST /search, " | ||||||
| "GET /projects, GET /shelves, " | ||||||
| "GET /pending, POST /pending/resolve, " | ||||||
| "POST /a2a/send, GET /a2a/messages, GET /a2a/stream, " | ||||||
| "POST /a2a/send, POST /a2a/import, GET /a2a/messages, GET /a2a/stream, " | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Remove
Line 2262 prints the data-plane endpoints. Every other admin route appears only on the admin line at Line 2272. Line 2277 already lists 🐛 Proposed fix- "POST /a2a/send, POST /a2a/import, GET /a2a/messages, GET /a2a/stream, "
+ "POST /a2a/send, GET /a2a/messages, GET /a2a/stream, "📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| "GET /a2a/channels, GET /a2a/members, " | ||||||
| "POST /tasks, GET /tasks, GET /tasks/ready, GET /tasks/prime, " | ||||||
| "POST /tasks/{id}, POST /tasks/{id}/edges, POST /tasks/{id}/edges/remove, " | ||||||
|
|
@@ -2244,7 +2274,7 @@ def serve(host: str = DEFAULT_HOST, port: int = DEFAULT_PORT, data_dir=None) -> | |||||
| "POST /shelves, POST /shelves/{id}/archive, " | ||||||
| "POST /shelves/{id}/unarchive, " | ||||||
| "POST /a2a/admin/delete-channel, POST /a2a/admin/rename-channel, " | ||||||
| "POST /a2a/admin/supersede-message") | ||||||
| "POST /a2a/admin/supersede-message, POST /a2a/import") | ||||||
| try: | ||||||
| httpd.serve_forever() | ||||||
| except KeyboardInterrupt: | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -228,6 +228,24 @@ async def a2a_send( | |
| payload["blocks"] = blocks | ||
| return await self._run("POST", "/a2a/send", payload) | ||
|
|
||
| async def a2a_import( | ||
| self, | ||
| source: str, | ||
| messages: list[dict], | ||
| *, | ||
| defer_index: bool = False, | ||
| **_opts, | ||
| ) -> dict: | ||
| """POST /a2a/import: admin batch-import historical messages onto the remote bus. | ||
|
|
||
| Returns ``{"imported", "skipped", "first_id", "last_id"}`` (taOSmd #211 Q3a). | ||
| Uses the client's bearer token, which must be an admin token. | ||
| """ | ||
| payload: dict = {"source": source, "messages": messages} | ||
| if defer_index: | ||
| payload["defer_index"] = True | ||
| return await self._run("POST", "/a2a/import", payload) | ||
|
|
||
|
Comment on lines
+231
to
+248
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win The 30 second default timeout is too short for a batch import.
A historical import posts an entire message batch in one request. The server writes every row and, unless On timeout Re-running the import is safe because it is idempotent on Accept a per-call timeout override for this method so callers can scale it with the batch size. ♻️ Proposed change async def a2a_import(
self,
source: str,
messages: list[dict],
*,
defer_index: bool = False,
+ timeout: int | None = None,
**_opts,
) -> dict:
"""POST /a2a/import: admin batch-import historical messages onto the remote bus.
Returns ``{"imported", "skipped", "first_id", "last_id"}`` (taOSmd `#211` Q3a).
Uses the client's bearer token, which must be an admin token.
+
+ ``timeout`` overrides the client default for this call. A large batch
+ needs a longer timeout, because the server writes and embeds every
+ message before it responds.
"""
payload: dict = {"source": source, "messages": messages}
if defer_index:
payload["defer_index"] = True
- return await self._run("POST", "/a2a/import", payload)
+ prev = self._timeout
+ if timeout is not None:
+ self._timeout = timeout
+ try:
+ return await self._run("POST", "/a2a/import", payload)
+ finally:
+ self._timeout = prevMutating 🤖 Prompt for AI Agents |
||
| async def a2a_feed( | ||
| self, | ||
| *, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CRITICAL: Unresolved merge conflict markers in source file
The file contains active merge conflict markers (
<<<<<<< HEAD,=======,>>>>>>> 04e6b07af77bb30d797e0867a2647196c18b7c28) starting at line 108. These must be resolved before merge — they will cause syntax errors or incorrect runtime behavior.Reply with
@kilocode-bot fix itto have Kilo Code address this issue.