Skip to content

fix: catch the CLI up with the current API - #132

Merged
boristane merged 8 commits into
mainfrom
fix/catch-up-with-api
Oct 15, 2026
Merged

boristane merged 8 commits into
mainfrom
fix/catch-up-with-api

Conversation

@boristane

@boristane boristane commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

The CLI builds against the live API again: memory reads the new git-backed memory API, service find and name resolution use the node list filter, and the feed and note commands go. Without this, CI fails on every CLI PR and those commands error for users.

Why: nominal removed node search (#4921) and the old feed, notes and memory endpoints (#4745 and earlier) on 2026-10-15. The CLI generates its client from the live spec, so typecheck broke on main.

Where to look

  1. lookupNodes runs one scoped query per name field, exact or substring, paging past rows an underscore wildcard matches until it has enough literal matches. ⚠ Natural-language search is gone.
  2. resolveServiceId backs service show/logs/metrics/graph: exact matches first, then a sole untruncated substring match; a capped scan that kept nothing is reported as incomplete, never "not found".
  3. memory list reads the memory tree; memory show and memory history read a file and the commit log.
  4. service find carries truncated in its JSON and notes it on stderr, so a capped list never passes for a complete one.
  5. thread ask drops the "memory" context type the API no longer accepts.
  6. Service lookup tests run against a stand-in that applies the graph's =/LIKE semantics: underscores, wildcard-only hits filling whole pages, the page cap, exact matches past a full page, and ambiguity.

Feedback wanted: Is a substring match good enough for service find, or should the CLI call something smarter now the vector search is gone?

Risk: Users of feed, note, memory save/delete/find lose those commands, but they already fail against production. Roll back by revert (main would go red again).

Verified: npm run typecheck, npm run lint and npm test (584 pass, offline) against the current production spec.

Decisions (3)
  • Three requests, not one OR query. nominal's graph appends clauses after the provider/type filters without parentheses, so an OR clause would escape those filters; nominal's MCP findResources does the same split.
  • Port memory reads, remove writes. Only tree, file and commits endpoints exist; memory is written by agents now, so save/delete/find have no target.
  • Remove feed and note outright. No replacement endpoints exist in the live spec.
For agents

The four commits don't typecheck individually because the API removals break several command groups at once; review the branch as a whole. src/generated/ is gitignored and regenerated from the live spec by the typecheck step. committedAt from memories.commits is in seconds (as nominal's console reads it), so memory history multiplies by 1000.

🤖 Generated with Claude Code

boristane and others added 4 commits October 15, 2026 08:38
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@polylane

polylane Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Warning

Polylane could not verify the production impact of this pull request.

Checked nominal-api-cloud-infra-prod (40,367 requests, 0 failed invocations, 96h) and CLI telemetry (595 command events / 96h). The extra node-list requests per command are immaterial, and the endpoints the removed commands called were already gone.

View the full analysis →

Also considered · 4 refuted
  • Refuted · Node-lookup fanout multiplies CLI-driven load on the production API worker · The ceiling is 6 command events per hour x 33 requests = 205 requests/h, and that holds only if every command were a capped wildcard scan.
  • Refuted · Removed feed, note and memory write commands break production callers · The failure precedes the change: the endpoints that backed feed, note and the memory writes no longer exist, so a caller already fails today.
  • Refuted · Substring resolution picks a different service than the removed vector search · A wrong pick costs one extra read of logs, metrics, or graph data.
  • Refuted · The new capped-scan branch changes the exit contract for scripts · The exit status stays 1 and the item shape stays the same.

nominal-api-cloud-infra-prod · requests per hour

nominal-api-cli-telemetry-prod · CLI command events per hour

Analysed against 9 cloud accounts and 1 repository
  • Cloud accounts: coreplane-prod, baseberry-uat, coreplane-infra, coreplane, coreplane-gtm, 251714435813, Polylane, coreplanelabs, nominal-493716
  • Repository: coreplanelabs/cli

View in Polylane Disable reviews

Polylane could not find the cloud resources this repository manages, so this review looked at the entire cloud account. Connect this repository to its resources and the next review will focus on exactly what this code deploys to.

Connect resources

Polylane analysed 8ae156d for production impact. You can ask follow-ups by mentioning @polylane in a comment.

Rate this review: 👍 helpful · 👎 not helpful · 😕 confusing

Previous verdicts (4)
Head Verdict Analysis
2229d3e Production impact not verified analysis
7cb01cd Production impact not verified analysis
3712ea7 Production impact not verified analysis
66b6cfe Production impact not verified analysis

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: 2 issues

Reviewed at 66b6cfe.

For agents

Rationale: Service lookup loses underscore matches and makes exact-match decisions after discarding candidates.

Severity Finding Where
minor F1 Queries containing underscores miss matching service names and aliases. src/commands/service/id.ts:88
minor F2 Truncating candidates before exact matching can reject the right service or silently choose an ambiguous one. src/commands/service/id.ts:102

F1 invariant: An underscore in the query must not disable substring matching across service IDs, names and alternate names, whether finding services or resolving short names.

  • [C1] Unscoped service find payments_api with a node whose id is prod-payments_api-worker and whose other name fields do not match. → Return the node because its ID contains the query.
  • [C2] Provider-scoped service find payments_api with a node whose data.name is prod-payments_api-worker. → Return the matching node within the requested provider.
  • [C3] Type-scoped service find payments_api with a node whose alternateNames contains payments_api and whose ID and data.name are unrelated. → Return the node within the requested type, despite alternate names being stored as a JSON array.
  • [C4] Service find payments_api with both provider and type supplied and a matching alternate name. → Return the matching node while preserving both scope filters.
  • [C5] Short-name resolution of payments_api for service show, logs, metrics or graph when the only matching resource has that alternate name. → Resolve that resource rather than reporting that no service exists.
  • [C6] A fully qualified composite service ID containing underscores is supplied to a resolver caller. → Preserve the existing strict-ID bypass and do not perform substring queries.

F2 invariant: Short-name resolution must establish exact-match preference and ambiguity before candidate truncation; neither per-query pagination nor the merged display limit may establish false uniqueness or hide a returned exact match.

  • [C1] The ID query returns 50 substring-only nodes and the name query returns another node whose data.name exactly equals the requested short name. → Resolve the unique exact-name node instead of discarding it and reporting substring ambiguity.
  • [C2] The ID query fills the merged limit with substring-only nodes and the alternate-name query returns a different node with an exact alternate name. → Resolve the exact-alias node instead of dropping it.
  • [C3] The retained 50 ID matches include one exact ID, while a later name-query result on a different node is also an exact match. → Report multiple exact matches instead of silently selecting the retained exact-ID node.
  • [C4] The retained candidates contain one exact match while a later alternate-name-query result on another node has an exact alias. → Report ambiguity rather than treating the retained exact match as unique.
  • [C5] A field has more than 50 substring matches and its unique exact match falls beyond the first API page. → Find the exact match independently of the first substring page, such as through exact-match queries before bounded fallback.
  • [C6] A complete lookup has one exact match, one nonexact match only, or multiple exact/nonexact matches. → Preserve the existing intended branches: choose a unique exact match, choose the sole nonexact match, and reject ambiguous results.
  • [C7] A complete lookup returns no matches. → Retain the no-service error rather than introducing a fallback selection.
  • [C8] A fully qualified composite ID is supplied. → Keep the strict-ID bypass without requiring name lookup or pagination.

Full review

F1 (high confidence): The backend only adds surrounding % wildcards when a LIKE value contains neither _ nor %. Passing payments_api unchanged therefore misses longer IDs/names and even an exact alias stored as ["payments_api"]. Both service find and short-name resolution can report no match. Construct the substring pattern explicitly rather than relying on server auto-wrapping.

F2 (high confidence): Results are merged ID-first and truncated to 50 before exact matching. With 50 ID substring hits, an exact name returned by the second query is discarded. Worse, retaining one exact match while discarding another makes an ambiguous name resolve silently. Establish exact matches and ambiguity before applying the display cap, using exact queries so API pagination cannot hide them either.

The graph only wraps a LIKE value in % when it holds no _ or %, so a
query like payments_api was matched as a whole value. Send the pattern
explicitly and drop the extra rows the _ wildcard lets through.

Short-name resolution now queries exact id, name and alias matches on
their own before the substring search, so a page of partial matches can
neither hide the exact one nor make a shared name look unique. A full
substring page counts as ambiguous.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@boristane

Copy link
Copy Markdown
Contributor Author

Both findings were real; fixed in 3712ea7.

F1 (underscores). Confirmed in nominal's coerceClauseValue (packages/durable-infra-graph/do.ts): a LIKE value containing _ or % is used as-is, unwrapped. The CLI now sends %<query>% itself, then keeps only nodes whose id, data.name or an alternate name literally contains the query (case-insensitive), since _ is a one-character wildcard. Scope filters are unchanged. The strict composite-ID bypass is untouched and still skips the API (test: returns the full form without calling the API, now with an underscore id). Covers C1–C6.

F2 (truncation). resolveServiceId now queries exact matches first (= on id and name; = on alternate_names goes through the graph's json_each path), independent of any substring page:

  • one exact match → resolved;
  • several exact matches → ambiguity error listing them;
  • no exact match → substring search, resolved only when it returns a single node and no field query filled its page; a full page is reported as ambiguous.

The display cap in service find applies only after merging. Tests use a stand-in that applies the graph's =/LIKE semantics and page limit: exact name and exact alias found behind 60 partial matches (C1, C2, C5), two exact matches with one behind the page reported as ambiguous (C3, C4), and the existing single/none/multiple branches (C6–C8).

npm run typecheck, npm run lint, npm test (575 pass) all pass. I also ran the new tests against the previous id.ts: the underscore, hidden-exact and full-page cases fail there.

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: 1 issue

Reviewed at 3712ea7.

For agents

Rationale: Exact-match truncation is fixed, but underscore lookups can still lose real matches when wildcard-only hits fill the first API page.

Severity Finding Where
minor review:5536204904:F1 Queries containing underscores miss matching service names and aliases. src/commands/service/id.ts:88

review:5536204904:F1 invariant: An underscore in the query must not disable substring matching across service IDs, names and alternate names, whether finding services or resolving short names.

  • [C1] Unscoped service find payments_api with a node whose id is prod-payments_api-worker and whose other name fields do not match. → Return the node because its ID contains the query.
  • [C2] Provider-scoped service find payments_api with a node whose data.name is prod-payments_api-worker. → Return the matching node within the requested provider.
  • [C3] Type-scoped service find payments_api with a node whose alternateNames contains payments_api and whose ID and data.name are unrelated. → Return the node within the requested type, despite alternate names being stored as a JSON array.
  • [C4] Service find payments_api with both provider and type supplied and a matching alternate name. → Return the matching node while preserving both scope filters.
  • [C5] Short-name resolution of payments_api for service show, logs, metrics or graph when the only matching resource has that alternate name. → Resolve that resource rather than reporting that no service exists.
  • [C6] A fully qualified composite service ID containing underscores is supplied to a resolver caller. → Preserve the existing strict-ID bypass and do not perform substring queries.
  • [C7] Unscoped service find payments_api when the ID query's first 50 raw LIKE hits have IDs prod-payments-api-0 through prod-payments-api-49, followed by prod-payments_api-worker; other name fields do not match. → Return prod-payments_api-worker; wildcard-only false positives must not consume the literal-result limit.
  • [C8] Provider-scoped service find payments_api when 50 nodes in that provider have data.name values prod-payments-api-0 through prod-payments-api-49 before a node named prod-payments_api-worker; other fields do not match. → Return the literal name match while preserving the provider filter, rather than filtering the first page to an empty result.
  • [C9] Type-scoped service find payments_api when the alternate_names query first returns 50 nodes with prod-payments-api-* aliases before a node with prod-payments_api-worker as an alias; IDs and data.name do not match. → Return the literal alias match within the requested type despite the preceding JSON-text LIKE false positives.
  • [C10] Service find payments_api with both provider and type supplied, when 50 in-scope wildcard-only alias hits precede the only literal alias match. → Find the literal alias match beyond the first raw page and preserve both scope filters.
  • [C11] Short-name resolution of payments_api for service show, logs, metrics or graph has no exact match, 50 wildcard-only ID hits on the first page, and the sole literal substring match prod-payments_api-worker on the next page. → Resolve the unique literal substring match after establishing completeness; do not report No service found from a fully filtered first page.
  • [C12] Short-name resolution of payments_api has no exact match, 50 wildcard-only data.name hits before the sole literal substring name prod-payments_api-worker, and unrelated IDs and aliases. → Resolve the unique literal name match after paging the raw candidates instead of treating the filtered first page as complete.
  • [C13] Short-name resolution of payments_api has no exact match, 50 wildcard-only alternate_names hits before the sole literal substring alias prod-payments_api-worker, and unrelated IDs and data.name. → Resolve the unique literal alias match after paging the raw candidates instead of reporting no service.
  • [C14] findServiceNodes is called with query payments_api and limit 1, and its first raw ID hit is prod-payments-api-worker while the next is prod-payments_api-worker. → Apply the requested limit to literal matches, returning prod-payments_api-worker rather than an empty list.
Finding Case Outcome Current-head evidence
review:5536204904:F1 C1 fixed At this head id.ts:105-108 sends %payments_api% and :119-126 keeps prod-payments_api-worker by literal inclusion. The original unscoped sparse ID scenario is repaired; raw-page saturation is covered by appended cases.
review:5536204904:F1 C2 fixed id.ts:101-115 retains provider in filters for every field query; %payments_api% matches a longer data.name, and containsQuery at :81-84 retains it in the original sparse scenario.
review:5536204904:F1 C3 fixed id.ts sends a type-scoped alternate_names LIKE %payments_api%; nominal generateClause applies LIKE to JSON text, so ["payments_api"] matches, and containsQuery retains its literal alias.
review:5536204904:F1 C4 fixed id.ts:101-115 carries provider and type together in all three requests. The wrapped substring pattern and literal alias filter preserve the original matching-alias case without dropping either scope.
review:5536204904:F1 C5 fixed id.ts:163-165 now queries exact matches first. nominal generateClause uses json_each for alternate_names '=', so an exact payments_api alias yields the single node. All four callers use resolveServiceId; test/service-find.test.ts also contains this alias fixture.
review:5536204904:F1 C6 fixed id.ts:158-159 still returns parseServiceIdStrict(raw) before either lookup. The full-ID test now uses pay_api and asserts no API requests; show/logs/metrics/graph all retain this path.
review:5536204904:F2 C1 fixed id.ts:163 queries '=' independently across fields before any substring lookup. Fifty ID substring-only nodes cannot fill the name '=' query or hide its unique exact name.
review:5536204904:F2 C2 fixed id.ts:163 exact lookup includes alternate_names '='; backend json_each performs element equality. ID substring-only nodes never enter the exact candidate merge, so the exact alias is retained.
review:5536204904:F2 C3 fixed lookupNodes merges all exact field results without a merged slice at :119-128; resolveServiceId:165 rejects multiple nodes. An exact ID and another node's exact name therefore remain ambiguous.
review:5536204904:F2 C4 fixed Exact ID/name and exact-alias nodes are all merged before resolveServiceId:165 checks ambiguity; alternate_names '=' uses backend json_each. The display cap only exists in findServiceNodes:139, outside resolver exact selection.
review:5536204904:F2 C5 fixed The '=' queries at id.ts:163 are independent of all LIKE pages; substring-only candidates do not consume an exact-query page. test/service-find.test.ts:113-120 supplies 60 partial matches before exact name/alias fixtures.
review:5536204904:F2 C6 fixed id.ts:164 chooses one exact node, :165 rejects multiple exact nodes, and :176 selects a sole nonexact node only for an untruncated complete fallback. Other nonempty fallbacks reach multipleMatches. New F1 cases address incomplete wildcard-only fallback pages, not this complete-lookup case.
review:5536204904:F2 C7 fixed When exact and complete substring queries have no matches, id.ts:168-174 still throws the no-service CLIError. No fallback node is synthesized.
review:5536204904:F2 C8 fixed id.ts:158-159 preserves strict composite-ID return before name queries or pagination; all four resolver callers use this shared implementation.
Prior finding Resolution Evidence at this head
review:5536204904:F2 fixed Verified all C1-C8 against id.ts:93-177 and show/logs/metrics/graph callers. Independent '=' field queries and an uncapped exact merge establish exact preference and ambiguity before substring pagination; complete fallback uniqueness, no-match errors and strict-ID bypass remain intact. Backend do.ts confirms alternate_names equality uses json_each. Verification was by code inspection; no project tests were run.

Full review

review:5536204904:F1 — Minor, high confidence: At src/commands/service/id.ts:108–124, the 50-row API limit applies before literal filtering. For payments_api, 50 prod-payments-api-* rows can consume the page because _ is a LIKE wildcard, hiding a later prod-payments_api-worker. service find then returns nothing; short-name resolution with no exact match reports “No service found.” The same failure affects names and aliases. Page through raw candidates before applying the literal-result cap or deciding uniqueness, and add a regression case with a full page of wildcard-only hits.

The _ in a query like payments_api matches any character, so rows such
as prod-payments-api-N can fill a whole page and hide the literal match
behind them. Each field query now reads pages of 200, ordered by id,
until it has `limit` literal matches or runs out (10 pages at most, and
a field that hits the cap counts as truncated). The limit applies to
literal matches, not raw rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@boristane

Copy link
Copy Markdown
Contributor Author

Follow-up on the reopened F1 (C7–C14), fixed in 7cb01cd.

You're right that filtering only the first page wasn't enough: rows that match only because of the _ wildcard (prod-payments-api-N) could fill the page and hide the literal match. Each field query now pages (limit: 200, offset, orderBy: 'id' for stable offsets), keeping only literal matches, until the field has limit literal matches or a short page ends it. The scan is capped at 10 pages per field; a field that hits the cap, or fills limit, counts as truncated, so resolution never claims uniqueness from an incomplete scan. The limit now applies to literal matches, not raw rows (C14). Scope filters are sent on every page.

New tests put 250 wildcard-only rows ahead of the literal match for id, name and alias, both for service find (unscoped and provider+type scoped) and for short-name resolution (C7–C13), plus the limit-1 case (C14). Against 3712ea7 all seven fail; at 7cb01cd they pass. npm run typecheck, npm run lint and npm test (581 pass) are green.

@polylane polylane Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested observability improvements for code this pull request changes.

Comment thread src/commands/service/id.ts Outdated
Comment on lines +155 to +156
const { nodes } = await lookupNodes(api, workspaceId, query, 'substring', opts);
return nodes.slice(0, opts.limit ?? DEFAULT_FIND_LIMIT);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The truncation flag is computed and then dropped, so a capped listing looks complete; a stderr warning keeps stdout pure data.

Suggested change
const { nodes } = await lookupNodes(api, workspaceId, query, 'substring', opts);
return nodes.slice(0, opts.limit ?? DEFAULT_FIND_LIMIT);
const { nodes, truncated } = await lookupNodes(api, workspaceId, query, 'substring', opts);
if (truncated) process.stderr.write(`More nodes match "${query}" than the ${opts.limit ?? DEFAULT_FIND_LIMIT} shown. Narrow the query.\n`);
return nodes.slice(0, opts.limit ?? DEFAULT_FIND_LIMIT);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 2229d3e, slightly reshaped: findServiceNodes now returns { nodes, truncated } (also true when merged literal matches exceed the limit), and the service find command writes the note to stderr, skipped under --quiet, so the library stays free of output. Test: says when more services match than it returns.

findServiceNodes now returns whether more services match than it
returned, and service find notes it on stderr (unless --quiet), so a
capped list no longer looks complete and stdout stays parseable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: 1 issue

Reviewed at 2229d3e.

For agents

Rationale: The new warning helps nonquiet service find, but capped empty scans still produce false no-service errors and silently empty quiet JSON output.

Severity Finding Where
minor review:5536204904:F1 Queries containing underscores miss matching service names and aliases. src/commands/service/id.ts:88

review:5536204904:F1 invariant: An underscore in the query must not disable substring matching across service IDs, names and alternate names, whether finding services or resolving short names.

  • [C1] Unscoped service find payments_api with a node whose id is prod-payments_api-worker and whose other name fields do not match. → Return the node because its ID contains the query.
  • [C2] Provider-scoped service find payments_api with a node whose data.name is prod-payments_api-worker. → Return the matching node within the requested provider.
  • [C3] Type-scoped service find payments_api with a node whose alternateNames contains payments_api and whose ID and data.name are unrelated. → Return the node within the requested type, despite alternate names being stored as a JSON array.
  • [C4] Service find payments_api with both provider and type supplied and a matching alternate name. → Return the matching node while preserving both scope filters.
  • [C5] Short-name resolution of payments_api for service show, logs, metrics or graph when the only matching resource has that alternate name. → Resolve that resource rather than reporting that no service exists.
  • [C6] A fully qualified composite service ID containing underscores is supplied to a resolver caller. → Preserve the existing strict-ID bypass and do not perform substring queries.
  • [C7] Unscoped service find payments_api when the ID query's first 50 raw LIKE hits have IDs prod-payments-api-0 through prod-payments-api-49, followed by prod-payments_api-worker; other name fields do not match. → Return prod-payments_api-worker; wildcard-only false positives must not consume the literal-result limit.
  • [C8] Provider-scoped service find payments_api when 50 nodes in that provider have data.name values prod-payments-api-0 through prod-payments-api-49 before a node named prod-payments_api-worker; other fields do not match. → Return the literal name match while preserving the provider filter, rather than filtering the first page to an empty result.
  • [C9] Type-scoped service find payments_api when the alternate_names query first returns 50 nodes with prod-payments-api-* aliases before a node with prod-payments_api-worker as an alias; IDs and data.name do not match. → Return the literal alias match within the requested type despite the preceding JSON-text LIKE false positives.
  • [C10] Service find payments_api with both provider and type supplied, when 50 in-scope wildcard-only alias hits precede the only literal alias match. → Find the literal alias match beyond the first raw page and preserve both scope filters.
  • [C11] Short-name resolution of payments_api for service show, logs, metrics or graph has no exact match, 50 wildcard-only ID hits on the first page, and the sole literal substring match prod-payments_api-worker on the next page. → Resolve the unique literal substring match after establishing completeness; do not report No service found from a fully filtered first page.
  • [C12] Short-name resolution of payments_api has no exact match, 50 wildcard-only data.name hits before the sole literal substring name prod-payments_api-worker, and unrelated IDs and aliases. → Resolve the unique literal name match after paging the raw candidates instead of treating the filtered first page as complete.
  • [C13] Short-name resolution of payments_api has no exact match, 50 wildcard-only alternate_names hits before the sole literal substring alias prod-payments_api-worker, and unrelated IDs and data.name. → Resolve the unique literal alias match after paging the raw candidates instead of reporting no service.
  • [C14] findServiceNodes is called with query payments_api and limit 1, and its first raw ID hit is prod-payments-api-worker while the next is prod-payments_api-worker. → Apply the requested limit to literal matches, returning prod-payments_api-worker rather than an empty list.
  • [C15] Short-name resolution of payments_api for show, logs, metrics or graph has no exact match, 2000 wildcard-only ID matches zz-payments-api-0000 through zz-payments-api-1999, and the unique literal ID aa-payments_api-worker after the ten-page cap in backend DESC id order. → Resolve after establishing completeness, or explicitly report an incomplete lookup; do not claim No service found while the literal match remains unscanned.
  • [C16] Short-name resolution of payments_api has no exact match; 2000 nodes zz-0000 through zz-1999 have wildcard-only data.name values prod-payments-api-N, and aa-target has the sole literal substring name prod-payments_api-worker beyond the ten-page DESC-ordered scan. IDs and aliases do not match. → Resolve after establishing completeness, or report an incomplete lookup rather than No service found.
  • [C17] Short-name resolution of payments_api has no exact match; 2000 nodes zz-0000 through zz-1999 have wildcard-only aliases prod-payments-api-N, and aa-target has the sole literal substring alias prod-payments_api-worker beyond the ten-page DESC-ordered scan. IDs and data.name do not match. → Resolve after establishing completeness, or report an incomplete lookup rather than No service found.
  • [C18] Unscoped service find payments_api --output json --quiet sees 2000 wildcard-only IDs zz-payments-api-0000 through zz-payments-api-1999 before aa-payments_api-worker under backend DESC id ordering; data.name and aliases do not match. → Return the literal match or an explicit machine-readable incomplete-search result/error; do not silently emit items [] and count 0 as a normal complete result.
  • [C19] Provider-scoped service find payments_api --output json --quiet sees 2000 in-provider nodes zz-0000 through zz-1999 with wildcard-only data.name values prod-payments-api-N before aa-target with the literal name prod-payments_api-worker in DESC id order; IDs and aliases do not match. → Preserve the provider scope and return the literal match or surface the incomplete scan in machine-readable output/error, even when quiet suppresses progress.
  • [C20] Type-scoped service find payments_api --output json --quiet sees 2000 in-type nodes zz-0000 through zz-1999 with wildcard-only aliases prod-payments-api-N before aa-target with literal alias prod-payments_api-worker in DESC id order; IDs and data.name do not match. → Preserve the type scope and return the literal alias match or expose that the capped search is incomplete rather than emitting a silent empty result.
  • [C21] Service find payments_api --output json --quiet with both provider and type supplied sees 2000 in-scope wildcard-only alias hits on nodes zz-0000 through zz-1999 before the sole literal alias prod-payments_api-worker on aa-target in DESC id order. → Preserve both scope filters and return the literal match or an explicit incomplete-search result/error; quiet must not make capped empty scans indistinguishable from completed searches.
Finding Case Outcome Current-head evidence
review:5536204904:F1 C1 fixed At 2229d3e service/id.ts:111-127 still sends %payments_api% and containsQuery at :81-84 retains prod-payments_api-worker. findServiceNodes:159-160 returns this node in its nodes property, consumed correctly by find.ts:27.
review:5536204904:F1 C2 fixed service/id.ts:107-109 and :123 retain provider on every field/page request. Wrapped LIKE plus literal filtering still retains the original longer data.name match; the new NodeLookup return does not drop it.
review:5536204904:F1 C3 fixed service/id.ts still queries alternate_names with %payments_api% and the type filter. Backend JSON-text LIKE admits the exact alias, and containsQuery retains it in the returned nodes property.
review:5536204904:F1 C4 fixed service/id.ts:107-109 passes provider and type together through :123 on every request. Literal alias matching and the new findServiceNodes result preserve the original scoped match.
review:5536204904:F1 C5 fixed service/id.ts:184-186 performs exact lookup before substring scans; alternate_names equality uses backend json_each. All four checked callers show/logs/metrics/graph use the same resolver, so the sole exact payments_api alias resolves.
review:5536204904:F1 C6 fixed service/id.ts:179-180 returns strict composite IDs before lookup. The current test at service-find.test.ts:157-162 retains the pay_api fixture and zero-request assertion; all four resolver callers preserve this route.
review:5536204904:F1 C7 fixed service/id.ts:119-131 scans 200 raw rows per page and counts only kept matches. Fifty wildcard-only ID hits plus the literal hit do not exhaust the scan. The updated test at service-find.test.ts:115-119 retains its 250-hit regression.
review:5536204904:F1 C8 fixed The name scan at service/id.ts:117-134 retains provider per page, filters wildcard-only hits and completes the original 51-row scenario within one raw page, returning the literal node through findServiceNodes.nodes.
review:5536204904:F1 C9 fixed The alternate_names scan at service/id.ts:117-134 retains type and does not count false positives toward the literal limit. Fifty false hits followed by the literal alias return that alias node.
review:5536204904:F1 C10 fixed service/id.ts:107-109/:123 carries both filters through the alias scan, and :126-131 keeps the literal alias despite preceding 50 false hits. The scoped 250-hit test retains its assertion at service-find.test.ts:121-134.
review:5536204904:F1 C11 fixed For the original 50-wildcard-ID-hit scenario, empty exact queries are followed by one complete 51-row partial page with one kept node and truncated false. service/id.ts:197 therefore resolves the literal ID.
review:5536204904:F1 C12 fixed The data.name field scan filters the original 50 wildcard-only rows and retains the sole literal name on a short 51-row page. The complete one-node fallback resolves at service/id.ts:197.
review:5536204904:F1 C13 fixed The alternate_names field scan filters the original 50 JSON-text LIKE false positives, retains the literal alias, and establishes completeness from the short raw page; service/id.ts:197 resolves it.
review:5536204904:F1 C14 fixed service/id.ts:126-129 applies limit 1 after literal filtering, so the wildcard-only first row does not consume it. The updated helper returns the literal node plus truncation metadata at :160; service-find.test.ts:136-140 preserves the node assertion.

Full review

review:5536204904:F1 — Minor, high confidence: The new warning only partially fixes scan-limit handling. With 2,000 wildcard-only IDs such as zz-payments-api-* sorting ahead of aa-payments_api-worker, lookup still returns { nodes: [], truncated: true }. The resolver ignores truncation at src/commands/service/id.ts:189 and reports “No service found” for show, logs, metrics and graph. Also, service find --output json --quiet suppresses the warning and returns {"items":[],"count":0} without an incompleteness signal. Preserve the cap, but surface incomplete scans in resolver errors and machine-readable output.

When the capped scan keeps no literal match, short-name resolution now
says the lookup couldn't finish (with the full form and scoped find as
next steps) instead of "No service found". service find's JSON output
carries `truncated`, so a quiet, machine-read empty result is not
mistaken for a complete one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@boristane

Copy link
Copy Markdown
Contributor Author

C15–C21 addressed in 8ae156d, keeping the 10-page cap as you suggested.

  • Resolver (C15–C17): when the substring scan hits the cap and keeps no literal match, resolveServiceId now throws "Couldn't finish looking up …: too many services partly match it", with the full form and a scoped service find as next steps. "No service found" is only reported after a complete scan.
  • service find (C18–C21): the JSON output now carries truncated, so --output json --quiet returns {"items":[],"count":0,"truncated":true} instead of an empty result that looks complete. Scope filters are sent on every page as before. The stderr note stays for interactive use.

Tests: a capped scan (2,001 wildcard-only rows ahead of the literal) for id, name and alias reports the incomplete lookup, and a scoped capped find returns { nodes: [], truncated: true }. npm run typecheck, npm run lint, npm test (584 pass) are green.

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM: no issues found

Reviewed at 8ae156d.

For agents

Rationale: All 21 outstanding lookup cases are fixed at this head; no further actionable defects found.

Finding Case Outcome Current-head evidence
review:5536204904:F1 C1 fixed service/id.ts:111-129 sends %payments_api% and applies literal containsQuery before counting matches. The original sparse ID scenario returns prod-payments_api-worker through findServiceNodes.nodes.
review:5536204904:F1 C2 fixed service/id.ts:107-123 retains provider on every field/page request; wrapped LIKE plus containsQuery retains the original longer data.name match. service/find.ts:27 consumes the returned nodes.
review:5536204904:F1 C3 fixed service/id.ts queries alternate_names with %payments_api% while retaining type. JSON-text LIKE admits the original exact alias, and containsQuery retains it in the returned nodes.
review:5536204904:F1 C4 fixed service/id.ts:107-123 carries provider and type together on every query. Wrapped alias matching plus literal filtering preserves the original matching alias without dropping either scope.
review:5536204904:F1 C5 fixed service/id.ts:184-186 queries exact fields first, including alternate_names equality, and returns the sole exact alias node. Checked show.ts, logs.ts, metrics.ts and graph.ts: all use this resolver before their resource requests.
review:5536204904:F1 C6 fixed service/id.ts:179-180 returns parseServiceIdStrict(raw) before lookup. All four resolver callers retain this route; service-find.test.ts:182-187 uses pay_api and asserts zero API requests.
review:5536204904:F1 C7 fixed service/id.ts:119-131 scans 200 raw rows per page and counts only literal matches. The original 50 wildcard-only IDs plus one literal ID fit in a complete page and return the literal node; the 250-hit regression fixture also exercises continued paging.
review:5536204904:F1 C8 fixed service/id.ts:117-134 scans the name field with provider retained on every page and filters wildcard-only rows before counting. The original 51-row scenario returns the sole literal name match.
review:5536204904:F1 C9 fixed The alternate_names scan at service/id.ts:117-134 retains type and ignores wildcard-only aliases for the literal limit. Fifty false positives followed by the original literal alias are returned on a complete page.
review:5536204904:F1 C10 fixed service/id.ts:107-123 carries provider and type through every alias page, and :126-129 counts only kept matches. The original literal alias beyond 50 false hits is retained; the scoped 250-hit fixture checks continued paging and both request filters.
review:5536204904:F1 C11 fixed With empty exact queries, the original 50 wildcard-only ID rows and sole literal ID produce one node on a short substring page with truncated false. service/id.ts:206 resolves that node; all four command callers share this path.
review:5536204904:F1 C12 fixed The data.name scan filters the original 50 false positives and retains the sole longer literal name on a complete 51-row page. service/id.ts:206 permits the resulting unique, untruncated fallback.
review:5536204904:F1 C13 fixed The alternate_names scan filters the original 50 JSON-text LIKE false positives, retains the sole literal alias and establishes completeness on a short page. service/id.ts:206 then resolves it.
review:5536204904:F1 C14 fixed service/id.ts:126-129 checks keep before incrementing nodes.length, so a wildcard-only first row cannot consume limit 1. findServiceNodes slices the retained literal nodes; service-find.test.ts:136-140 preserves the explicit result assertion.
review:5536204904:F1 C15 fixed After ten full ID pages containing only wildcard false positives, service/id.ts:134 returns truncated true with zero nodes. The new :190-197 branch throws an incomplete-lookup CLIError before the no-service branch. This is independent of ascending or descending backend order and reaches all four callers.
review:5536204904:F1 C16 fixed A capped data.name scan propagates truncated through service/id.ts:145 even when it keeps no nodes. The new zero-node/truncated branch at :190-197 reports incomplete lookup rather than No service found; the capped name fixture checks that error branch.
review:5536204904:F1 C17 fixed A capped alternate_names scan propagates truncated even when literal alias filtering keeps nothing. service/id.ts:190-197 now reports incomplete lookup before :199-205 can claim no service; the capped alias fixture covers this branch.
review:5536204904:F1 C18 fixed An unscoped capped ID scan produces nodes [] and truncated true. service/find.ts:37-40 now includes truncated in its result, and output/formatter.ts:81-83 serializes the entire result before any quiet handling, preserving the machine-readable incompleteness flag.
review:5536204904:F1 C19 fixed Provider is retained by service/id.ts:107-123 on every data.name page. Any capped field sets the merged truncated flag, passed through findServiceNodes and service/find.ts:40 into JSON; quiet only suppresses the later stderr warning.
review:5536204904:F1 C20 fixed Type remains on every alternate_names request. service/id.ts:145/:160 preserves capped-scan truncation, service/find.ts:40 includes it, and formatList JSON output retains it independently of quiet, so an empty capped alias search no longer looks complete.
review:5536204904:F1 C21 fixed Both provider and type remain in filters on every alias page. Truncation is ORed across fields and included in service find JSON before quiet handling; the scoped capped-find fixture also asserts nodes [] and truncated true.
Prior finding Resolution Evidence at this head
review:5536204904:F1 fixed Verified C1-C21 at 8ae156d by inspecting service/id.ts, service/find.ts, output/formatter.ts, output/json.ts, errors/handler.ts and all show/logs/metrics/graph resolver callers. Wrapped literal-aware paged queries and scope propagation preserve C1-C14. New empty-truncated handling reports incomplete lookup for C15-C17; JSON now preserves truncated even with quiet for C18-C21. Reviewed existing and added regression fixtures without executing project tests. The fix is order-independent and covers all three queried fields and all four scope combinations.

Full review

The prior lookup issue is fixed; no further actionable findings.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-approved: coreplane-switchboard[bot] reviewed this PR and posted an LGTM verdict (see its review). A repo admin enabled this via the auto-approve workflow.

@boristane
boristane merged commit d02576b into main Oct 15, 2026
5 checks passed
@boristane
boristane deleted the fix/catch-up-with-api branch October 15, 2026 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant