feat(telos): integrate Telos SQLite for reads and promote#398
Conversation
Replace the Python subprocess read path for /api/todos with a native TypeScript TelosStore that opens smart_todo.db readonly via better-sqlite3. This fixes two bugs: 1. "Item not found" on Promote — the promote endpoint now falls back to TelosStore when an item isn't found in WorkloadStore, bridging Telos SHA256 IDs to the Task Board. 2. SUB-TASKS showing 0/9 — TelosStore fetches ALL children regardless of status before counting, ensuring completedChildCount is accurate. - New telos-store.ts: readonly reader with proper tree building - Updated GET /api/todos: direct SQLite read (no subprocess for reads) - Updated POST promote: Telos fallback with --set-goal bridge - 18 new tests covering tree building, prefix matching, profiles Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dimakis
left a comment
There was a problem hiding this comment.
Centaur Review
Found 8 issue(s) (4 warning).
server/app.ts
Solid TelosStore implementation with good test coverage of the store itself, but the HTTP layer changes bypass Zod validation (schema drift risk), the promote endpoint returns item: null for Telos items which may break frontend callers, and integration tests for the changed endpoints are missing.
- 🟡 regressions (L1700): The TelosStore path bypasses the
TodoListResponseZod validation that the old Python-subprocess path used.TelosItemincludes extra fields not in the Zod schema or frontend types:links(array ofTelosLink) andcontextHints.sessionIds. These extra fields are silently serialized to the client. While extra JSON properties are harmless today, skipping validation means the endpoint has no runtime guarantee the shape matches the contract. Consider either (a) passing the result throughTodoListResponse.parse()to strip/validate, or (b) omittinglinksfrom theTelosItemshape (or mappingTelosItem → TodoItembefore returning).[fixable] - 🟡 unsafe_assumptions (L1920):
execFileAsync('python3', [TODO_SCRIPT, '--set-goal', telosItem.id, task.id])invokes a--set-goalCLI flag ontodo_api.pythat doesn't exist in this repo and can't be verified. If the Python script doesn't support this flag yet, every promote-from-Telos call will silently fail (the.catchswallows the error with alog.warn). The fire-and-forget pattern is fine, but a comment noting the dependency or a check in the Python side would reduce surprise. - 🟡 regressions (L1930): When promoting a Telos item, the response is
{ task, item: null }. The old (workload) path returns{ task, item: updatedItem }with the updated item. Frontend callers of this endpoint that readitemfrom the response to update local state will getnullfor Telos promotions. Verify the frontend handlesitem: nullgracefully — if it doesn't, this is a runtime error.[fixable] - 🔵 unsafe_assumptions (L275):
TELOS_DBpath is hardcoded tojoin(BASE_REPO, 'command_center', 'data', 'smart_todo.db'). IfBASE_REPOis empty (env var unset), this resolves to a relative path likecommand_center/data/smart_todo.db. TheexistsSyncguard prevents a crash, but the path assumption couples this to the mgmt repo's internal directory layout. Consider making it configurable via.mitzo.jsonor env var.[fixable]
server/__tests__/telos-store.test.ts
Solid TelosStore implementation with good test coverage of the store itself, but the HTTP layer changes bypass Zod validation (schema drift risk), the promote endpoint returns item: null for Telos items which may break frontend callers, and integration tests for the changed endpoints are missing.
- 🟡 missing_tests: The TelosStore unit tests are thorough, but there are no integration tests for the changed HTTP endpoints: (1)
GET /api/todoswith TelosStore present vs absent, (2)GET /api/todos?refresh=truetriggering Python then reading TelosStore, (3)POST /api/workload/items/:id/promotefalling through from WorkloadStore to TelosStore. The existingtodos.test.tsandworkload-routes.test.tsfiles should be updated to cover the new TelosStore paths.[fixable]
server/telos-store.ts
Solid TelosStore implementation with good test coverage of the store itself, but the HTTP layer changes bypass Zod validation (schema drift risk), the promote endpoint returns item: null for Telos items which may break frontend callers, and integration tests for the changed endpoints are missing.
- 🔵 bugs (L123):
parseContextHintsnull path:return { ...EMPTY_HINTS, sessionIds: [] }— thesessionIds: []override is redundant sinceEMPTY_HINTSalready hassessionIds: []. The shallow spread also shares the same frozen array references for all other fields (repos, paths, etc.), though this is safe in practice since the arrays are empty and the returned object is a new reference. Minor nit.[fixable] - 🔵 style (L1): The file-level JSDoc block (11 lines) is longer than the project convention of 'default to writing no comments, one short line max'. The information about WAL concurrency and write delegation is useful but could be a single line.
[fixable] - 🔵 bugs (L357):
loadLinksForItemsqueriessqlite_masteron every call to check if thelinkstable exists. Since the DB is opened readonly and the schema won't change during the process lifetime, this check could be cached in the constructor (likehasGoalId) to avoid a query per request.[fixable]
| const items = telosStore.listItems(profile, ['active', 'acknowledged']); | ||
| const profiles = telosStore.listProfiles(); | ||
| res.json({ profiles, items }); | ||
| } catch (err: unknown) { |
There was a problem hiding this comment.
🟡 regressions: The TelosStore path bypasses the TodoListResponse Zod validation that the old Python-subprocess path used. TelosItem includes extra fields not in the Zod schema or frontend types: links (array of TelosLink) and contextHints.sessionIds. These extra fields are silently serialized to the client. While extra JSON properties are harmless today, skipping validation means the endpoint has no runtime guarantee the shape matches the contract. Consider either (a) passing the result through TodoListResponse.parse() to strip/validate, or (b) omitting links from the TelosItem shape (or mapping TelosItem → TodoItem before returning). [fixable]
| description: descParts.join('\n\n') || undefined, | ||
| annotations: item.sources.map((s) => `Source: [${s.sourceType}] ${s.title} — ${s.url}`), | ||
| annotations: telosItem.sources.map((s) => `Source: [${s.type}] ${s.title} — ${s.url}`), | ||
| }); |
There was a problem hiding this comment.
🟡 unsafe_assumptions: execFileAsync('python3', [TODO_SCRIPT, '--set-goal', telosItem.id, task.id]) invokes a --set-goal CLI flag on todo_api.py that doesn't exist in this repo and can't be verified. If the Python script doesn't support this flag yet, every promote-from-Telos call will silently fail (the .catch swallows the error with a log.warn). The fire-and-forget pattern is fine, but a comment noting the dependency or a check in the Python side would reduce surprise.
| log.warn('failed to set goal on telos item', { | ||
| itemId: telosItem.id, | ||
| error: err instanceof Error ? err.message : 'unknown', | ||
| }); |
There was a problem hiding this comment.
🟡 regressions: When promoting a Telos item, the response is { task, item: null }. The old (workload) path returns { task, item: updatedItem } with the updated item. Frontend callers of this endpoint that read item from the response to update local state will get null for Telos promotions. Verify the frontend handles item: null gracefully — if it doesn't, this is a runtime error. [fixable]
| export const workloadStore = new WorkloadStore(taskStore.getDatabase()); | ||
|
|
||
| // Telos: read-only access to the strategic task DB (smart_todo.db) | ||
| const TELOS_DB = join(BASE_REPO, 'command_center', 'data', 'smart_todo.db'); |
There was a problem hiding this comment.
🔵 unsafe_assumptions: TELOS_DB path is hardcoded to join(BASE_REPO, 'command_center', 'data', 'smart_todo.db'). If BASE_REPO is empty (env var unset), this resolves to a relative path like command_center/data/smart_todo.db. The existsSync guard prevents a crash, but the path assumption couples this to the mgmt repo's internal directory layout. Consider making it configurable via .mitzo.json or env var. [fixable]
| }) as TelosContextHints; | ||
|
|
||
| function parseContextHints(raw: string | null): TelosContextHints { | ||
| if (!raw) return { ...EMPTY_HINTS, sessionIds: [] }; |
There was a problem hiding this comment.
🔵 bugs: parseContextHints null path: return { ...EMPTY_HINTS, sessionIds: [] } — the sessionIds: [] override is redundant since EMPTY_HINTS already has sessionIds: []. The shallow spread also shares the same frozen array references for all other fields (repos, paths, etc.), though this is safe in practice since the arrays are empty and the returned object is a new reference. Minor nit. [fixable]
| @@ -0,0 +1,447 @@ | |||
| /** | |||
There was a problem hiding this comment.
🔵 style: The file-level JSDoc block (11 lines) is longer than the project convention of 'default to writing no comments, one short line max'. The information about WAL concurrency and write delegation is useful but could be a single line. [fixable]
| } | ||
|
|
||
| private loadLinksForItems(itemIds: string[]): Map<string, TelosLink[]> { | ||
| if (itemIds.length === 0) return new Map(); |
There was a problem hiding this comment.
🔵 bugs: loadLinksForItems queries sqlite_master on every call to check if the links table exists. Since the DB is opened readonly and the schema won't change during the process lifetime, this check could be cached in the constructor (like hasGoalId) to avoid a query per request. [fixable]
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Trim verbose JSDoc to single line - Remove redundant sessionIds spread in parseContextHints - Cache links table existence check in constructor - Return mapped item from promote endpoint instead of null - Add dependency comment for --set-goal CLI command Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dimakis
left a comment
There was a problem hiding this comment.
Centaur Review
Found 9 issue(s) (1 critical) (5 warning).
server/app.ts
Solid read-only store implementation with good test coverage of the store itself, but the /api/todos endpoint sends TelosItem objects to the frontend without schema validation (unlike the Python fallback path), and the refresh-without-profile behavior changed. The promote endpoint's fire-and-forget bridge to Python creates a consistency gap.
- 🔴 regressions: The
/api/todosTelosStore path returnsTelosItemobjects directly (which include alinksfield andsessionIdsincontextHints), bypassing theTodoListResponseZod schema validation that the Python fallback path uses. The frontendTodoItemtype (frontend/src/types/todo.ts) has nolinksfield — this won't break anything today since the frontend ignores unknown fields, but thesessionIdsfield inTelosContextHintsis not present in the frontend'sTodoContextHints, creating a silent type mismatch. More importantly, the TelosStore path sends unvalidated data to the client, diverging from the pattern used in the fallback path.[fixable] - 🟡 regressions: The
GET /api/todos?refresh=trueflow changed behavior: previously, a refresh without a profile (refresh=truebut noprofileparam) would fall into the--listbranch. Now, the refresh-then-read path requires BOTHrefreshANDprofileto be set (if (refresh && profile && existsSync(...))). A client calling?refresh=truewithout a profile will skip the Python refresh entirely and go straight to the TelosStore read, returning potentially stale data. The old code handledrefreshwithoutprofile(it would run--refreshwithout--profile).[fixable] - 🟡 unsafe_assumptions: The promote endpoint's Telos path uses fire-and-forget
execFileAsyncto setgoal_idvia Python (--set-goal). If this fails, the task is created intaskStorebut the Telos item'sgoal_idis never set — the frontend will show the item as still promotable. There's no reconciliation mechanism, and the warning log is the only signal. Theitem: nullresponse also means the frontend can't update the item'sgoalIdoptimistically.
server/telos-store.ts
Solid read-only store implementation with good test coverage of the store itself, but the /api/todos endpoint sends TelosItem objects to the frontend without schema validation (unlike the Python fallback path), and the refresh-without-profile behavior changed. The promote endpoint's fire-and-forget bridge to Python creates a consistency gap.
- 🟡 bugs: In
getItem(), when loading children, sources and links for child items are fetched separately (childSourceMap,childLinkMap) and then merged into the parent'ssourceMap/linkMap. However, if the parent item's ID also appears inchildIds(which can't happen since parent_id != id due to foreign key), there's no issue. But the bigger concern is thatfetchAllChildrenonly fetches one level deep. ThebuildTreemethod supports arbitrary nesting, butfetchAllChildrenonly queries direct children. If items nest deeper than parent→child (the comment says they don't, but the schema has no constraint preventing it), deeper descendants would be silently dropped.[fixable] - 🔵 style: The file has a 12-line module-level JSDoc comment block. Per project conventions: 'default to writing no comments' and 'never write multi-paragraph docstrings or multi-line comment blocks'. The
listItemsandgetItemmethods also have multi-line JSDoc blocks. These could be removed since the method names and signatures are self-explanatory.[fixable] - 🔵 style: The
EMPTY_HINTSfrozen object is spread with{ ...EMPTY_HINTS, sessionIds: [] }in two places inparseContextHints, butEMPTY_HINTSalready hassessionIds: []. The spread override is redundant.[fixable] - 🔵 bugs: In
loadLinksForItems(), thesqlite_mastercheck runs on every call, even when invoked multiple times within the samelistItems()call (once for root items, potentially again for children ingetItem). This could be cached as a class-level flag likehasGoalIdis.[fixable]
server/__tests__/telos-store.test.ts
Solid read-only store implementation with good test coverage of the store itself, but the /api/todos endpoint sends TelosItem objects to the frontend without schema validation (unlike the Python fallback path), and the refresh-without-profile behavior changed. The promote endpoint's fire-and-forget bridge to Python creates a consistency gap.
- 🟡 missing_tests: No tests for the
linkstable existence check inloadLinksForItems()— the method checkssqlite_masterfor thelinkstable to handle pre-migration DBs. This defensive code path (no links table) is untested. Also no test for thehasGoalIdmigration check (DB withoutgoal_idcolumn).[fixable] - 🟡 missing_tests: No integration tests for the modified
/api/todosendpoint or the/api/workload/items/:id/promoteTelos fallback path inapp.ts. The test file only covers theTelosStoreclass in isolation. The endpoint behavior changes (refresh flow, dual-source promote, fire-and-forget Python bridge) are untested.[fixable]
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dimakis
left a comment
There was a problem hiding this comment.
Centaur Review
LGTM — no issues found.
dimakis
left a comment
There was a problem hiding this comment.
Centaur Review
Found 7 issue(s) (3 warning).
server/telos-store.ts
Solid read-only store integration with good test coverage and proper readonly DB handling. Main concerns: shallow-freeze sharing mutable arrays in EMPTY_HINTS, missing WS broadcast for Telos promotes, and fire-and-forget goal-setting risking duplicate tasks on retry.
- 🟡 bugs (L113):
EMPTY_HINTSis shallowly frozen viaObject.freeze, but{ ...EMPTY_HINTS }creates a shallow copy whose array properties (repos, paths, etc.) are shared references to the same mutable array objects. If any consumer mutates e.g.hints.repos.push(...), it corrupts the template for all future fallback calls. Either deep-freeze the nested arrays or return fresh arrays inparseContextHints's fallback path (e.g.repos: [], paths: [], ...).[fixable] - 🔵 style (L258): The
getItemprefix match queryWHERE substr(id, 1, ?) = ?cannot use indexes and triggers a full table scan. An equivalentWHERE id LIKE ? || '%'would at least allow a prefix index scan on theidprimary key. Minor for the current data volume but worth noting.[fixable]
server/app.ts
Solid read-only store integration with good test coverage and proper readonly DB handling. Main concerns: shallow-freeze sharing mutable arrays in EMPTY_HINTS, missing WS broadcast for Telos promotes, and fire-and-forget goal-setting risking duplicate tasks on retry.
- 🟡 regressions (L1935): The Telos promote path no longer broadcasts
onWorkloadBroadcast(the WorkloadStore path still does at line 1897). Other connected clients relying onworkload_item_updatedWS events to update the todo list'sgoalIdbadge in real-time won't see the change until they refresh. ThemappedItemis already constructed — consider broadcasting it or documenting the omission as intentional.[fixable] - 🟡 regressions (L1929): The
mappedItemreturned for Telos promotes has only{id, title, status, goalId}, a much smaller shape than the fullTodoItem/WorkloadItemthe WorkloadStore path returns. While the current frontend only readsresult.task.idand navigates away, any future consumer expecting fields likeurgency,starred,contextHints,sources,ageDays, orprofileon the responseitemwill getundefined. Consider returning a fuller shape or documenting this as a minimal-response contract.[fixable] - 🔵 unsafe_assumptions (L1919): The
--set-goalPython call is fire-and-forget: if it fails, the TaskStore has a new task but the Telos DB still showsgoal_id = NULL. A subsequent promote attempt on the same item would create a duplicate task. The.catchlogs the error but the user sees success. Consider returning the Telos item's existinggoalIdin the response if it's already set, or checkingtelosItem.goalIdbefore creating a new task.[fixable]
server/__tests__/telos-store.test.ts
Solid read-only store integration with good test coverage and proper readonly DB handling. Main concerns: shallow-freeze sharing mutable arrays in EMPTY_HINTS, missing WS broadcast for Telos promotes, and fire-and-forget goal-setting risking duplicate tasks on retry.
- 🔵 missing_tests: No test covers the case where a child item matches the root status filter AND its parent also matches (both 'active'). This exercises a key correctness property of
buildTree— the child must appear nested under the parent, not as a duplicate root. Adding a test that inserts an active parent with an active child and assertsitems.length === 1withitems[0].children.length === 1would lock this down.[fixable] - 🔵 missing_tests: No test for
listItemswith an empty statuses array ([]). The current code skips the status WHERE clause when statuses is empty, returning all items regardless of status. If this is intentional, a test would document the behavior; if not, it's a latent bug.[fixable]
| }) as TelosContextHints; | ||
|
|
||
| function parseContextHints(raw: string | null): TelosContextHints { | ||
| if (!raw) return { ...EMPTY_HINTS }; |
There was a problem hiding this comment.
🟡 bugs: EMPTY_HINTS is shallowly frozen via Object.freeze, but { ...EMPTY_HINTS } creates a shallow copy whose array properties (repos, paths, etc.) are shared references to the same mutable array objects. If any consumer mutates e.g. hints.repos.push(...), it corrupts the template for all future fallback calls. Either deep-freeze the nested arrays or return fresh arrays in parseContextHints's fallback path (e.g. repos: [], paths: [], ...). [fixable]
| // Prefix match | ||
| const rows = db | ||
| .prepare('SELECT * FROM items WHERE substr(id, 1, ?) = ?') | ||
| .all(id.length, id) as ItemRow[]; |
There was a problem hiding this comment.
🔵 style: The getItem prefix match query WHERE substr(id, 1, ?) = ? cannot use indexes and triggers a full table scan. An equivalent WHERE id LIKE ? || '%' would at least allow a prefix index scan on the id primary key. Minor for the current data volume but worth noting. [fixable]
| // Return the Telos item mapped to workload shape so frontend can update state | ||
| const mappedItem = { | ||
| id: telosItem.id, | ||
| title: telosItem.summary, |
There was a problem hiding this comment.
🟡 regressions: The Telos promote path no longer broadcasts onWorkloadBroadcast (the WorkloadStore path still does at line 1897). Other connected clients relying on workload_item_updated WS events to update the todo list's goalId badge in real-time won't see the change until they refresh. The mappedItem is already constructed — consider broadcasting it or documenting the omission as intentional. [fixable]
| itemId: telosItem.id, | ||
| error: err instanceof Error ? err.message : 'unknown', | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🟡 regressions: The mappedItem returned for Telos promotes has only {id, title, status, goalId}, a much smaller shape than the full TodoItem/WorkloadItem the WorkloadStore path returns. While the current frontend only reads result.task.id and navigates away, any future consumer expecting fields like urgency, starred, contextHints, sources, ageDays, or profile on the response item will get undefined. Consider returning a fuller shape or documenting this as a minimal-response contract. [fixable]
| annotations: item.sources.map((s) => `Source: [${s.sourceType}] ${s.title} — ${s.url}`), | ||
| annotations: telosItem.sources.map((s) => `Source: [${s.type}] ${s.title} — ${s.url}`), | ||
| }); | ||
|
|
There was a problem hiding this comment.
🔵 unsafe_assumptions: The --set-goal Python call is fire-and-forget: if it fails, the TaskStore has a new task but the Telos DB still shows goal_id = NULL. A subsequent promote attempt on the same item would create a duplicate task. The .catch logs the error but the user sees success. Consider returning the Telos item's existing goalId in the response if it's already set, or checking telosItem.goalId before creating a new task. [fixable]
dimakis
left a comment
There was a problem hiding this comment.
Centaur Review
Found 5 issue(s) (2 warning).
server/__tests__/telos-store.test.ts
Solid integration that replaces Python subprocess reads with direct SQLite access. Main concerns are missing WS broadcast for Telos-promoted items and missing test coverage for schema evolution guards.
- 🟡 missing_tests: Schema evolution guards
hasGoalIdandhasLinksTable(telos-store.ts lines 176–185) are not tested. These paths defensively handle older DBs that lack thegoal_idcolumn orlinkstable. A test with a DB created without those schema elements would confirm the fallback behavior works.[fixable]
server/app.ts
Solid integration that replaces Python subprocess reads with direct SQLite access. Main concerns are missing WS broadcast for Telos-promoted items and missing test coverage for schema evolution guards.
- 🟡 regressions: The Telos promote path (starting around the
telosItemfallback) does not emitonWorkloadBroadcastafter promotion. The WorkloadStore path emits{ type: 'workload_item_updated', item: updatedItem }so other connected clients see the goalId update in real-time. Telos-promoted items won't get this broadcast, so other open tabs/devices won't reflect the promotion until the next full fetch.[fixable] - 🔵 regressions: The Python fallback path for
/api/todos(whentelosStoreis null) removed thelog.warn('todo API returned unexpected shape', ...)that previously fired on Zod validation failure. The ternaryparsed.success ? parsed.data : { profiles: [], items: [] }silently swallows bad data, reducing observability for debugging the Python script.[fixable] - 🔵 unsafe_assumptions: The
--set-goalPython call in the Telos promote path is fire-and-forget and depends on a CLI flag from an unmerged PR (mgmt PR #202, as noted in the comment). If the Python script doesn't support this flag, goal_id is never persisted in the Telos DB — the TaskStore task will exist but the Telos item won't link to it, so on next page load the item appears un-promoted. Consider logging atinfolevel (not justwarnon failure) to make this observable, or gating the call on a feature check.[fixable]
server/telos-store.ts
Solid integration that replaces Python subprocess reads with direct SQLite access. Main concerns are missing WS broadcast for Telos-promoted items and missing test coverage for schema evolution guards.
- 🔵 style: The
fetchAllChildrencomment says 'Single level — items in this repo don't nest deeper than parent→child'. If a child itself has children, they would be silently dropped. This is fine as a documented constraint, but consider adding a clarifying note thatchildCount/completedChildCountonly reflect direct children, or adding a recursive fetch if deeper nesting is possible in the future.
- Replace shallow-frozen EMPTY_HINTS with fresh-array factory function - Add duplicate promote guard (return existing goal if already promoted) - Return full TelosItem shape from promote endpoint - Add WS broadcast for Telos promote events - Add tests: active child nesting, empty statuses array behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dimakis
left a comment
There was a problem hiding this comment.
Centaur Review
Found 7 issue(s) (1 critical) (3 warning).
server/app.ts
Solid read-only SQLite integration with good defensive coding (schema checks, busy_timeout, graceful fallback), but the promote endpoint has a type-shape mismatch when broadcasting TelosItems as WorkloadItems and a race condition on the duplicate-prevention guard. Both are fixable without major rework.
- 🔴 bugs (L1946): The promote endpoint broadcasts
workload_item_updatedwith a TelosItem (hassummary,ageDays,links,children) but the frontendprotocol-parser.tscasts this asWorkloadItem(expectstitle,snippet,snoozedUntil,clusterId,createdAt,updatedAt). TheupdateWorkloadItem()function in@mitzo/clientmatches byitem.idand since the Telos item wouldn't exist in the workload slice, it's a no-op — but downstream consumers that readitem.titlefrom the broadcast will getundefinedinstead of the actual summary. Consider either (a) broadcasting a different event type for Telos promotes, or (b) mapping the TelosItem to a WorkloadItem shape before broadcasting.[fixable] - 🟡 bugs (L1907): The 'already promoted' guard (
if (telosItem.goalId)) reads from a readonly DB connection, but goal_id is written by a fire-and-forget Python subprocess (--set-goal). If two promote requests arrive before the first Python process commits, both will seegoalId === nulland create duplicate tasks. Consider using an in-memory Set or the taskStore itself (check for existing task with matching Telos item ID in annotations) to prevent duplicates.[fixable] - 🟡 regressions (L1699): The TelosStore path in
/api/todosskipsTodoListResponse.safeParse()validation that the Python fallback path performs (line 1688). TelosItem includes alinksfield not present in theTodoItemSchemaZod schema, andTelosContextHintshas asessionIdsfield absent fromTodoContextHintsSchema. While extra fields pass through JSON.stringify harmlessly, the inconsistency means any future strict-mode or.strict()addition to the schema would break this path silently. Consider runningTodoListResponse.safeParse()on the TelosStore response too, or updating the schema.[fixable] - 🟡 missing_tests (L1900): The promote endpoint's TelosStore fallback path (lines 1900–1947) has no integration test coverage. The existing
workload-routes.test.tstests only cover WorkloadStore promotes. Key scenarios missing: (1) promote a Telos item successfully, (2) promote an already-promoted Telos item returns 200 with existing task, (3) item not found in either store returns 404.[fixable] - 🔵 missing_tests (L1697): The
/api/todosTelosStore path (lines 1696–1703) is not covered bytodos.test.ts. That test file only exercises the Python fallback path. Adding a test that mocks TelosStore presence would verify the primary read path.[fixable] - 🔵 style (L1874): The WorkloadStore promote logic (lines 1874–1896) and TelosStore promote logic (lines 1912–1946) duplicate the description-building pattern (descParts + hintsWithValues). Consider extracting a shared
buildPromoteDescription(contextHints, description?)helper to reduce duplication.[fixable]
server/telos-store.ts
Solid read-only SQLite integration with good defensive coding (schema checks, busy_timeout, graceful fallback), but the promote endpoint has a type-shape mismatch when broadcasting TelosItems as WorkloadItems and a race condition on the duplicate-prevention guard. Both are fixable without major rework.
- 🔵 unsafe_assumptions (L316):
fetchAllChildrenonly fetches one level deep (items whoseparent_idis in the given set). The inline comment acknowledges this ('items in this repo don't nest deeper than parent→child'), but this is a data assumption not enforced by the schema. If a grandchild exists, it would be silently dropped. This is acceptable if the Telos schema guarantees max depth 2, but worth a note in case that invariant changes.
| res.status(201).json({ task, item: mappedItem }); | ||
| onTaskBroadcast?.({ type: 'task_state', tasks: taskStore.getTree() }); | ||
| onWorkloadBroadcast?.({ type: 'workload_item_updated', item: updatedItem }); | ||
| onWorkloadBroadcast?.({ type: 'workload_item_updated', item: mappedItem }); |
There was a problem hiding this comment.
🔴 bugs: The promote endpoint broadcasts workload_item_updated with a TelosItem (has summary, ageDays, links, children) but the frontend protocol-parser.ts casts this as WorkloadItem (expects title, snippet, snoozedUntil, clusterId, createdAt, updatedAt). The updateWorkloadItem() function in @mitzo/client matches by item.id and since the Telos item wouldn't exist in the workload slice, it's a no-op — but downstream consumers that read item.title from the broadcast will get undefined instead of the actual summary. Consider either (a) broadcasting a different event type for Telos promotes, or (b) mapping the TelosItem to a WorkloadItem shape before broadcasting. [fixable]
|
|
||
| // Build description from item context | ||
| // Guard: if already promoted, return existing goal link instead of creating duplicate | ||
| if (telosItem.goalId) { |
There was a problem hiding this comment.
🟡 bugs: The 'already promoted' guard (if (telosItem.goalId)) reads from a readonly DB connection, but goal_id is written by a fire-and-forget Python subprocess (--set-goal). If two promote requests arrive before the first Python process commits, both will see goalId === null and create duplicate tasks. Consider using an in-memory Set or the taskStore itself (check for existing task with matching Telos item ID in annotations) to prevent duplicates. [fixable]
| try { | ||
| const items = telosStore.listItems(profile, ['active', 'acknowledged']); | ||
| const profiles = telosStore.listProfiles(); | ||
| res.json({ profiles, items }); |
There was a problem hiding this comment.
🟡 regressions: The TelosStore path in /api/todos skips TodoListResponse.safeParse() validation that the Python fallback path performs (line 1688). TelosItem includes a links field not present in the TodoItemSchema Zod schema, and TelosContextHints has a sessionIds field absent from TodoContextHintsSchema. While extra fields pass through JSON.stringify harmlessly, the inconsistency means any future strict-mode or .strict() addition to the schema would break this path silently. Consider running TodoListResponse.safeParse() on the TelosStore response too, or updating the schema. [fixable]
| } | ||
|
|
||
| // Fallback: try TelosStore (SHA256 items from strategic task tracker) | ||
| const telosItem = telosStore?.getItem(req.params.id); |
There was a problem hiding this comment.
🟡 missing_tests: The promote endpoint's TelosStore fallback path (lines 1900–1947) has no integration test coverage. The existing workload-routes.test.ts tests only cover WorkloadStore promotes. Key scenarios missing: (1) promote a Telos item successfully, (2) promote an already-promoted Telos item returns 200 with existing task, (3) item not found in either store returns 404. [fixable]
| } | ||
|
|
||
| try { | ||
| const items = telosStore.listItems(profile, ['active', 'acknowledged']); |
There was a problem hiding this comment.
🔵 missing_tests: The /api/todos TelosStore path (lines 1696–1703) is not covered by todos.test.ts. That test file only exercises the Python fallback path. Adding a test that mocks TelosStore presence would verify the primary read path. [fixable]
| // Try WorkloadStore first (UUID items from work signals) | ||
| const workloadItem = workloadStore.get(req.params.id); | ||
|
|
||
| if (workloadItem) { |
There was a problem hiding this comment.
🔵 style: The WorkloadStore promote logic (lines 1874–1896) and TelosStore promote logic (lines 1912–1946) duplicate the description-building pattern (descParts + hintsWithValues). Consider extracting a shared buildPromoteDescription(contextHints, description?) helper to reduce duplication. [fixable]
| return [...profiles].sort(); | ||
| } | ||
|
|
||
| // --- Private helpers --- |
There was a problem hiding this comment.
🔵 unsafe_assumptions: fetchAllChildren only fetches one level deep (items whose parent_id is in the given set). The inline comment acknowledges this ('items in this repo don't nest deeper than parent→child'), but this is a data assumption not enforced by the schema. If a grandchild exists, it would be silently dropped. This is acceptable if the Telos schema guarantees max depth 2, but worth a note in case that invariant changes.
TelosItems have a different shape than WorkloadItems (summary vs title, etc.) — broadcasting them as workload_item_updated would cause type mismatches in frontend consumers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dimakis
left a comment
There was a problem hiding this comment.
Centaur Review
Found 6 issue(s) (3 warning).
server/app.ts
Well-structured readonly SQLite integration with good test coverage for the store itself. Main concerns: partial task stub in the already-promoted guard, missing schema-evolution test coverage, and duplicated promote logic. The workload broadcast shape mismatch appears already fixed in the latest branch commit.
- 🟡 bugs (L1946): Broadcasting
workload_item_updatedwith aTelosItem(hassummary,TelosSource.type) into the workload store that expectsWorkloadItem(hastitle,WorkloadSource.sourceType). The shape mismatch meansupdateWorkloadItemsilently no-ops (IDs won't match workload items), but the broadcast is misleading dead code. NOTE: commitedf7ba9on the branch already removes this line — confirm it's included in the PR diff.[fixable] - 🟡 bugs (L1909): When a Telos item is already promoted but the task was deleted from
TaskStore,existingTask ?? { id: telosItem.goalId }returns a minimal stub{ id }instead of a fullTaskobject. The frontend promote handler likely expects a complete task shape (title, status, etc.). Consider returning 404 or re-creating the task whentaskStore.get()returns undefined.[fixable] - 🔵 style (L1878): The description-building logic (descParts, hintsWithValues) is duplicated between the WorkloadStore path (lines 1875–1881) and the TelosStore path (lines 1913–1919). Extract a helper like
buildPromoteDescription(body, contextHints, taskHint)to reduce duplication.[fixable]
server/__tests__/telos-store.test.ts
Well-structured readonly SQLite integration with good test coverage for the store itself. Main concerns: partial task stub in the already-promoted guard, missing schema-evolution test coverage, and duplicated promote logic. The workload broadcast shape mismatch appears already fixed in the latest branch commit.
- 🟡 missing_tests: The
TelosStoreconstructor explicitly handles schema evolution (hasGoalId,hasLinksTableflags) but no tests exercise these paths. Add test cases with a DB that lacks thegoal_idcolumn and/or thelinkstable to verify graceful degradation.[fixable] - 🔵 missing_tests: No integration test for the Telos promote path in
/api/workload/items/:id/promote— specifically the idempotent guard (already-promoted returns 200), the--set-goalfire-and-forget bridging, and the fallback from WorkloadStore to TelosStore. The existingworkload-routes.test.tsonly tests the WorkloadStore path.[fixable]
server/telos-store.ts
Well-structured readonly SQLite integration with good test coverage for the store itself. Main concerns: partial task stub in the already-promoted guard, missing schema-evolution test coverage, and duplicated promote logic. The workload broadcast shape mismatch appears already fixed in the latest branch commit.
- 🔵 unsafe_assumptions (L259):
getItem('')would match ALL items: SQLite'ssubstr(id, 1, 0)returns'', and'' = ''is true for every row. If the prefix has multiple matches, it returns null (safe), but an empty string on a single-item DB would return that item. Consider guarding against empty/very short ID prefixes (e.g.,if (id.length < 4) return null).[fixable]
| res.status(201).json({ task, item: mappedItem }); | ||
| onTaskBroadcast?.({ type: 'task_state', tasks: taskStore.getTree() }); | ||
| onWorkloadBroadcast?.({ type: 'workload_item_updated', item: updatedItem }); | ||
| onWorkloadBroadcast?.({ type: 'workload_item_updated', item: mappedItem }); |
There was a problem hiding this comment.
🟡 bugs: Broadcasting workload_item_updated with a TelosItem (has summary, TelosSource.type) into the workload store that expects WorkloadItem (has title, WorkloadSource.sourceType). The shape mismatch means updateWorkloadItem silently no-ops (IDs won't match workload items), but the broadcast is misleading dead code. NOTE: commit edf7ba9 on the branch already removes this line — confirm it's included in the PR diff. [fixable]
| // Guard: if already promoted, return existing goal link instead of creating duplicate | ||
| if (telosItem.goalId) { | ||
| const existingTask = taskStore.get(telosItem.goalId); | ||
| res.status(200).json({ task: existingTask ?? { id: telosItem.goalId }, item: telosItem }); |
There was a problem hiding this comment.
🟡 bugs: When a Telos item is already promoted but the task was deleted from TaskStore, existingTask ?? { id: telosItem.goalId } returns a minimal stub { id } instead of a full Task object. The frontend promote handler likely expects a complete task shape (title, status, etc.). Consider returning 404 or re-creating the task when taskStore.get() returns undefined. [fixable]
| const descParts: string[] = []; | ||
| if (body.data.description) descParts.push(body.data.description); | ||
| if (workloadItem.contextHints.taskHint) descParts.push(workloadItem.contextHints.taskHint); | ||
| const hintsWithValues = Object.entries(workloadItem.contextHints) |
There was a problem hiding this comment.
🔵 style: The description-building logic (descParts, hintsWithValues) is duplicated between the WorkloadStore path (lines 1875–1881) and the TelosStore path (lines 1913–1919). Extract a helper like buildPromoteDescription(body, contextHints, taskHint) to reduce duplication. [fixable]
| if (!row) { | ||
| // Prefix match | ||
| const rows = db | ||
| .prepare('SELECT * FROM items WHERE substr(id, 1, ?) = ?') |
There was a problem hiding this comment.
🔵 unsafe_assumptions: getItem('') would match ALL items: SQLite's substr(id, 1, 0) returns '', and '' = '' is true for every row. If the prefix has multiple matches, it returns null (safe), but an empty string on a single-item DB would return that item. Consider guarding against empty/very short ID prefixes (e.g., if (id.length < 4) return null). [fixable]
dimakis
left a comment
There was a problem hiding this comment.
Centaur Review
Found 6 issue(s) (3 warning).
server/app.ts
Solid readonly store with good test coverage and defensive schema checks, but the Telos promote path has a race condition from fire-and-forget goal_id write-back and lacks integration tests.
- 🟡 bugs (L1907): Race condition: the idempotency guard (
if (telosItem.goalId)) relies on the fire-and-forget Python--set-goalcall completing before any retry. Since the TelosStore is readonly and the Python write-back is async, a fast double-promote creates duplicate tasks — both calls seegoalId: null, both create tasks, and goal_id is set to whichever Python call finishes last. Consider awaiting the Python call (like the refresh path does) or adding an in-memory promote lock keyed by item ID.[fixable] - 🟡 regressions (L1697): The
/api/todosendpoint now returnsTelosItem[]directly without Zod validation viaTodoListResponse. The previous code always validated throughTodoListResponse.safeParse().TelosItemincludes alinksfield andcontextHints.sessionIdsthat aren't in theTodoListResponseschema or the frontend'sTodoItemtype. The extra fields are harmless in JSON, but the endpoint now bypasses the schema contract entirely — any future TelosStore shape change will pass through to the frontend unvalidated.[fixable] - 🟡 missing_tests (L1899): The Telos promote path (lines 1899–1946) has no integration test coverage. The existing
workload-routes.test.tspromote tests only exercise the WorkloadStore path. Missing cases: (1) promote a Telos item when WorkloadStore has no match, (2) the already-promoted guard returning 200, (3) the fallback to 404 when both stores miss, (4) the fire-and-forget Python--set-goalcall.[fixable] - 🔵 bugs (L1909): When a Telos item is already promoted but the linked task has been deleted from
taskStore, this returns{ task: { id: telosItem.goalId }, item: telosItem }— a stub object with onlyidand no other task fields. Callers/frontend expecting a fullTaskshape (status, title, etc.) will get undefined fields. Consider returning 409 or re-creating the task instead of returning a partial object.[fixable] - 🔵 style (L1913): The description-building logic (descParts + hintsWithValues assembly) is duplicated verbatim between the WorkloadStore path (lines 1875–1881) and the TelosStore path (lines 1913–1919). Extract to a helper to reduce the duplication, especially since the filter conditions differ subtly (
sessionIdsis excluded only in the Telos path).[fixable]
server/__tests__/telos-store.test.ts
Solid readonly store with good test coverage and defensive schema checks, but the Telos promote path has a race condition from fire-and-forget goal_id write-back and lacks integration tests.
- 🔵 missing_tests: No test for
getItemwith an empty string input. SQLitesubstr(id, 1, 0) = ''matches all rows, but therows.length === 1guard should return null. Also no test forlistItemswhen a child's parent doesn't match the status filter (orphaned child becomes a root item).[fixable]
|
|
||
| // Build description from item context | ||
| // Guard: if already promoted, return existing goal link instead of creating duplicate | ||
| if (telosItem.goalId) { |
There was a problem hiding this comment.
🟡 bugs: Race condition: the idempotency guard (if (telosItem.goalId)) relies on the fire-and-forget Python --set-goal call completing before any retry. Since the TelosStore is readonly and the Python write-back is async, a fast double-promote creates duplicate tasks — both calls see goalId: null, both create tasks, and goal_id is set to whichever Python call finishes last. Consider awaiting the Python call (like the refresh path does) or adding an in-memory promote lock keyed by item ID. [fixable]
| } | ||
|
|
||
| try { | ||
| const items = telosStore.listItems(profile, ['active', 'acknowledged']); |
There was a problem hiding this comment.
🟡 regressions: The /api/todos endpoint now returns TelosItem[] directly without Zod validation via TodoListResponse. The previous code always validated through TodoListResponse.safeParse(). TelosItem includes a links field and contextHints.sessionIds that aren't in the TodoListResponse schema or the frontend's TodoItem type. The extra fields are harmless in JSON, but the endpoint now bypasses the schema contract entirely — any future TelosStore shape change will pass through to the frontend unvalidated. [fixable]
| return; | ||
| } | ||
|
|
||
| // Fallback: try TelosStore (SHA256 items from strategic task tracker) |
There was a problem hiding this comment.
🟡 missing_tests: The Telos promote path (lines 1899–1946) has no integration test coverage. The existing workload-routes.test.ts promote tests only exercise the WorkloadStore path. Missing cases: (1) promote a Telos item when WorkloadStore has no match, (2) the already-promoted guard returning 200, (3) the fallback to 404 when both stores miss, (4) the fire-and-forget Python --set-goal call. [fixable]
| // Guard: if already promoted, return existing goal link instead of creating duplicate | ||
| if (telosItem.goalId) { | ||
| const existingTask = taskStore.get(telosItem.goalId); | ||
| res.status(200).json({ task: existingTask ?? { id: telosItem.goalId }, item: telosItem }); |
There was a problem hiding this comment.
🔵 bugs: When a Telos item is already promoted but the linked task has been deleted from taskStore, this returns { task: { id: telosItem.goalId }, item: telosItem } — a stub object with only id and no other task fields. Callers/frontend expecting a full Task shape (status, title, etc.) will get undefined fields. Consider returning 409 or re-creating the task instead of returning a partial object. [fixable]
| return; | ||
| } | ||
|
|
||
| const descParts: string[] = []; |
There was a problem hiding this comment.
🔵 style: The description-building logic (descParts + hintsWithValues assembly) is duplicated verbatim between the WorkloadStore path (lines 1875–1881) and the TelosStore path (lines 1913–1919). Extract to a helper to reduce the duplication, especially since the filter conditions differ subtly (sessionIds is excluded only in the Telos path). [fixable]
dimakis
left a comment
There was a problem hiding this comment.
Centaur Review
Found 9 issue(s) (5 warning).
server/app.ts
Solid read-only store with good test coverage for the core class, but the route-level integration (dual-source promote, response validation bypass, fire-and-forget goal writes) has gaps in both testing and error handling that could cause subtle UX inconsistencies.
- 🟡 regressions (L1700): The TelosStore path in
/api/todosbypassesTodoListResponse.safeParse()validation that was applied to Python output. If TelosStore returns malformed data (e.g., null urgency, missing fields), it goes straight to the client unvalidated. Consider wrapping withTodoListResponse.safeParse({ profiles, items })for consistency, or at minimum document why validation is skipped.[fixable] - 🟡 regressions (L1658): The old
GET /api/todoshandler checkedexistsSync(TODO_SCRIPT)and returned empty early for all cases (list + refresh). The new code only checksTODO_SCRIPTexistence for the refresh path but not before calling it. Ifrefresh=trueandprofileis set butTODO_SCRIPTis missing, the code correctly guards withexistsSync(TODO_SCRIPT)in theifcondition, so this is fine. However, whentelosStoreis null and falling back to Python, the--listfallback also correctly guards. No regression here — disregard. - 🟡 bugs (L1905): In the Telos promote path, the
--set-goalPython call is fire-and-forget. If it fails (and it's logged), the response still returnsgoalId: task.idto the frontend. On the nextGET /api/todoscall, the item will come back from TelosStore withoutgoalId(since the DB write failed), causing the frontend to show the Promote button again. This is a known trade-off (documented as 'requires mgmt PR #202'), but the UX inconsistency could be confusing. - 🟡 bugs (L1893): The duplicate-promote guard (
if (telosItem.goalId)) returns the existing task viataskStore.get(telosItem.goalId). If the task was deleted from TaskStore butgoal_idstill exists in Telos DB,existingTaskwill be null, and the response becomes{ task: { id: telosItem.goalId }, item: telosItem }— a partial task object with onlyid. Callers expecting a full task shape will get unexpected results.[fixable] - 🔵 style (L1868): The promote endpoint now has two near-identical blocks for building
descPartsandhintsWithValues— one for WorkloadStore items, one for TelosStore items. The only differences are field names (titlevssummary,sourceTypevstype, extrasessionIdsfilter). Consider extracting a shared helper to reduce duplication.[fixable] - 🔵 regressions (L1898): The Telos promote path does not call
onWorkloadBroadcast(unlike the WorkloadStore path). This is intentional since it's not a workload item, but frontend components listening forworkload_item_updatedwon't get notified when a Telos item is promoted. If the frontend todo list needs to refresh after promote, it may need a separate broadcast or the client needs to re-fetch.[fixable]
server/__tests__/telos-store.test.ts
Solid read-only store with good test coverage for the core class, but the route-level integration (dual-source promote, response validation bypass, fire-and-forget goal writes) has gaps in both testing and error handling that could cause subtle UX inconsistencies.
- 🟡 missing_tests: No integration tests for the modified
/api/todosor/api/workload/items/:id/promoteendpoints. The unit tests for TelosStore are thorough, but the route handler changes (dual-source promote, TelosStore-first reads, refresh-then-read flow) are untested. Key missing scenario: promote endpoint falling through from WorkloadStore miss to TelosStore hit.[fixable] - 🔵 missing_tests: No test for
TelosStorebehavior whengoal_idcolumn is missing from the schema (thehasGoalIdfeature flag path). The constructor caches this check, but no test verifies thatgoalIdis null when the column doesn't exist.[fixable]
server/telos-store.ts
Solid read-only store with good test coverage for the core class, but the route-level integration (dual-source promote, response validation bypass, fire-and-forget goal writes) has gaps in both testing and error handling that could cause subtle UX inconsistencies.
- 🔵 unsafe_assumptions (L241): The prefix-match query
SELECT * FROM items WHERE substr(id, 1, ?) = ?does a full table scan — it can't use the primary key index. For a small Telos DB this is fine, but if the table grows, considerWHERE id >= ? AND id < ?(range scan on the index) orWHERE id LIKE ? || '%'with appropriate escaping.[fixable]
| const items = telosStore.listItems(profile, ['active', 'acknowledged']); | ||
| const profiles = telosStore.listProfiles(); | ||
| res.json({ profiles, items }); | ||
| } catch (err: unknown) { |
There was a problem hiding this comment.
🟡 regressions: The TelosStore path in /api/todos bypasses TodoListResponse.safeParse() validation that was applied to Python output. If TelosStore returns malformed data (e.g., null urgency, missing fields), it goes straight to the client unvalidated. Consider wrapping with TodoListResponse.safeParse({ profiles, items }) for consistency, or at minimum document why validation is skipped. [fixable]
| @@ -1649,34 +1658,47 @@ app.get('/api/todos', async (req, res) => { | |||
| const profile = req.query.profile as string | undefined; | |||
There was a problem hiding this comment.
🟡 regressions: The old GET /api/todos handler checked existsSync(TODO_SCRIPT) and returned empty early for all cases (list + refresh). The new code only checks TODO_SCRIPT existence for the refresh path but not before calling it. If refresh=true and profile is set but TODO_SCRIPT is missing, the code correctly guards with existsSync(TODO_SCRIPT) in the if condition, so this is fine. However, when telosStore is null and falling back to Python, the --list fallback also correctly guards. No regression here — disregard.
| res.status(404).json({ error: 'Item not found' }); | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
🟡 bugs: In the Telos promote path, the --set-goal Python call is fire-and-forget. If it fails (and it's logged), the response still returns goalId: task.id to the frontend. On the next GET /api/todos call, the item will come back from TelosStore without goalId (since the DB write failed), causing the frontend to show the Promote button again. This is a known trade-off (documented as 'requires mgmt PR #202'), but the UX inconsistency could be confusing.
|
|
||
| workloadStore.setGoalId(workloadItem.id, task.id); | ||
| const updatedItem = workloadStore.get(workloadItem.id); | ||
| res.status(201).json({ task, item: updatedItem }); |
There was a problem hiding this comment.
🟡 bugs: The duplicate-promote guard (if (telosItem.goalId)) returns the existing task via taskStore.get(telosItem.goalId). If the task was deleted from TaskStore but goal_id still exists in Telos DB, existingTask will be null, and the response becomes { task: { id: telosItem.goalId }, item: telosItem } — a partial task object with only id. Callers expecting a full task shape will get unexpected results. [fixable]
| @@ -1846,35 +1868,81 @@ app.post('/api/workload/items/:id/promote', (req, res) => { | |||
| return; | |||
There was a problem hiding this comment.
🔵 style: The promote endpoint now has two near-identical blocks for building descParts and hintsWithValues — one for WorkloadStore items, one for TelosStore items. The only differences are field names (title vs summary, sourceType vs type, extra sessionIds filter). Consider extracting a shared helper to reduce duplication. [fixable]
| onWorkloadBroadcast?.({ type: 'workload_item_updated', item: updatedItem }); | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
🔵 regressions: The Telos promote path does not call onWorkloadBroadcast (unlike the WorkloadStore path). This is intentional since it's not a workload item, but frontend components listening for workload_item_updated won't get notified when a Telos item is promoted. If the frontend todo list needs to refresh after promote, it may need a separate broadcast or the client needs to re-fetch. [fixable]
| // Step 3: batch-load sources and links for all items | ||
| const allIds = [...new Set([...rootIds, ...childRows.map((c) => c.id)])]; | ||
| const sourceMap = this.loadSourcesForItems(allIds); | ||
| const linkMap = this.loadLinksForItems(allIds); |
There was a problem hiding this comment.
🔵 unsafe_assumptions: The prefix-match query SELECT * FROM items WHERE substr(id, 1, ?) = ? does a full table scan — it can't use the primary key index. For a small Telos DB this is fine, but if the table grows, consider WHERE id >= ? AND id < ? (range scan on the index) or WHERE id LIKE ? || '%' with appropriate escaping. [fixable]
dimakis
left a comment
There was a problem hiding this comment.
Centaur Review
Found 7 issue(s) (4 warning).
server/app.ts
Well-structured read-only store with good test coverage for the core class, but the app.ts integration has gaps: no duplicate-promote protection until a dependency PR lands, no schema validation on the new TelosStore response path, and missing integration tests for the promote fallback flow.
- 🟡 bugs (L1907): The
already promotedguard checkstelosItem.goalId, but TelosStore opens the DB readonly. After a fire-and-forget--set-goalcall writesgoal_idto SQLite, the readonly connection may not see the update (SQLite readonly connections using WAL can see concurrent writes, but only after re-reading — better-sqlite3 will see them on next query in WAL mode, so this likely works, but the guard is fragile since the Python write is fire-and-forget and may fail silently). If--set-goalhasn't been implemented yet (comment says 'requires mgmt PR #202'), the guard will never trigger and duplicate tasks can be created on repeated promotes. - 🟡 regressions (L1697): The
/api/todosresponse no longer validates againstTodoListResponsewhen using TelosStore. The old path always ranTodoListResponse.safeParse()to ensure response shape conformance. The new TelosStore path returns rawTelosItem[]which includes extra fields (links,contextHints.sessionIds) not in the frontend'sTodoItemtype. While extra fields are harmless, the loss of schema validation means any future TelosStore shape drift won't be caught at the API boundary.[fixable] - 🔵 regressions (L1945): The Telos promote path does not call
onWorkloadBroadcast(the workload path does at the old line 1895). Frontend components listening forworkload_item_updatedwon't see real-time updates for Telos promotes. This may be intentional (Telos items aren't in WorkloadStore), but the frontend may need a separate broadcast or shared event to update the todo list UI after a promote.[fixable]
server/__tests__/telos-store.test.ts
Well-structured read-only store with good test coverage for the core class, but the app.ts integration has gaps: no duplicate-promote protection until a dependency PR lands, no schema validation on the new TelosStore response path, and missing integration tests for the promote fallback flow.
- 🟡 missing_tests: No integration tests for the promote endpoint's Telos path (
/api/workload/items/:id/promotewhen the item is a Telos item). Key untested behaviors: the already-promoted guard (line 1907), the fire-and-forget--set-goalPython call, and the fallback whentelosStoreis null. The existingtodos.test.tsalso doesn't cover the new TelosStore read path.[fixable]
server/telos-store.ts
Well-structured read-only store with good test coverage for the core class, but the app.ts integration has gaps: no duplicate-promote protection until a dependency PR lands, no schema validation on the new TelosStore response path, and missing integration tests for the promote fallback flow.
- 🔵 bugs (L259):
substr(id, 1, ?) = ?does a full table scan on every prefix lookup. For a readonly DB this is acceptable at small scale, but consider usingid LIKE ? || '%'orid >= ? AND id < ?with a computed upper bound for index-friendly prefix matching if the items table grows.[fixable] - 🟡 unsafe_assumptions (L322):
fetchAllChildrenonly fetches one level of children. The comment says 'items in this repo don't nest deeper than parent→child', but the DB schema allows arbitrary nesting (parent_id REFERENCES items(id)). If Telos items ever nest 2+ levels, grandchildren won't appear in counts or results. A safer approach would be a recursive CTE or iterative fetch.[fixable] - 🔵 style (L377):
row.status as TelosItem['status']is an unsafe cast — any string from the DB is accepted. If the DB contains an unexpected status value (e.g. 'dismissed'), it'll silently pass through. Consider validating against the known union or defaulting to a fallback.[fixable]
|
|
||
| // Build description from item context | ||
| // Guard: if already promoted, return existing goal link instead of creating duplicate | ||
| if (telosItem.goalId) { |
There was a problem hiding this comment.
🟡 bugs: The already promoted guard checks telosItem.goalId, but TelosStore opens the DB readonly. After a fire-and-forget --set-goal call writes goal_id to SQLite, the readonly connection may not see the update (SQLite readonly connections using WAL can see concurrent writes, but only after re-reading — better-sqlite3 will see them on next query in WAL mode, so this likely works, but the guard is fragile since the Python write is fire-and-forget and may fail silently). If --set-goal hasn't been implemented yet (comment says 'requires mgmt PR #202'), the guard will never trigger and duplicate tasks can be created on repeated promotes.
| } | ||
|
|
||
| try { | ||
| const items = telosStore.listItems(profile, ['active', 'acknowledged']); |
There was a problem hiding this comment.
🟡 regressions: The /api/todos response no longer validates against TodoListResponse when using TelosStore. The old path always ran TodoListResponse.safeParse() to ensure response shape conformance. The new TelosStore path returns raw TelosItem[] which includes extra fields (links, contextHints.sessionIds) not in the frontend's TodoItem type. While extra fields are harmless, the loss of schema validation means any future TelosStore shape drift won't be caught at the API boundary. [fixable]
| goalId: task.id, | ||
| }; | ||
| res.status(201).json({ task, item: mappedItem }); | ||
| onTaskBroadcast?.({ type: 'task_state', tasks: taskStore.getTree() }); |
There was a problem hiding this comment.
🔵 regressions: The Telos promote path does not call onWorkloadBroadcast (the workload path does at the old line 1895). Frontend components listening for workload_item_updated won't see real-time updates for Telos promotes. This may be intentional (Telos items aren't in WorkloadStore), but the frontend may need a separate broadcast or shared event to update the todo list UI after a promote. [fixable]
| if (!row) { | ||
| // Prefix match | ||
| const rows = db | ||
| .prepare('SELECT * FROM items WHERE substr(id, 1, ?) = ?') |
There was a problem hiding this comment.
🔵 bugs: substr(id, 1, ?) = ? does a full table scan on every prefix lookup. For a readonly DB this is acceptable at small scale, but consider using id LIKE ? || '%' or id >= ? AND id < ? with a computed upper bound for index-friendly prefix matching if the items table grows. [fixable]
| * Fetch all children (any status) whose parent_id is in the given set. | ||
| * Single level — items in this repo don't nest deeper than parent→child. | ||
| */ | ||
| private fetchAllChildren(parentIds: string[]): ItemRow[] { |
There was a problem hiding this comment.
🟡 unsafe_assumptions: fetchAllChildren only fetches one level of children. The comment says 'items in this repo don't nest deeper than parent→child', but the DB schema allows arbitrary nesting (parent_id REFERENCES items(id)). If Telos items ever nest 2+ levels, grandchildren won't appear in counts or results. A safer approach would be a recursive CTE or iterative fetch. [fixable]
| profile: row.profile, | ||
| urgency: row.urgency, | ||
| starred: row.starred === 1, | ||
| status: row.status as TelosItem['status'], |
There was a problem hiding this comment.
🔵 style: row.status as TelosItem['status'] is an unsafe cast — any string from the DB is accepted. If the DB contains an unexpected status value (e.g. 'dismissed'), it'll silently pass through. Consider validating against the known union or defaulting to a fallback. [fixable]
- Add in-memory promote lock to prevent duplicate task creation - Extract buildPromoteDescription() helper (shared by both paths) - Return 409 when promoting an item whose task was deleted - Guard getItem against empty string prefix match - Add Zod validation on TelosStore read path - Add links + sessionIds to TodoItemSchema - Add edge case tests: empty string ID, orphaned child as root Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dimakis
left a comment
There was a problem hiding this comment.
Centaur Review
Found 7 issue(s) (3 warning).
server/app.ts
Solid integration with good test coverage for the store layer. Main concerns: fire-and-forget goal_id persistence creates a duplicate-task risk on retry, and the Telos promote route path lacks route-level test coverage.
- 🟡 bugs (L1949): Fire-and-forget
--set-goalvia Python means if the subprocess fails (logged but swallowed),goal_idis never persisted in the Telos DB. On the next promote attempt for the same item,telosItem.goalIdwill still be null (readonly TelosStore re-reads from DB), bypassing the 'already promoted' guard and creating a duplicate task. ThepromotesInFlightSet only guards within a single in-flight request, not across sequential requests. Consider making the Python call non-fire-and-forget (await it, return 500 on failure) or at minimum logging aterrorlevel instead ofwarn.[fixable] - 🟡 regressions (L1721): The Telos read path's Zod fallback sends raw items on parse failure (
{ profiles, items }) instead of empty arrays. If TelosStore returns data that doesn't match the schema (e.g., a new unexpected status value), the frontend receives unvalidated data without Zod's.default()coercions applied. The Python fallback path correctly returns{ profiles: [], items: [] }on parse failure. Consider using the same empty fallback for consistency, or log the parse error to aid debugging.[fixable] - 🟡 missing_tests (L1916): The Telos branch of the promote endpoint has no route-level tests. The existing
workload-routes.test.tsonly covers the WorkloadStore branch. Missing coverage: Telos item promote (happy path), already-promoted guard (200 with existing task), deleted-task guard (409), race guard (409 in-flight), and the Python--set-goalbridge path.[fixable] - 🔵 style (L1661):
buildPromoteDescriptionacceptsRecord<string, unknown>and both call sites castcontextHints as unknown as Record<string, unknown>. Since bothTelosContextHintsand the workloadContextHintsare known types, the function could accept a typed union (or the shared subset) to avoid the double cast.[fixable]
server/__tests__/telos-store.test.ts
Solid integration with good test coverage for the store layer. Main concerns: fire-and-forget goal_id persistence creates a duplicate-task risk on retry, and the Telos promote route path lacks route-level test coverage.
- 🔵 missing_tests: No test for
listItemswhen the DB lacks thegoal_idcolumn (thehasGoalId = falsepath). The constructor checks for this viapragma table_info, but the test schema always includesgoal_id. A test with an older schema (nogoal_idcolumn) would verify graceful degradation.[fixable] - 🔵 missing_tests: No test for
listItems/getItemwhen the DB lacks alinkstable (thehasLinksTable = falsepath). The constructor detects this, but all tests create the full schema. A test with a schema that omits thelinkstable would cover this backward-compatibility path.[fixable]
server/telos-store.ts
Solid integration with good test coverage for the store layer. Main concerns: fire-and-forget goal_id persistence creates a duplicate-task risk on retry, and the Telos promote route path lacks route-level test coverage.
- 🔵 unsafe_assumptions (L325):
fetchAllChildren,loadSourcesForItems, andloadLinksForItemsall buildIN (?, ?, ...)clauses with dynamically-sized placeholder lists. SQLite has a defaultSQLITE_MAX_VARIABLE_NUMBERlimit (typically 999-32766 depending on version). With a large item set this could hit the limit. In practice Telos likely has <100 items, but the pattern is worth noting. A chunked approach would be safer.[fixable]
| telosItem.contextHints as unknown as Record<string, unknown>, | ||
| ), | ||
| annotations: telosItem.sources.map((s) => `Source: [${s.type}] ${s.title} — ${s.url}`), | ||
| }); |
There was a problem hiding this comment.
🟡 bugs: Fire-and-forget --set-goal via Python means if the subprocess fails (logged but swallowed), goal_id is never persisted in the Telos DB. On the next promote attempt for the same item, telosItem.goalId will still be null (readonly TelosStore re-reads from DB), bypassing the 'already promoted' guard and creating a duplicate task. The promotesInFlight Set only guards within a single in-flight request, not across sequential requests. Consider making the Python call non-fire-and-forget (await it, return 500 on failure) or at minimum logging at error level instead of warn. [fixable]
| const items = telosStore.listItems(profile, ['active', 'acknowledged']); | ||
| const profiles = telosStore.listProfiles(); | ||
| const parsed = TodoListResponse.safeParse({ profiles, items }); | ||
| res.json(parsed.success ? parsed.data : { profiles, items }); |
There was a problem hiding this comment.
🟡 regressions: The Telos read path's Zod fallback sends raw items on parse failure ({ profiles, items }) instead of empty arrays. If TelosStore returns data that doesn't match the schema (e.g., a new unexpected status value), the frontend receives unvalidated data without Zod's .default() coercions applied. The Python fallback path correctly returns { profiles: [], items: [] } on parse failure. Consider using the same empty fallback for consistency, or log the parse error to aid debugging. [fixable]
| return; | ||
| } | ||
|
|
||
| // Fallback: try TelosStore (SHA256 items from strategic task tracker) |
There was a problem hiding this comment.
🟡 missing_tests: The Telos branch of the promote endpoint has no route-level tests. The existing workload-routes.test.ts only covers the WorkloadStore branch. Missing coverage: Telos item promote (happy path), already-promoted guard (200 with existing task), deleted-task guard (409), race guard (409 in-flight), and the Python --set-goal bridge path. [fixable]
| const promotesInFlight = new Set<string>(); | ||
|
|
||
| /** Build a description from context hints and optional user description. */ | ||
| function buildPromoteDescription( |
There was a problem hiding this comment.
🔵 style: buildPromoteDescription accepts Record<string, unknown> and both call sites cast contextHints as unknown as Record<string, unknown>. Since both TelosContextHints and the workload ContextHints are known types, the function could accept a typed union (or the shared subset) to avoid the double cast. [fixable]
| */ | ||
| private fetchAllChildren(parentIds: string[]): ItemRow[] { | ||
| if (parentIds.length === 0) return []; | ||
| const db = this.getDb(); |
There was a problem hiding this comment.
🔵 unsafe_assumptions: fetchAllChildren, loadSourcesForItems, and loadLinksForItems all build IN (?, ?, ...) clauses with dynamically-sized placeholder lists. SQLite has a default SQLITE_MAX_VARIABLE_NUMBER limit (typically 999-32766 depending on version). With a large item set this could hit the limit. In practice Telos likely has <100 items, but the pattern is worth noting. A chunked approach would be safer. [fixable]
Summary
telos-store.ts: readonly better-sqlite3 reader forsmart_todo.dbwith proper parent/child tree buildingGET /api/todosnow reads directly from Telos SQLite (eliminates Python subprocess for reads)POST /api/workload/items/:id/promotefalls back to TelosStore when item not in WorkloadStore, bridging Telos SHA256 IDs to Task BoardBugs fixed
Companion PR: dimakis/mgmt#202 (goal_id column + --set-goal CLI)
Test plan
🤖 Generated with Claude Code