Kestrel Fastening Systems KESTREL FASTENING SYSTEMS

Search

The page most likely to be mis-diagnosed. Search on this baseline binds a repository that no layer ships, and its failure mode is a 200.

What the baseline guarantees

Layers: surface-swift 1.5.1 (the pages and the bindings). No layer ships an index.
Pages:
  Search result page                                (+ grid-row-1, grid-row-2)
  Service Pages/Content search results
  Service Pages/Product and content search results
  Desktop Header carries a Swift-v2_SearchField paragraph.
Bindings — every eCom_ProductCatalog surface in this composition binds the SAME repository
BY PATH:
  /Files/System/Repositories/ProductsFrontend/Products.query
  /Files/System/Repositories/ProductsFrontend/Products.facets
Bound by: surface-swift Shop PLP (IndexQuery + FacetGroups), surface-swift desktop header
search, surface-swift Express Buy, feature-bom-configurator Kit Configurator, and
feature-reordering's Quick Order validation feed (1.0.2 repointed it here).
** No layer in this Distribution ships ProductsFrontend. ** base.contract.json
`repositories.provisionedByGate: false`. The three files arrive with the HOST's Swift 2.4.0
Files overlay, alongside ProductsBackend. A consumer whose host has no ProductsFrontend gets
HTTP 200 with an in-page Lucene "numHits must be > 0" error — never a 500.
Second, separate trap, learned on other demos on this VM and in no skill: an index only
rebuilds because a `Build+Index.task` file exists INSIDE the repository folder. The
`Repository task handler` scheduled task drains those files. A repository without one is never
rebuilt, and build_product_index still answers "Full index build queued" and returns success.
Not present by design: zero content index, zero user index, zero SQL index, zero saved product
queries beyond whatever the host overlay carries.

Which skill covers it

dw-search-indexing (dynamo: true) — references/index-management.md (the build-status
contract), references/mcp-query-tools.md, references/query-authoring.md,
references/query-expressions.md.

Which MCP tools brand it

The read-edit-verify loop is mandatory and ordered
(dw-search-indexing/references/mcp-query-tools.md:70-88):
  get_product_query_expressions(queryId)
  -> delete_product_query_expressions (targeted) OR replace_product_query_expressions
     (anything structural — send the COMPLETE desired tree)
  -> get_product_query_expressions again, and confirm the tree matches the intent
  NodeKeys are POSITIONAL and shift after every change. Never reuse keys from an earlier read.
Discovery before authoring: get_standard_fields, get_product_category_fields,
get_macro_fields. Use only the returned field system names.
Build, in this order (mcp-query-tools.md:135-143):
  build_product_index    — pass indexName EXPLICITLY, as the index FILE name INCLUDING the
                           .index extension ("Products.index"). The default is "Products" and
                           a wrong name succeeds vacuously.
  wait_for_product_index — or poll get_product_index_status until complete WITH a non-zero
                           documentCount
  then, and only then, trust query results.
Safe deletion (mcp-query-tools.md:170-179): get_product_queries first, copy the id AND the
exact name, pass both. If the user's name is not in the list, stop and confirm; never retry
against a different query.

Verify

Three asserts, and you need all three because each hides the others' failure:
1. File: read_file /Files/System/Repositories/ProductsFrontend/Products.query succeeds, and
   list_files on that folder shows a Build+Index.task.
2. MCP: get_product_index_status reports the PRIMARY instance LifecycleState Completed with a
   NON-ZERO documentCount. A status with no documentCount means the tool addressed nothing.
   An index-level State=Warning caused only by an unbuilt secondary balancer instance is a
   false alarm.
3. Storefront: GET <site-root>/shop and assert product-card count > 0 AND the body contains
   neither `dw-error` nor `numHits must be > 0`. Never gate this on HTTP status.
Standing predicate check before shipping any query filter (dw-search-indexing/SKILL.md:177-180):
  count(field = X) + count(field != X) == count(no predicate), with neither side degenerate.