Skip to content

fix(search): publish lexical route corrections (successor to #183) - #194

Merged
waterbro-8 merged 1 commit into
mainfrom
codex/fix-pr-183
Sep 17, 2026
Merged

waterbro-8 merged 1 commit into
mainfrom
codex/fix-pr-183

Conversation

@PeterGuy326

@PeterGuy326 PeterGuy326 commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Linked draft successor — original #183 remains open

Refs #176.

Current follow-up: c0421168bd145077bf164f91c0d2b454a79760ef

Preserves @sun-970 / liyuanyang's complete authored chain through 13ebe9efa6ba3c733199d374e8db3d107f598ca2 for #176. This draft makes the bounded reviewed corrections accessible; it does not replace independent review, CI, or human approval. Do not close #183 before a replacement is verified and merged.

The original fork's Git-data write returned HTTP 404 and its repository permissions report push:false. No ACL override was attempted. This canonical branch preserves original commit identities and hashes. Added blobs, trees, and commits were checked against local Git hashes; no force update was used.

Corrections

Validation at head 3d885a8427e06fd7175011ef0188a06f32028a3f

  • PASS: migration to 24 on PostgreSQL 16.14 / pgvector 0.8.2 and PostgreSQL 17.10 / pgvector 0.8.3.
  • PASS: TestLexicalSearchWithoutWorker, five cases: lexical without worker, text/auto fail closed, CJK trigram, path restriction. Go tests cross-compiled for Linux and executed against real disposable PostgreSQL with GOMAXPROCS=2 and serial package builds.
  • PASS: audited lockfile check; shared fix from chore(web): refresh audited development dependencies #192 remains explicit, not attributed to this feature.
  • NOT CLAIMED: provider-backed vector retrieval or production benchmark quality.
  • REQUIRED: fresh CI on this exact head and independent review. Original-head CI/review is not approval of this draft.

Original feature scope: model-free file-corpus lexical route, migration 0024, API/CLI/docs, managed-provider bypass. Scope excludes tokenizer, generation lifecycle, and ranking redesign.

@PeterGuy326

Copy link
Copy Markdown
Collaborator Author

Independent automated preflight: HOLD at 3d885a8427e06fd7175011ef0188a06f32028a3f.

The MCP mem_search route enum in server/internal/tools/builtin/builtin.go still lists only text/visual/auto; tools/list publishes it unchanged. Schema-conforming clients cannot select the newly documented lexical route. An independent source-schema assertion reproduced the omission; this is not a claim that an unchecked direct invocation fails. Add lexical to the contract and cover both the exported schema and forwarding path.

The five PostgreSQL lexical cases and latest 20 successful CI checks do not cover this interface gap. Correcting the migration-number expectation alone is insufficient. Implementation follow-up has been assigned; no approval or merge readiness is claimed. Migration ordering is also being coordinated with #195/#197 without weakening Goose or renumbering deployed migrations.

@PeterGuy326

Copy link
Copy Markdown
Collaborator Author

Independent automated preflight — bounded code PASS, candidate c0421168bd145077bf164f91c0d2b454a79760ef.

The earlier MCP blocker is fixed: tools/list advertises lexical, and the new JSON-RPC list/call regression verifies schema plus route/filter forwarding through the real MCP server and HTTP client to a loopback fixture. The coordinator inspected this delta; independent reviewer Mendel found no remaining source blocker. Worker evidence separately includes all nine MCP tests, five real PostgreSQL lexical cases and the populated strict schema 23→24 upgrade. This is not a live embedding-provider benchmark.

The final docs-only correction from 2f2e965 changes the test command to verify.sh integration; server code/tests/migrations and scripts are byte-identical. Independent compile-only, shell syntax and diff checks passed without running temporary native macOS Go binaries. Latest exact-head CI and acceptance now pass all 20 current checks.

Migration numbers and SQL identities are retained. Cumulative branches enforce #194 (24) → #197 (25) → #195 (26), with a gap guard and real sequential-upgrade tests; a pre-existing 23+26 database is rejected, not silently repaired or granted AllowMissing.

Product/review gate remains open: #176 is still an RFC with status:needs-triage, asking whether the model-free lane is intended. Technical preflight and CI do not accept that RFC, authorize deployment or satisfy human approval. This candidate remains draft pending that decision and review. #197's query-strategy acceptance and #195's dependency HOLD are outside this verdict.

@sun-970

sun-970 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overall the design is solid — the three-tier scoring (exact → FTS → trigram) mirrors the existing memory Recall shape, migration safety is well-considered, and test coverage is thorough (especially the sequential upgrade regression). No blocking bugs found. Below are specific findings and suggested fixes.


1. candidates CTE doesn't leverage the trigram index (search.gosearchLexical)

Problem: The candidates CTE computes word_similarity and strpos over all of the user's files before the ranked CTE applies the score threshold. The idx_files_name_trgm GIN index created in migration 0024 won't help here because no LIKE/%/ILIKE predicate drives the index scan.

Fix: Add a trigram pre-filter in the candidates WHERE clause so the GIN index can prune the scan:

WHERE f.user_id = $1
  AND (strpos(lower(f.name), lower($N)) > 0
       OR f.search_tsv @@ plainto_tsquery('simple', $N)
       OR lower(f.name) % lower($N))  -- trigram index can kick in

For a personal KB this is probably fine today, but it matters as the file corpus grows.


2. Empty query not guarded (search.gosearchLexical)

Problem: searchLexical doesn't check for an empty text parameter. plainto_tsquery('simple', '') returns an empty tsquery, and strpos/word_similarity with an empty string produce meaningless results.

Fix:

if strings.TrimSpace(text) == "" {
    return nil, nil
}

The API layer may already validate this, but defense-in-depth here is cheap.


3. lower(f.name) computed redundantly (search.gosearchLexical CTE)

Problem: lower(f.name) appears 4 times in the CTE. PostgreSQL may common-subexpression-eliminate some, but it's not guaranteed for lower() inside word_similarity and strpos.

Fix: Minor — consider a subquery or a lower_name generated column if this becomes a hotspot. Not blocking.


4. auto route silently excludes lexical — worth documenting

Problem: route=auto still only fuses text + visual. This is intentional (lexical is a worker-free fallback), but users may expect auto to "try everything available."

Fix: Add a one-liner in SPEC.md or the MCP tool description:

route=auto fuses text and visual embedding routes; it does not automatically fall back to lexical. Use route=lexical explicitly when no worker is available.


5. Migration IF NOT EXISTS + Goose interaction

Problem: The migration uses ADD COLUMN IF NOT EXISTS / CREATE INDEX IF NOT EXISTS. If a prior failed deployment partially applied 0024, Goose still records it as "applied" even if some statements were no-ops. TestMigrationUpgradeSequence covers the happy path, but operators should know about this edge case.

Fix: Document this in docs/MIGRATION_SEQUENCE.md — note that partial-application recovery requires manual inspection of goose_db_version and the actual schema state.


6. Import path mismatch (lexical_test.go:10)

Problem: The test imports github.com/PeterGuy326/mem/server/internal/db — should this be the upstream canonical path (e.g. github.com/bytefolk/mem/...)?

Fix: Align the import path with the repository's canonical module path.


7. CHANGELOG placement

Problem: The MCP schema advertisement fix is listed under "Fixed" in the Unreleased section, but it's part of the new lexical feature — semantically it belongs under "Added."

Fix: Move the MCP-related changelog entry into the "Added" block alongside the lexical route description.


8. PR description

Problem: The PR body contains extensive meta-commentary (fork permissions, commit preservation, validation history) that reads more like an internal engineering log. This makes it harder for reviewers to quickly understand what changed and why.

Fix: Consider moving the "Validation at head", "Corrections", and fork-permission sections to a linked issue or internal doc. Keep the PR body focused on: what changed, why, and how to verify.


Non-blocking nit

In search.go:222, the worker check changed from s.worker == nil to q.Route != RouteLexical. The logic is correct but reads slightly awkwardly. Consider extracting a small helper:

func needsWorker(route string) bool {
    return route != RouteLexical
}

Summary: Well-designed feature with solid test coverage and careful migration safety. Main actionable items are (1) add trigram pre-filter to leverage the new index, (2) guard against empty queries, and (3) align the test import path. Nothing blocking.

@waterbro-8 waterbro-8 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code review(draft,给作者的清单,非批准)

一、这份 PR 最有价值的东西,是它把"栈"这个风险自己写下来了

我独立排查时担心的那件事——三条 PR 各自带一个迁移(0024/0025/0026),而 #195/#197 的 base 又是另一个 PR 的分支,部署顺序一旦错乱就会出现"跳过 24 直接到 26"——docs/MIGRATION_SEQUENCE.md 已经把它当成一等公民写清楚了

  • 明确三条是累积的、不可独立部署的,并给出顺序表与前驱关系;
  • 明确写出"不要因为假设没人用过草稿就重排迁移号"——它甚至论证了为什么 main 只有 0001–0023 不能证明私有部署没应用过;
  • 明确写了 Goose 保持严格(没有 WithAllowMissing),已经应用到 26 却缺 24/25 的库应当正确地在启动时失败,并且"不要改它的迁移历史、不要重排、不要倒序应用来凑一个通过";
  • 还明确声明这份文档本身不批准查询策略变更、不豁免评审闸门、不授权部署。

配套 TestMigrationUpgradeSequence 也不是摆设:不开数据库就能拒绝编号缺口(TestMigrationFilesContiguous)、拒绝已有 Goose 历史的库、强制 _test 库名、从 23 一级一级升到分支声明的 head、每级都校验"应用历史完整 + 数据保留",在 ≥24 校验词法回填、≥25 校验三个 HNSW 索引有效、≥26 校验去重与唯一约束拒绝,最后还要让生产启动路径 DB.Migrate 原样接受升级后的历史。

这正是这条链需要的测试。它把"栈"从一个口头约定变成了 CI 里会失败的断言。

二、路由接入是完整的,不是只加了枚举

我把每个入口都核了一遍:search.go 新增 RouteLexical 常量与 searchLexical 实现;api.gobad_route 白名单与错误文案同步;managed_embeddings.go 让 lexical 与 visual 一样不触发托管文本 embedding 提供器(这是"无模型"的关键,否则省了 worker 却去调 provider 就自相矛盾);CLI 的 --route 帮助文案;MCP mem_searchenum;SPEC 与 docs/mcp.md 也都改了。

MCP 那条测试也是真的在验证转发,而不只是读 schema 文本:它用 httptest 接住 /v1/search,断言 route/scope/type/limit 都被原样转发,并且断言 tools/listmem_search 的 enum 恰好是 4 个值(少一个就失败)。

三、必须改的一条:0024 的注释声称覆盖 path,实际只覆盖 name

server/internal/db/migrations/0024_files_lexical_search.sql 的注释写:

Mirrors the FTS + trigram shape already established for memories (0008) so that filename and path substring search works without an embedding worker.

但生成的列和索引都只覆盖 name

ADD COLUMN IF NOT EXISTS search_tsv tsvector GENERATED ALWAYS AS (
    to_tsvector('simple', coalesce(name, ''))   -- 只有 name
) STORED;
CREATE INDEX ... ON files USING gin (lower(name) gin_trgm_ops);  -- 还是只有 name

searchLexical 里也确实只有 strpos(lower(f.name), ...)f.search_tsv @@ ...word_similarity(..., lower(f.name))——没有任何一处碰 f.path。所以按路径片段检索在 lexical 路由下是搜不到的。

同一份 PR 里 CHANGELOG 写的是 "FTS + trigram over files.name"、SPEC 写的是"文件名无模型词法召回"——这两处是对的,只有迁移注释说多了。二选一:把注释改成只提 name(推荐,改动最小且与既有说法一致),或者真的把 path 纳入(那就是功能扩张,需要单独讨论,PathPrefix 过滤跟"检索 path"是两回事,别混)。

四、非阻塞,但请知悉的两点

  • 这次迁移在生产库上不是无感的。 ALTER TABLE files ADD COLUMN ... GENERATED ALWAYS AS (...) STORED 会让 PostgreSQL 重写整张 files 表并为每一行计算 tsvector,两个 CREATE INDEX 也都不是 CONCURRENTLY。也就是说迁移期间 files 上会有一段排他锁。个人盘规模可以接受,但建议在 docs/MIGRATION_SEQUENCE.md 或 DEPLOYMENT 里写一句预期窗口,让自建部署的人有心理准备。
  • exact_phrase 这个名字其实测的是子串strpos(lower(f.name), lower($1)) > 0),不是 tsvector 意义上的短语匹配。它作为第一档打分是合理的,只是名字容易误导,建议改成 name_contains 之类,或加一句注释。
  • trigram 阈值 >= 0.12 相当宽松,word_similarity 在这个量级会放进不少弱相关结果。这是召回取向的取舍,我不反对;只是如果后面有人抱怨"搜什么都出来",第一个该调的就是它。

五、我没有声称验证的事

本机没有 PostgreSQL + pgvector,我没有跑过 TestMigrationUpgradeSequenceTestLexicalSearchWithoutWorker。以上是逐行读代码、迁移与测试得出的结论,不是运行时观测。

(本次为独立评审:我不是本 PR 作者,也不是其 head 的推送者。draft 状态下我不投批准票;第三节那条改完后我再来复评。)

sun-970 pushed a commit to sun-970/mem that referenced this pull request Sep 17, 2026
…ytefolk#197)

Three comments referenced "migration 0024" for the HNSW index, but 0024
is the lexical search migration from bytefolk#194. The HNSW indexes live in 0025.
Fix the number in 0001_init.sql, 0019_versioned_index_generations.sql and
face.go.
sun-970 pushed a commit to sun-970/mem that referenced this pull request Sep 17, 2026
…ly (bytefolk#194)

The migration comment claimed "filename and path substring search" but
search_tsv and the trigram index only cover name. No code in the repo
touches f.path for lexical search. Fix the comment to match reality.
@PeterGuy326

Copy link
Copy Markdown
Collaborator Author

已修复:migration 注释 filename and pathfilename (name column)

感谢 @waterbro-8 的 review。

问题确认0024_files_lexical_search.sql 的注释写了 "filename and path substring search",但 migration 内两个索引(idx_files_search_tsvtsvectoridx_files_name_trgm 的 trigram)均只覆盖 files.name 列,未碰 path。按路径片段检索在 lexical 路由下确实搜不到。

本次改动(commit db452f6):

```diff

  • -- shape already established for memories (0008) so that filename and path
  • -- substring search works without an embedding worker.
  • -- shape already established for memories (0008) so that filename (name column)
  • -- substring search works without an embedding worker.
    ```

仅改注释,不涉及 schema、索引或查询逻辑。将 path 纳入索引属于功能扩展,单独立 issue 讨论。

非阻塞备注(review 中提到,不影响本 PR 合入):

  • 0001/0019 中的注释改动已确认安全;
  • 测试函数改名建议已记录,后续跟进。

请重新 review,确认注释描述已与实际索引覆盖一致。谢谢 🙏

@PeterGuy326 PeterGuy326 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Current-head follow-up review at db452f6:

The previous blocking documentation mismatch is resolved: migration 0024 now accurately states that lexical substring search covers the filename/name column, not path. I rechecked the route integration and ran the affected Go packages locally; all passed.

I am not approving or recommending merge yet because the PR remains Draft, is currently DIRTY against main, and has no fresh exact-head checks. Please resolve the base conflict/update, run exact-head CI (including the populated migration sequence test), then request final review. Non-blocking notes remain: document the migration lock/window, and consider renaming exact_phrase to name_contains.

@PeterGuy326
PeterGuy326 marked this pull request as ready for review September 17, 2026 09:22
@sun-970

sun-970 commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Review Summary

Status: LGTM

Reviewed the lexical search corrections:

  • Migration 0024 adds search_tsv tsvector column and GIN indexes for FTS + trigram search
  • searchLexical() implements model-free file search using name substring → FTS → trigram three-tier scoring
  • Managed embedding bypass is correctly implemented for lexical route
  • MCP tool enum updated to include lexical
  • All CI checks passing

The implementation correctly handles:

  • Path/MIME/time filters via parameterized queries (no SQL injection risk)
  • Worker-less operation (lexical route doesn't require embedding worker)
  • Score normalization across the three matching tiers

No issues found. Ready to merge once #197's test fix is resolved.

@waterbro-8 waterbro-8 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed as the #176 lexical-lane implementation.

What holds:

  • Migration 0024 mirrors memories FTS+trigram on files.name only (filename lane, not extracted body text). That is a conservative, model-free degrade path.
  • MCP tools/list grows a lexical route with tests.
  • Sequential-upgrade guard is documented; deployment order is recorded as #194#197#195.

I am not treating this as a retrieval-quality claim. Filename FTS is enough to answer “is a model-free file lane intended?” with “yes, for names.” Merge this before #197 so migration numbers stay ordered.

@waterbro-8
waterbro-8 merged commit 6543983 into main Sep 17, 2026
22 checks passed
@waterbro-8
waterbro-8 deleted the codex/fix-pr-183 branch September 17, 2026 16:10
waterbro-8 pushed a commit that referenced this pull request Sep 17, 2026
) (#196)

## Linked draft successor — original #184 remains open

Refs #175.

Preserves @sun-970 / liyuanyang's authored chain through
`89bb12bdf2ae5edbdfa17260953ec6352ab9239f` for #175. Do not close #184
before a replacement is independently verified and merged. This draft
remains HOLD for live acceptance.

Source-fork Git-data writes returned HTTP 404, with repository
`push:false`; no ACL override was attempted. The canonical branch
preserves original commit hashes and identities. Added blobs, trees, and
commits were individually hash-verified; no force update was used.

## Bounded corrections

- Fail closed on ambiguous cross-workspace mappings, unknown result
paths, malformed responses, non-finite scores, and failed requests;
retain an error artifact and exit 2.
- Map the shipping folder `path` plus file `name`; do not silently
discard unknown hits or infer tenant identity from snippets.
- Vector mode sends `route=text`; lexical mode sends `route=lexical`
with null provider/model/dimension metadata. Do not claim `auto` is
lexical/vector hybrid.
- Reject structured-memory queries this file-search endpoint cannot
serve. Document the existing file-only `profile-text-v1` fixture as the
bounded corpus.
- Remove hostname collection and invented provider/index identity.
Configuration labels remain explicitly operator-declared, not
server-verified.
- Carry #192 audit remediation as a separate `cherry-pick -x` of
`11e02e21ef2c3dbd2dae26e4376872e54e78ecb5`; audit threshold unchanged.

## Validation at `651bec1679c50a9cd07cf77b27b5556c20e3273e`

- PASS: Python 3.11.14, `python3.11 -m unittest discover -s
benchmarks/recall/tests`: 39 tests, including the reproduced fail-closed
regressions and a loopback HTTP fixture.
- PASS: `python3.11 -m benchmarks.recall verify`: deterministic harness
and intentional leakage failure gate.
- PASS: Web audit with the explicit shared fix.
- NOT VERIFIED: real memd retrieval, actual embedding-provider quality,
real index selection, production latency, or full structured-memory
corpus acceptance. The HTTP handler is a fixture, not memd; its timing
is not live benchmark evidence.

## Exact remaining live prerequisites (no external provider authorized)

1. An isolated, authorized test deployment of real memd and its Worker,
with PostgreSQL/pgvector and ingest dependencies configured, plus a
token verified to belong to the test workspace. No existing user
deployment or provider credentials have been used.
2. Ingest all five synthetic files from
`benchmarks/recall/data/profile-text-v1/corpus.jsonl`, preserving their
full paths and contents, into that workspace. The producer is not an
ingestor. Confirm indexing completed and file/result identities match
the fixture.
3. For the bounded fixed-text experiment, use the same locally
available, explicitly selected 768-dimensional text embedding model for
corpus and query. Verify the corpus/provider metadata and which active
generation or fixed table the server actually uses. A label passed to
the producer proves none of these facts. No paid provider, external
endpoint, model download, or provider configuration was enabled by this
correction.
4. Execute all four file queries through real `/v1/search` with `--mode
vector`, retain sanitized rankings, then score with `run --rankings` and
record the exact memd head, actual model/dimension/index, environment,
and errors. An empty/error run does not satisfy live acceptance.
5. Model-free lexical is a separate optional real-server experiment
requiring #183's server capability (draft successor #194); it cannot
establish vector quality. Full v1 structured-memory acceptance remains
unsupported by this producer and must not be reported as passed.

Fresh exact-head CI and independent review/human approval remain
separate required gates. No fake or paid live run is substituted for the
missing evidence.
waterbro-8 added a commit that referenced this pull request Sep 18, 2026
## Summary

Completes the work [#213](#213) left
undone against [#173](#173).

- Migration **0025** (0024 is already lexical from #194) adds cosine
HNSW indexes on `embeddings_text` (768), `embeddings_visual` (512), and
`embeddings_face` (512) with `vector_cosine_ops`.
- Shipping **text** search no longer uses `DISTINCT ON (f.id) ORDER BY
f.id, distance` as the primary plan. That shape cannot use HNSW. It now
walks `ORDER BY embedding <=> $1 LIMIT n`, keeps the first sighting of
each file, excludes selected files, and **falls back** to the original
exact `DISTINCT ON` query if a bounded scan underfills (one file owning
many near chunks).
- Relator text neighbors use the same continuation/fallback.
- Visual already matched HNSW. Face clustering stays in-process; the
face index is DDL only.
- `index_generation_vectors` is not indexed (scope boundary).
- Transactional `CREATE INDEX` (not `CONCURRENTLY`): a failed concurrent
build leaves an `INVALID` index that `IF NOT EXISTS` will skip.
- Wrong dimensions fail at the `vector(N)` column. Recall is **not**
claimed; harness is [#175](#175).

Does not reopen #213. #197 remains the earlier HOLD attempt; this branch
is based on current `main` (schema 24) and takes 0025.

## Changes

- `server/internal/db/migrations/0025_ann_hnsw_indexes.sql`
- `server/internal/search/search.go` — text continuation + exact
fallback
- `server/internal/relator/relator.go` — same policy
- `TestHNSWMigrationPostgres` — populated 24→25→24→25, ingest, dimension
rejection, EXPLAIN
- `TestTextANNFileSemanticsPostgres` — 101-chunk file still returns 10
eligible files
- `scripts/verify_hnsw_indexes.sh` + `scripts/verify.sh` head 25
- `docs/VALIDATION_HNSW.md`, SPEC, CHANGELOG

## Validation ledger

| ID | Criterion | Command | Status |
| --- | --- | --- | --- |
| V1 | Server builds, unit contracts | `make test-server` | Pending CI
(sandbox has Go 1.22; module requires 1.25) |
| V2 | Worker | `make test-worker` | Not affected |
| V3 | Web | `make test-web` | Not affected |
| V4 | Race | `make test-race` | Pending CI |
| V5 | Fresh schema, rollback, PostgreSQL | `make test-integration` |
Pending CI — `EXPECTED_MIGRATION_HEAD=25`; `TestHNSWMigrationPostgres` +
`verify_hnsw_indexes.sh` |
| V6 | DB race | `make test-integration-race` | Pending CI |
| V7–V9 | Acceptance / MCP / visual quality | — | Not affected |
| V10 | Recall | `make test-recall` | Not measured. Recorded harness:
#175 |
| Text semantics | 101-chunk file still yields k files |
`TestTextANNFileSemanticsPostgres` | Pending CI |
| Planner | EXPLAIN uses HNSW for text cosine-order and visual |
`TestHNSWMigrationPostgres` + `scripts/verify_hnsw_indexes.sh` | Pending
CI. No `enable_seqscan=off`. |

Local sandbox could not run Docker or download Go 1.25, so EXPLAIN was
not executed here. CI `memory-validation.yml` / `verify.sh integration`
is the evidence path.

## Design notes

- Defaults `m=16`, `ef_construction=64`. No iterative-scan GUC.
- Empty `uuid[]` exclude lists are never sent as SQL NULL (`ANY(NULL)`
would drop all rows).
- Editing 0001/0019 is comment-only. Goose does not checksum like
Flyway.

Fixes #173

---------

Co-authored-by: waterbro-8 <318569545+waterbro-8@users.noreply.github.com>
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.

3 participants