-
-
Notifications
You must be signed in to change notification settings - Fork 3
Revise PR #230: the new index breaks every existing install on upgrade #284
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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 | ||||||
|
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. CRITICAL: Unresolved merge conflict marker This merge conflict marker must be resolved before merge. It will cause a Python syntax error and prevent the module from being imported. Reply with |
||||||
| ``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 | ||||||
| ======= | ||||||
|
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. CRITICAL: Unresolved merge conflict marker This merge conflict marker must be resolved before merge. It will cause a Python syntax error and prevent the module from being imported. Reply with |
||||||
| ``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. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Find committed conflict markers and check that the module parses.
set -euo pipefail
echo "== conflict markers across the repository =="
rg -n '^(<{7}|={7}|>{7})' --type=py || echo "none found"
echo "== parse check =="
python - <<'PY'
import ast, pathlib
src = pathlib.Path("taosmd/http_server.py").read_text(encoding="utf-8")
try:
ast.parse(src)
print("taosmd/http_server.py parses; markers are inside a string literal")
except SyntaxError as exc:
print(f"SyntaxError at line {exc.lineno}: {exc.msg}")
PYRepository: jaylfc/taosmd Length of output: 207 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== target file lines 90-130 =="
nl -ba taosmd/http_server.py | sed -n '90,130p'
echo "== conflict-like markers in the target file =="
rg -n '<<<<<<<|=======|>>>>>>>' taosmd/http_server.py || true
echo "== parse check =="
python3 - <<'PY'
import ast
from pathlib import Path
path = Path("taosmd/http_server.py")
source = path.read_text(encoding="utf-8")
try:
ast.parse(source)
except SyntaxError as exc:
print(f"SyntaxError at line {exc.lineno}: {exc.msg}")
else:
print("taosmd/http_server.py parses")
PY
echo "== endpoint documentation entries =="
rg -n '/a2a/(import|messages|stream|threads)' taosmd/http_server.py || trueRepository: jaylfc/taosmd Length of output: 222 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== target file lines 90-130 =="
sed -n '90,130p' taosmd/http_server.py
echo "== conflict-like markers in the target file =="
grep -nE '<<<<<<<|=======|>>>>>>>' taosmd/http_server.py || true
echo "== parse check =="
python3 - <<'PY'
import ast
from pathlib import Path
path = Path("taosmd/http_server.py")
source = path.read_text(encoding="utf-8")
try:
ast.parse(source)
except SyntaxError as exc:
print(f"SyntaxError at line {exc.lineno}: {exc.msg}")
else:
print("taosmd/http_server.py parses")
PY
echo "== endpoint documentation entries =="
grep -nE '/a2a/(import|messages|stream|threads)' taosmd/http_server.py || trueRepository: jaylfc/taosmd Length of output: 8142 Remove the conflict markers and merge the endpoint documentation. The markers are inside the module docstring, so they do not prevent import. They leave incorrect and duplicate API documentation. Preserve the 🤖 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 The route is admin-only. Listing it on line 2265 tells an operator the route is reachable with a data-plane token. Keep the admin entry only. 🐛 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 |
|---|---|---|
|
|
@@ -199,6 +199,24 @@ def _archive_index_project(conn: sqlite3.Connection) -> None: | |
| add_column(conn, "archive_index", "project", "TEXT") | ||
|
|
||
|
|
||
| def _archive_index_source_uid(conn: sqlite3.Connection) -> None: | ||
| """Add source/source_id columns for idempotent A2A batch import (#211). | ||
|
|
||
| ``source`` and ``source_id`` tag each imported A2A message with the | ||
| external origin and its stable per-source id, so a re-import can skip | ||
| rows already present without relying on the JSON payload alone. The | ||
| partial unique index enforces (source, source_id) uniqueness at the | ||
| database level as a safety net against duplicate writes. | ||
| """ | ||
| add_column(conn, "archive_index", "source", "TEXT") | ||
| add_column(conn, "archive_index", "source_id", "TEXT") | ||
| conn.execute( | ||
| "CREATE UNIQUE INDEX IF NOT EXISTS idx_archive_source_uid " | ||
| "ON archive_index (source, source_id) " | ||
| "WHERE source IS NOT NULL AND source_id IS NOT NULL" | ||
| ) | ||
|
|
||
|
|
||
| _ARCHIVE_INDEX: tuple[Migration, ...] = ( | ||
|
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. CRITICAL: Migration The migration function at lines 202-217 adds Reply with |
||
| Migration( | ||
| 1, "archive_index_baseline", _archive_index_baseline, | ||
|
|
||
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:
record()method signature missingsourceandsource_idparametersThe docstring documents
sourceandsource_idas parameters for idempotent A2A batch import, but the method signature at line 182 only acceptsprojectandtimestamp. Thea2a_importfunction inservice.pypassessourceandsource_idas keyword arguments, which will raiseTypeErrorat runtime.Reply with
@kilocode-bot fix itto have Kilo Code address this issue.