Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: CI

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
verify:
name: Install, lint, typecheck, test, build, drills
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc

- name: Enable Corepack and activate the pinned pnpm version
run: |
corepack enable
corepack prepare --activate "$(node -p "require('./package.json').packageManager")"

- name: Get pnpm store path
id: pnpm-store
run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"

- name: Restore pnpm cache
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile

- name: Lint
run: pnpm lint

- name: Typecheck
run: pnpm typecheck

- name: Test
run: pnpm test

- name: Build
run: pnpm build

- name: Catalog configuration contract drills
run: |
pnpm catalog-config:validate
pnpm catalog-config:fixtures
pnpm catalog-intake:validate
pnpm catalog-intake:fixtures

- name: Catalog pipeline fixture drills
run: |
pnpm readiness:fixtures
pnpm normalization:fixtures
pnpm indexing:fixtures

- name: Dependency-free package drills
run: |
pnpm smoke:mock-catalog
pnpm smoke:performance
pnpm smoke:recovery
pnpm smoke:operations
pnpm smoke:security
pnpm smoke:indexing-jobs
pnpm smoke:release-control
pnpm smoke:commercial-readiness
pnpm validate:release-control
pnpm validate:commercial-readiness
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SearchSignal Commerce Retrieval Framework

T-022E refreshed the isolated runtime and verified source equality, but the current restricted network policy does not authorize registry egress. No dependency installation was attempted; package launchers and `zod` remain unavailable, P-020V remains required, Sprint 20 is not ready to close, and Sprint 21 is not implemented.
R-001 (repository recovery) restored a real dependency runtime on the GitHub-hosted clone: `pnpm install --frozen-lockfile` succeeds, and the full workspace lint, typecheck, test, and build ladder passes for the first time in this repository's history, along with the dependency-free package drills (background-jobs, operations, recovery, performance, mock-catalog, release-control, commercial-readiness, security). See [docs/FULL_VERIFICATION_REPORT.md](docs/FULL_VERIFICATION_REPORT.md) and [docs/ROADMAP_RECONCILIATION.md](docs/ROADMAP_RECONCILIATION.md) for command-by-command evidence. Live-provider verification (Typesense, a deployed preview, live browser QA) remains outstanding — see the reconciliation document for the current gate list. Production readiness is not proven.

SearchSignal is an applied information-retrieval framework for adapting structured ecommerce catalogs into grounded keyword, semantic, and hybrid product search. Sprint 16 adds a non-live `@searchsignal/background-jobs` reference contract for deterministic idempotency, leasing, bounded one-batch worker execution, provider abort/timeout handling, retries, checkpoints, immutable resume validation, safe events, tenant metrics, and reconciliation; it does not add a durable queue or live indexing.

Expand All @@ -12,7 +12,7 @@ Sprint 20 also includes `@searchsignal/mock-catalog`: a deterministic fictional

## Current status

Sprint 13's conditional source-intake layer now preflights exact allowlisted repository assets before the strict server-owned catalog configuration selects its adapter. `/console` exposes safe configuration and intake evidence only; it cannot edit configuration, upload a source, or run indexing. This remains non-live work: no deployment, real ecommerce connector, live index, authentication, or customer data ingestion has been completed. P-005, P-006C, and P-007D remain pending external environment.
The controlled catalog-intake layer (originally scoped as Sprint 13's replacement work; a real Shopify connector remains unimplemented backlog, see [docs/ROADMAP_RECONCILIATION.md](docs/ROADMAP_RECONCILIATION.md)) preflights exact allowlisted repository assets before the strict server-owned catalog configuration selects its adapter. `/console` exposes safe configuration and intake evidence only; it cannot edit configuration, upload a source, or run indexing. Merchant authentication and tenant isolation (Sprint 15) are implemented and unit/integration tested, but `pnpm smoke:merchant-isolation` requires live credentials and a running server not available in this environment. This remains non-live work: no deployment, real ecommerce connector, live search index, or customer data ingestion has been completed. P-005, P-006C, and P-007D (live Typesense keyword/semantic verification) remain pending an external Typesense environment.

## Stack direction

Expand Down Expand Up @@ -64,7 +64,7 @@ Repository Markdown is the source of truth. The external `awesome-vibe-coding` c

## Sprint 20 performance reference

`@searchsignal/performance` provides deterministic, bounded local measurements and safe evidence reports. `pnpm smoke:performance` uses synthetic inputs and existing reference APIs only; its capacity envelopes are environment-sensitive and never production claims. The T-022C catalog pipeline completion pass is blocked at the real connector's unavailable `zod` runtime dependency, so parser/normalization/readiness measurements and fixture regressions are `NOT RUN`; full runtime verification remains P-020V.
`@searchsignal/performance` provides deterministic, bounded local measurements and safe evidence reports. `pnpm smoke:performance` uses synthetic inputs and existing reference APIs only; its capacity envelopes are environment-sensitive and never production claims. As of R-001, dependencies install cleanly and the full catalog pipeline (parser, normalization, readiness) runs and passes its fixture drills — see `pnpm readiness:fixtures`, `pnpm normalization:fixtures`, `pnpm indexing:fixtures`. Full production-scale runtime and live-provider verification remains P-020V.

## Sprint 21 release control

Expand All @@ -74,11 +74,11 @@ Repository Markdown is the source of truth. The external `awesome-vibe-coding` c

`@searchsignal/commercial-readiness` validates a versioned commercial artifact, deterministic ICP scorecard, claim ledger, bounded packages/pricing hypotheses, pilot/onboarding scope, assumption-only value estimates, and a `pilot_ready` decision. It is not CRM, payment, sales automation, live onboarding, or production certification. See [commercial readiness](docs/COMMERCIAL_READINESS.md) and the concise [buyer brief](docs/SEARCHSIGNAL_BUYER_BRIEF.md).

T-024A live-usability target discovery found no documented deployed/preview target, no local listener, and no dependency-complete local web launcher. It captured no browser evidence and made no usability pass claim; `P-011E` remains required. See the [live usability QA report](docs/LIVE_USABILITY_QA_REPORT.md).
As of R-001, `pnpm dev` and `pnpm --filter @searchsignal/web build && pnpm --filter @searchsignal/web start` run successfully — the prior blocker (no dependency-complete local web launcher) is resolved. T-024A live-usability browser QA against a real target (local or preview) is tracked separately; `P-011E` is resolved only once fresh browser evidence exists. See the [live usability QA report](docs/LIVE_USABILITY_QA_REPORT.md).
## Controlled catalog intake API

## Security and privacy status

Sprint 18 adds a documented threat model, data classification/retention rules, canonical bounded log redaction, tenant-safe reference retention, configuration-only security readiness, deterministic security headers, and a synthetic `pnpm smoke:security` command. This is reference hardening, not a compliance, certification, production-readiness, or penetration-test claim. Runtime verification is deferred under P-018V while local test launchers are unavailable.
Sprint 18 adds a documented threat model, data classification/retention rules, canonical bounded log redaction, tenant-safe reference retention, configuration-only security readiness, deterministic security headers, and a synthetic `pnpm smoke:security` command. This is reference hardening, not a compliance, certification, production-readiness, or penetration-test claim. As of R-001, `pnpm smoke:security` runs and passes locally; full production-environment security verification remains P-018V.

An optional server-only controlled intake boundary is available at `/api/catalog-intake/*`. It is disabled by default, accepts only bounded authenticated CSV/JSON request bytes for immutable catalog bindings, and never persists or indexes submitted data. Its optional Sprint 15 merchant mode derives request-scoped tenant identity from hash-verified server credentials and hides cross-tenant catalogs. See [Controlled Catalog Intake API](docs/CONTROLLED_CATALOG_INTAKE_API.md) and [Merchant Authentication and Tenant Isolation](docs/MERCHANT_AUTHENTICATION_AND_TENANT_ISOLATION.md).
4 changes: 4 additions & 0 deletions SCRATCHPAD.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Current status

- R-001 repository recovery, full verification, CI, and preview enablement (2026-07-28): first task in this repository's history to run against a real, installed dependency tree on the GitHub-hosted clone. Root cause of every prior "launcher unavailable"/"registry egress restricted" entry below was environmental (isolated sandbox with no network egress and, separately, a Node-version mismatch: corepack's cached `pnpm@11.9.0` fails under Node 20.x with `ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`/missing `node:sqlite`; Node >=22 works and is now pinned in `.nvmrc` and `package.json` `engines`). `pnpm install --frozen-lockfile` failed on a real manifest/lockfile mismatch (`background-jobs`, `commercial-readiness`, `mock-catalog`, `operations`, `performance`, `release-control` existed on disk but were never captured in `pnpm-lock.yaml`); repaired with `pnpm install --no-frozen-lockfile` (additive only, no version bumps, documented in `docs/FULL_VERIFICATION_REPORT.md`). Root `package.json` devDependencies were also missing `@searchsignal/operations`/`@searchsignal/background-jobs`, which silently broke `smoke:operations`/`smoke:security`/`smoke:indexing-jobs` at the module-resolution level; fixed by adding them.
With a real runtime: workspace `lint`/`typecheck`/`test`/`build` all pass with zero repository-owned errors (previously never verified). Real, previously-invisible defects fixed: missing `tsconfig.json` in `release-control` and `commercial-readiness`; a TS2775 assertion-typing bug in `release-control`; a missing `cancelled` branch plus a `reasonCode` type-inference bug in `background-jobs`; `noUncheckedIndexedAccess`/readonly-array bugs in `recovery`'s tests (including one that silently mutated the wrong section because `createRecoverySnapshot` sorts sections alphabetically, not by insertion order); non-deterministic JSON key order in `operations`'s `createOperationalEvent`; a genuine runtime bug in `apps/web`'s catalog-intake handler — an accidental extra `()` invoked the timed operation immediately with `signal: undefined` instead of passing the function to the timeout wrapper; a circularly-referencing `SafeLogValue` type; a logger that emitted a spurious empty `details: {}` field; a missing `data/evaluation/benchmark-manifest.json` and a demo-search corpus import pointed at a file that never existed (repointed to the real, already-validated `data/search/keyword-products.json`); and five `scripts/smoke-*.ts` files whose "am I the entrypoint" guard compared `process.argv[1]` to the script path — which `vite-node` never sets, so `smoke:security`, `smoke:indexing-jobs`, `smoke:merchant-isolation`, `smoke:intake-api`, and `smoke:deployment` were **silently no-op'ing and reporting exit 0** every time they were "run" (fixed using `process.env.npm_lifecycle_event`, which `pnpm run` sets reliably; the latter three now correctly fail with clear errors since they need live credentials/a running server this environment doesn't have). apps/web had a declared `lint` script but no ESLint installed at all (`next lint` hung on interactive first-run setup); added `eslint`/`eslint-config-next` and fixed two real `<a>`-instead-of-`next/link` violations it found.
Corrected the landing page's stale "SPRINT 0"/"Search is not implemented yet" copy, added `/demo` and `/console` nav, and redirected the two stale placeholder routes instead of leaving false "Scheduled for Sprint N" copy live. Wrote `docs/ROADMAP_RECONCILIATION.md` confirming the audit's Sprint 11–14 drift hypothesis (persisted analytics, external SDK/widget, real Shopify connector, and webhook sync were never implemented — only supplemental hardening exists) and did not mark them complete. Added `.github/workflows/ci.yml`. Live-provider gates (P-005/P-006C/P-007D), P-011E, and the Vercel preview remain open — live Typesense is unavailable in this environment, and push/PR/deploy require explicit user confirmation per this task's safety rules. Full command-by-command evidence: `docs/FULL_VERIFICATION_REPORT.md` and `docs/RECOVERY_EVIDENCE_REPORT.md`.

- T-024B repository cleanup (2026-07-28): recorded deterministic baseline `tmp/cleanup-baseline.json`, the cleanup evidence ledger, canonical-source map, technical-debt report, static validator, and dependency-free drill. No generated junk, dead script, dead export, duplicate code, dependency, fixture, or lockfile change was proven safe; no removal occurred. Consolidated `scripts/README.md` and changed one public smoke description to avoid presenting a local URL. Direct Node tests (3), static validation, and the cleanup drill passed through `scripts/run-with-timeout.mjs`; lockfile SHA-256 remains `4951A642B5E20C559E39C5E9370C4886F24B99E467DD7750EE417B717C07765A`. Focused Vitest, package typecheck/test/build, web typecheck/test/build, and workspace typecheck/test/build are **NOT RUN** because launchers remain unavailable. `P-011E` remains required, T-024A remains blocked, and Sprint 23 remains not started.

- T-024A live usability QA and UX closure (2026-07-28): exact roadmap titles are **Sprint 22 — Commercial readiness** and **Sprint 23 — Public release and portfolio case study**. Target discovery found no public/preview deployment documented, no listener on ports 3000/3001/4173/5173/8080, and no dependency-complete `@searchsignal/web` workspace links for Next or Zod. No browser target was launched; personas, flows, screenshots, viewport checks, keyboard/zoom checks, console/network audit, first-time-comprehension test, and UX fixes are `NOT RUN`. No package install, dependency workaround, environment change, credential, production index, deployment, or Sprint 23 implementation occurred. Required task/report/accessibility/responsive/screenshot records document this as `Live usability QA: BLOCKED`; source review is supporting only; `P-011E` remains required and Sprint 23 is not ready from usability evidence.
Expand Down
3 changes: 3 additions & 0 deletions apps/web/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
3 changes: 0 additions & 3 deletions apps/web/app/catalog-readiness/page.tsx

This file was deleted.

3 changes: 2 additions & 1 deletion apps/web/app/console/_components/readiness-console.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import Link from "next/link";
import { useEffect, useMemo, useState } from "react";

import { requestDemoCatalogReadiness, requestDemoCatalogs } from "../../../lib/demo-readiness/client";
Expand Down Expand Up @@ -71,7 +72,7 @@ export function ReadinessConsole() {
}).sort((left, right) => `${left.externalId ?? ""}\u0000${left.sourceLocator}`.localeCompare(`${right.externalId ?? ""}\u0000${right.sourceLocator}`)), [issueFilter, report, textFilter]);

return <main className="readiness-console">
<header className="console-header"><a className="brand" href="/">SearchSignal</a><span className="demo-badge">Synthetic demo</span><p className="eyebrow">Read-only diagnostic</p><h1>Catalog Readiness Console</h1><p className="lede">Inspect how fixed synthetic catalogs move through mapping, validation, explicit normalization, readiness, eligibility, and indexing admission.</p></header>
<header className="console-header"><Link className="brand" href="/">SearchSignal</Link><span className="demo-badge">Synthetic demo</span><p className="eyebrow">Read-only diagnostic</p><h1>Catalog Readiness Console</h1><p className="lede">Inspect how fixed synthetic catalogs move through mapping, validation, explicit normalization, readiness, eligibility, and indexing admission.</p></header>
<section className="console-selector" aria-label="Synthetic catalog selector"><label htmlFor="catalog-selector">Synthetic catalog</label><select id="catalog-selector" value={catalogId} onChange={(event) => setCatalogId(event.target.value as DemoCatalogId)}>{catalogs.map((catalog) => <option key={catalog.id} value={catalog.id}>{catalog.label}</option>)}</select><div className="catalog-cards">{catalogs.map((catalog) => <button key={catalog.id} type="button" className={catalog.id === catalogId ? "selected" : ""} onClick={() => setCatalogId(catalog.id)}><strong>{catalog.label}</strong><span>{catalog.scenario}</span></button>)}</div></section>
<p className="sr-only" aria-live="polite">{state.status === "loading" ? "Loading catalog readiness report" : report ? `${report.demo.catalogLabel} readiness report loaded` : ""}</p>
{state.status === "loading" && <p className="notice" role="status">Loading synthetic readiness evidence…</p>}
Expand Down
5 changes: 3 additions & 2 deletions apps/web/app/demo/_components/demo-storefront.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import Link from "next/link";
import { useEffect, useMemo, useState } from "react";

import { requestDemoSearch } from "../../../lib/demo-search/client";
Expand Down Expand Up @@ -161,9 +162,9 @@ export function DemoStorefront() {
return (
<main className="storefront">
<header className="storefront-header">
<a className="brand" href="/">
<Link className="brand" href="/">
SearchSignal
</a>
</Link>
<p>Grounded product retrieval interface</p>
<span className="demo-badge">Synthetic demo</span>
</header>
Expand Down
Loading
Loading