Ship Matrix-intake CRM endpoints to the box (v0.1.0:84)
The intake bot's server-side dependencies — GET /api/intake/match (new-vs-
existing lookup) and `source` provenance on log-communication — shipped in
source with 7ad0ee7 but were never packaged. The box ran v83 (pre-7ad0ee7),
so the bot's match calls 404'd: a note on an existing investor would have
created a duplicate, and writes weren't tagged matrix_intake. Bump + build +
install verified live (installed-version 0.1.0:84, clean 83->84 migration,
match endpoint now resolves by name and email). No schema change.
Also log the conversational (LLM-mediated) edit enhancement in ROADMAP.
This commit is contained in:
@@ -102,6 +102,8 @@ Use the **matrix-bridge** repo's pattern to listen on a dedicated ten31-database
|
||||
|
||||
**Post-deploy enhancement — fuzzy match + in-thread confirm (Grant, 2026-06-17).** Today `find_intake_match` is **exact-after-normalization** (`_normalize_text` = lowercase+strip), so near-misses — "Charlie" vs "Charles" (same last name), "Acme Capital" vs "Acme Capital LLC", a one-character email typo — return no match and the bot proposes a **new** investor, risking a duplicate the human approves without realizing a near-match exists. The existing in-thread approval gate is useless against this because the human is never *shown* the near-match. Fix: matcher returns **ranked fuzzy candidates** (deterministic pre-filter: normalized name similarity / token overlap + email edit-distance ≤ ~2), surfaced in-thread for the human to confirm or pick, with the **local Spark LLM optionally re-ranking/judging the shortlist** (good at Charlie/Charles + legal-suffix equivalence; fed only the shortlist, never the whole LP list). Keeps the approval gate but makes it effective against duplicates. Land **after** the live smoke — net-new logic + reply grammar + tests; the current exact match is safe and its failure mode (a duplicate) is recoverable via the existing entity-merge subsystem (`backend/entity_*.py`).
|
||||
|
||||
**Post-deploy enhancement — conversational (LLM-mediated) edits (Grant, 2026-06-17).** Today an in-thread correction uses a rigid grammar (`edit field=value`). Let a free-form reply that isn't `yes`/`no`/a literal `edit …` be treated as a natural-language revision instruction: send {current proposal + the instruction} back through local Qwen (`spark.py`, the same parse leg — no Claude, no scrub) and re-render the revised proposal card for approval (e.g. "add that we met on June 14" → updated Note). Keeps the draft→human-approve gate (the human still confirms the LLM's revision) and subsumes `edit field=value` as a deterministic fast path. Thread the instruction text into `normalize`'s source so the email-integrity rule still holds (a revised email must appear in the original message or the instruction). Pairs naturally with the fuzzy-match item above — build both as one conversational-UX pass after the smoke. (Parsing of free-form *intake* messages already works today via the Qwen parse leg; this item is specifically about the *edit/refine* turn.)
|
||||
|
||||
### Scoped service-credential auth path for automated CRM writers
|
||||
*Surfaced 2026-06-17 while deploying the Matrix intake bot. **Decision: defer — the bot uses a dedicated member username/password for now.** The CRM has no API-key/service-token path; its only auth is username+password → JWT. A dedicated **member** login is appropriately scoped against what matters operationally (no admin: can't manage users, reset data, or change settings) and unblocks the live smoke today.*
|
||||
|
||||
|
||||
@@ -48,8 +48,9 @@ export const PACKAGE_TITLE = 'Ten31 Database'
|
||||
// * 0.1.0:80 (repurpose Communications tab as the admin-only email-activity panel: new GET /api/email/activity [admin-enforced] over the email_* tables, filterable by investor/mailbox/direction + free-text search; classic manual log form retired; code-only, no schema change)
|
||||
// * 0.1.0:81 (Communications tab is matched-only: query_email_activity gates on EXISTS email_investor_links, so unmatched cold/unknown-sender email is captured but never surfaced in the panel; code-only, no schema change)
|
||||
// * 0.1.0:82 (vendor + SRI-pin the front-end libs: React/ReactDOM/Babel now ship in the s9pk and load same-origin from /assets/vendor/ with integrity hashes, so a CDN can never swap prod deps [the v78/v79 blank-screen class] and the box needs no outbound internet to render; plus a committed jsdom render smoke check [start9/0.4/render-smoke.mjs] gating the default `make` build)
|
||||
// * Current: 0.1.0:83 (email search/query + windowed digest preview, code-only: Communications investor dropdown now mirrors the list with typed keys [fund:/org:/contact:] so classic-contact/org-domain matches show + are pickable [fixes the empty-dropdown bug], plus a date-range filter, a click-to-expand full-body view [GET /api/email/detail], and a semantic "Search content" mode over indexed email bodies [GET /api/email/search -> ingest hybrid_search, soft-delete-filtered, 503 if Spark/Qdrant down]; Daily Digest gains an in-app windowed preview before send [POST /api/admin/digest/preview, send-now takes the same window] that exercises the real Spark summarizer without touching the daily cursor)
|
||||
export const PACKAGE_VERSION = '0.1.0:83'
|
||||
// * 0.1.0:83 (email search/query + windowed digest preview, code-only: Communications investor dropdown now mirrors the list with typed keys [fund:/org:/contact:] so classic-contact/org-domain matches show + are pickable [fixes the empty-dropdown bug], plus a date-range filter, a click-to-expand full-body view [GET /api/email/detail], and a semantic "Search content" mode over indexed email bodies [GET /api/email/search -> ingest hybrid_search, soft-delete-filtered, 503 if Spark/Qdrant down]; Daily Digest gains an in-app windowed preview before send [POST /api/admin/digest/preview, send-now takes the same window] that exercises the real Spark summarizer without touching the daily cursor)
|
||||
// * Current: 0.1.0:84 (Matrix intake bot CRM support — ships the server side of commit 7ad0ee7, which was never packaged: new read-only GET /api/intake/match [new-vs-existing lookup against the canonical fundraising grid blob; returns the grid row id so an approved note lands on the matched investor, no duplicate] + source provenance on POST /api/fundraising/log-communication [audit records source, default "fundraising_grid"]; code-only, no schema change)
|
||||
export const PACKAGE_VERSION = '0.1.0:84'
|
||||
|
||||
export const DATA_MOUNT_PATH = '/data'
|
||||
export const WEB_PORT = 8080
|
||||
|
||||
@@ -44,8 +44,9 @@ import { v_0_1_0_80 } from './v0.1.0.80'
|
||||
import { v_0_1_0_81 } from './v0.1.0.81'
|
||||
import { v_0_1_0_82 } from './v0.1.0.82'
|
||||
import { v_0_1_0_83 } from './v0.1.0.83'
|
||||
import { v_0_1_0_84 } from './v0.1.0.84'
|
||||
|
||||
export const versionGraph = VersionGraph.of({
|
||||
current: v_0_1_0_83,
|
||||
other: [v_0_1_0_39, v_0_1_0_40, v_0_1_0_41, v_0_1_0_42, v_0_1_0_43, v_0_1_0_44, v_0_1_0_45, v_0_1_0_46, v_0_1_0_47, v_0_1_0_48, v_0_1_0_49, v_0_1_0_50, v_0_1_0_51, v_0_1_0_52, v_0_1_0_53, v_0_1_0_54, v_0_1_0_55, v_0_1_0_56, v_0_1_0_57, v_0_1_0_58, v_0_1_0_59, v_0_1_0_60, v_0_1_0_61, v_0_1_0_62, v_0_1_0_63, v_0_1_0_64, v_0_1_0_65, v_0_1_0_66, v_0_1_0_67, v_0_1_0_68, v_0_1_0_69, v_0_1_0_70, v_0_1_0_71, v_0_1_0_72, v_0_1_0_73, v_0_1_0_74, v_0_1_0_75, v_0_1_0_76, v_0_1_0_77, v_0_1_0_78, v_0_1_0_79, v_0_1_0_80, v_0_1_0_81, v_0_1_0_82],
|
||||
current: v_0_1_0_84,
|
||||
other: [v_0_1_0_39, v_0_1_0_40, v_0_1_0_41, v_0_1_0_42, v_0_1_0_43, v_0_1_0_44, v_0_1_0_45, v_0_1_0_46, v_0_1_0_47, v_0_1_0_48, v_0_1_0_49, v_0_1_0_50, v_0_1_0_51, v_0_1_0_52, v_0_1_0_53, v_0_1_0_54, v_0_1_0_55, v_0_1_0_56, v_0_1_0_57, v_0_1_0_58, v_0_1_0_59, v_0_1_0_60, v_0_1_0_61, v_0_1_0_62, v_0_1_0_63, v_0_1_0_64, v_0_1_0_65, v_0_1_0_66, v_0_1_0_67, v_0_1_0_68, v_0_1_0_69, v_0_1_0_70, v_0_1_0_71, v_0_1_0_72, v_0_1_0_73, v_0_1_0_74, v_0_1_0_75, v_0_1_0_76, v_0_1_0_77, v_0_1_0_78, v_0_1_0_79, v_0_1_0_80, v_0_1_0_81, v_0_1_0_82, v_0_1_0_83],
|
||||
})
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { VersionInfo } from '@start9labs/start-sdk'
|
||||
|
||||
// Matrix intake bot — CRM server-side support. Code-only, no schema change (migrations no-ops).
|
||||
// These shipped in source with the intake bot (commit 7ad0ee7) but were never packaged; the bot
|
||||
// (a separate process on the Spark) depends on them being live on the box:
|
||||
// * GET /api/intake/match — read-only new-vs-existing lookup against the canonical fundraising
|
||||
// grid blob; returns the grid row id so an approved note lands on the matched investor
|
||||
// (no duplicate). The bot calls this to label its in-thread proposal new-vs-existing.
|
||||
// * source provenance on POST /api/fundraising/log-communication — the audit entry now records
|
||||
// a `source` (e.g. "matrix_intake"); defaults to "fundraising_grid", so existing callers
|
||||
// (the grid UI) are unchanged.
|
||||
export const v_0_1_0_84 = VersionInfo.of({
|
||||
version: '0.1.0:84',
|
||||
releaseNotes: {
|
||||
en_US: [
|
||||
'Matrix intake bot server support: a read-only investor-match endpoint so the bot can',
|
||||
'recognize existing investors (no duplicates), plus write-provenance in the audit log.',
|
||||
'No data changes.',
|
||||
].join(' '),
|
||||
},
|
||||
migrations: { up: async () => {}, down: async () => {} },
|
||||
})
|
||||
Reference in New Issue
Block a user