feat(ADM-018): completed feature
This commit is contained in:
45
work/artifacts/F-012/architect.md
Normal file
45
work/artifacts/F-012/architect.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Architect — F-012 Search: interface plus PostgreSQL FTS
|
||||
|
||||
## Decision
|
||||
Split product search from product persistence behind a dedicated catalog port. The HTTP contract stays `GET /products/search`, while the implementation is swappable from PostgreSQL FTS to a future external engine without route changes.
|
||||
|
||||
## Domain/application
|
||||
Add a `ProductSearchRepository` (or equivalent) port in catalog domain:
|
||||
- Input: `q`, `limit`, `offset`, optional `brandSlug`, `activeOnly`.
|
||||
- Output: `Product[]` for this slice to preserve current API payload shape.
|
||||
|
||||
`SearchProducts` must depend on the search port, not on `ProductRepository`. Product CRUD remains in `ProductRepository`.
|
||||
|
||||
## PostgreSQL implementation
|
||||
Add `PgProductSearchRepository` under catalog infrastructure.
|
||||
|
||||
Search scope:
|
||||
- Product: `catalog_products.name`, `description`, `seo_title`, `seo_description`.
|
||||
- Brand: `brands_brands.name`, `slug`, `seo_title`, `seo_description`.
|
||||
- Category: `categories_categories.name`, `slug`, `seo_title`, `seo_description` through `catalog_product_categories`.
|
||||
|
||||
Ranking/order:
|
||||
- When `q` is present, use PostgreSQL full text search with `websearch_to_tsquery('spanish', q)` and `ts_rank_cd`.
|
||||
- Stable ordering: rank descending, then `p.created_at DESC`, then `p.name ASC`, then `p.id ASC`.
|
||||
- When `q` is absent, keep stable listing order by `p.created_at DESC`, `p.name ASC`, `p.id ASC`.
|
||||
- Preserve pagination with bounded `limit` and `offset`.
|
||||
- Preserve active-only public search and `brandSlug` filtering.
|
||||
|
||||
Migration:
|
||||
- Add GIN expression indexes for product, brand, and category searchable text. Do not add extensions or new runtime dependencies.
|
||||
- Keep migration reversible.
|
||||
|
||||
## Telemetry
|
||||
Measure search duration in the route or use case using `performance.now()` and log a structured event through the existing logger:
|
||||
- `event: 'catalog_search'`
|
||||
- `queryPresent`, `brandSlug`, `limit`, `offset`, `durationMs`, `resultCount`
|
||||
- If `q` is present, include a bounded/sanitized `query` value for popular-search analysis (trimmed max 200; never secrets).
|
||||
|
||||
## Tests/evidence
|
||||
- Unit: `SearchProducts` calls the search port with defaults and active-only true.
|
||||
- Unit or repository-level SQL construction: stable ordering and FTS query path should be represented in code and typechecked.
|
||||
- Existing integration tests may remain skipped without `TEST_DATABASE_URL`.
|
||||
- Required commands: `npm run lint`, `npm run typecheck`, `npm test`, `./scripts/verify.sh`.
|
||||
|
||||
## Out of scope
|
||||
No Elasticsearch, Meilisearch, Algolia, typo tolerance beyond PostgreSQL FTS, search suggestions UI, cache, or Redis persistence in this ticket.
|
||||
11
work/artifacts/F-012/documenter.md
Normal file
11
work/artifacts/F-012/documenter.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Documenter — F-012 Search: interface plus PostgreSQL FTS
|
||||
|
||||
## Documentation updated
|
||||
- Updated `project/README.md` catalog section to document PostgreSQL FTS search behavior.
|
||||
- Documented that `GET /products/search?q=<term>` searches product, brand and category text behind `ProductSearchRepository`.
|
||||
- Documented that the HTTP API stays stable when replacing the search implementation.
|
||||
- Documented structured `catalog_search` telemetry with sanitized bounded query metadata, duration and result count.
|
||||
|
||||
## Evidence
|
||||
- `cd project && npm run lint` — PASS after README update.
|
||||
- `./scripts/verify.sh` — PASS after README update.
|
||||
37
work/artifacts/F-012/implementer.md
Normal file
37
work/artifacts/F-012/implementer.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Implementer — F-012 Search: interface plus PostgreSQL FTS
|
||||
|
||||
## Summary
|
||||
Implemented catalog search behind a dedicated search port with a PostgreSQL full-text search adapter, stable relevance ordering, pagination, brand filtering, and structured search telemetry.
|
||||
|
||||
## Code changes
|
||||
- Split search from product persistence:
|
||||
- `ProductSearchCriteria` in `project/src/modules/catalog/domain/product.ts`.
|
||||
- `ProductSearchRepository` in `project/src/modules/catalog/domain/ports.ts`.
|
||||
- `SearchProducts` now depends on `ProductSearchRepository`, not `ProductRepository`.
|
||||
- Added PostgreSQL FTS implementation:
|
||||
- `project/src/modules/catalog/infrastructure/pg-product-search-repository.ts`.
|
||||
- Searches product name/description/SEO fields, brand fields, and category fields.
|
||||
- Uses `websearch_to_tsquery('spanish', q)` and `ts_rank_cd`.
|
||||
- Stable ordering: rank DESC for query search, then `created_at DESC`, `name ASC`, `id ASC`; non-query listing uses `created_at DESC`, `name ASC`, `id ASC`.
|
||||
- Preserves active-only public search, brand slug filtering, limit, and offset.
|
||||
- Added FTS indexes:
|
||||
- `project/migrations/010_catalog_search_fts.js`.
|
||||
- Added search telemetry:
|
||||
- `GET /products/search` measures duration with `performance.now()`.
|
||||
- Logs structured `catalog_search` events via the existing app logger with query presence, bounded sanitized query, brand slug, pagination, duration, and result count.
|
||||
- Query telemetry redacts email-like values, Stripe-like keys, and long token-like strings before logging.
|
||||
- Composition root passes the app logger into catalog routes.
|
||||
- Updated unit tests:
|
||||
- `project/src/modules/catalog/tests/product-use-cases.test.ts` verifies search defaults/active-only forwarding through the dedicated port.
|
||||
|
||||
## Evidence
|
||||
- `cd project && npm run lint` — PASS.
|
||||
- `cd project && npm run typecheck` — PASS.
|
||||
- `cd project && npm test` — PASS: 17 files passed, 7 skipped; 64 passed, 33 skipped.
|
||||
- `./scripts/verify.sh` — PASS.
|
||||
- Security correction run: telemetry query sanitization added, then lint/typecheck/test/verify rerun and passed.
|
||||
|
||||
## Notes
|
||||
- No new npm dependencies.
|
||||
- No Elasticsearch/Meilisearch/Algolia, no typo-tolerance layer, no suggestions UI, no Redis/cache persistence.
|
||||
- Integration tests remain skipped without `TEST_DATABASE_URL`, consistent with prior features.
|
||||
15
work/artifacts/F-012/leader-close.json
Normal file
15
work/artifacts/F-012/leader-close.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"feature_id": "F-012",
|
||||
"agent": "leader",
|
||||
"verdict": "APPROVED",
|
||||
"summary": "F-012 closed with reviewer, security, and QA gates approved. Catalog search now uses a dedicated ProductSearchRepository port, PostgreSQL FTS adapter, stable ordering/pagination, sanitized telemetry, docs, and migrations.",
|
||||
"evidence": [
|
||||
"work/artifacts/F-012/reviewer.json verdict APPROVED",
|
||||
"work/artifacts/F-012/security.json verdict APPROVED",
|
||||
"work/artifacts/F-012/qa.json verdict APPROVED",
|
||||
"work/artifacts/F-012/documenter.md present",
|
||||
"./scripts/verify.sh final close run: PASS",
|
||||
"cd project && npm run lint/typecheck/test: PASS in build evidence"
|
||||
],
|
||||
"timestamp": "2026-08-15T14:53:15Z"
|
||||
}
|
||||
16
work/artifacts/F-012/qa.json
Normal file
16
work/artifacts/F-012/qa.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"feature_id": "F-012",
|
||||
"agent": "qa",
|
||||
"verdict": "APPROVED",
|
||||
"summary": "QA approved. Acceptance criteria are traceable: search has a dedicated swappable port, PostgreSQL FTS adapter uses stable ordering and pagination, search telemetry logs latency/result metadata, and verify is green.",
|
||||
"evidence": [
|
||||
"Acceptance: Given matching query When search Then product returned with stable ordering -> PgProductSearchRepository uses FTS rank then created_at/name/id ordering; unit test verifies active-only forwarding and result filtering through search port",
|
||||
"Acceptance: Swapping implementation requires no API contract change -> route depends on SearchProducts, use case depends on ProductSearchRepository port, PostgreSQL adapter is infrastructure only",
|
||||
"Acceptance: Search latency measured and logged -> GET /products/search logs catalog_search durationMs/resultCount through app logger with sanitized query",
|
||||
"Acceptance: verify.sh green -> ./scripts/verify.sh PASS",
|
||||
"cd project && npm test -- src/modules/catalog/tests/product-use-cases.test.ts: PASS (3 tests)",
|
||||
"cd project && npm run typecheck: PASS",
|
||||
"Full build evidence: lint/typecheck/test/verify PASS"
|
||||
],
|
||||
"timestamp": "2026-08-15T14:52:21Z"
|
||||
}
|
||||
19
work/artifacts/F-012/reviewer.json
Normal file
19
work/artifacts/F-012/reviewer.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"feature_id": "F-012",
|
||||
"agent": "reviewer",
|
||||
"verdict": "APPROVED",
|
||||
"summary": "F-012 review approved. Search is split behind a dedicated ProductSearchRepository port, PostgreSQL FTS searches product/brand/category fields with stable ordering and pagination, telemetry is logged through the existing logger, and verification is green. CodeGraph exploration was attempted first; it returned poor coverage, so review used targeted file reads afterward.",
|
||||
"evidence": [
|
||||
"Read work/current.md and work/artifacts/F-012/architect.md/implementer.md",
|
||||
"Inspected ProductSearchCriteria/ProductSearchRepository/SearchProducts split",
|
||||
"Inspected PgProductSearchRepository SQL: websearch_to_tsquery(spanish), ts_rank_cd, product/brand/category searchable fields, brandSlug and active-only filters, stable order and pagination",
|
||||
"Inspected migration 010_catalog_search_fts.js: reversible GIN FTS indexes, no extension/dependency addition",
|
||||
"Inspected catalog route telemetry and build-app logger injection",
|
||||
"cd project && npm run lint: PASS",
|
||||
"cd project && npm run typecheck: PASS",
|
||||
"cd project && npm test: PASS (64 passed, 33 skipped)",
|
||||
"./scripts/verify.sh: PASS",
|
||||
"gentle-ai review mode status: receipt-driven development off globally, ordinary gates used"
|
||||
],
|
||||
"timestamp": "2026-08-15T14:51:13Z"
|
||||
}
|
||||
16
work/artifacts/F-012/security.json
Normal file
16
work/artifacts/F-012/security.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"feature_id": "F-012",
|
||||
"agent": "security",
|
||||
"verdict": "APPROVED",
|
||||
"summary": "Security approved. Search inputs stay parameterized in SQL, query bounds remain enforced by zod, no dependencies or external services were added, and telemetry now redacts common secret-like patterns before logging search terms.",
|
||||
"evidence": [
|
||||
"Checked PgProductSearchRepository uses parameterized query values for q, brandSlug, limit, offset; no string interpolation of user values into SQL",
|
||||
"Checked API validation bounds q max 200, limit max 100, offset max 10000, brandSlug strict slug regex",
|
||||
"Checked catalog_search telemetry redacts email-like values, Stripe-like keys, and long token-like strings before logging bounded query",
|
||||
"Checked no new runtime dependency or external search service credentials introduced",
|
||||
"cd project && npm audit --audit-level=high --omit=dev: found 0 vulnerabilities",
|
||||
"rg secret patterns over catalog module and migration 010: no credential findings; only sanitizer pattern code references token/key words intentionally",
|
||||
"Post-sanitization lint/typecheck/test/verify all passed in build evidence"
|
||||
],
|
||||
"timestamp": "2026-08-15T14:52:06Z"
|
||||
}
|
||||
Reference in New Issue
Block a user