-
- AuthzService: form-level allowlist ported from backend getsAuthsIdNew (sysjurisdiction sKey set); sysadmin=all; wired into Read/lookupRecord/proposeUpdate/proposeDelete (admin dev-login => all-access; enforces per-user once prod passes through user token) - proposeDelete + ErpClient.deleteForm (handleType=del) + OpController branches op type; HITL-gated like update - docs/security-findings.md: report of the 5 current-system vulns (backend form-perm off, read-API-can-write, NL2SQL no-tenant/over-privilege/partial-SQL-safety/cache-skips-validation) for the business - Verified end-to-end: read after authz = 93; propose-delete card -> confirm -> row actually deleted; audit rows written.
-
…nce) vs deferred (authz/create-delete-审核/FormCollect/staging-executor/Langfuse/NL2SQL)
-
- FormResolverService: shared KG resolution (entity->master table excl. viw_*, field 中文->column, name field, labels) - ErpReadTool.lookupRecord(entity, record): reliably resolves the master table + returns a single record's full labelled fields — fixes the 'specific field of a specific record' case (e.g. 必胜客的销售员 -> 马艺祖) - AuditService + ai_audit_log: immutable audit of propose/confirm/cancel/query (who/when/action/target/result) - QueryTool.queryData(question): read-only SQL escape hatch — coder-model NL2SQL grounded on 字段字典, jsqlparser single-SELECT guard, blocks OUTFILE/LOAD_FILE/information_schema/SLEEP, forced LIMIT, audited. SAFE by construction; NL2SQL table/join accuracy for complex questions is limited (architecture-flagged, deferred). Verified: lookupRecord works; audit rows written; Query guards + LIMIT + audit work (NL2SQL grounding still mis-picks entity master on multi-table questions).
-
- ai_op_queue: staging table for AI write ops (draft|confirmed|executed|failed|cancelled) - ProposeWriteTool.proposeUpdate(entity, record, field, newValue): self-resolves the entity master table (excludes viw_* report views), resolves field via 字段字典, locates the unique record + old value, stages a DRAFT to ai_op_queue — DOES NOT execute; returns a proposal - ErpClient.updateForm: executes via ERP addUpdateDelBusinessData with the frontend-compatible payload {data:[{sTable,name:master,column:[{handleType:update,sId,field:value}]}]} - OpController: deterministic (non-LLM) POST /op/{id}/confirm (executes + writes back status) / cancel / GET pending - AgentChatController.onToolExecuted: on proposeUpdate, attach conversation + push write_proposal SSE - chat.html: confirm/cancel card + result echo - system prompt: proposeUpdate flow; never claim a write is done before confirm Verified end-to-end: '把必胜客简称改成必胜客中国' -> proposal card -> confirm -> ERP update -> DB changed; cancel -> DB unchanged. Nothing executes without explicit user confirm. -
- ErpReadTool: optional keyword -> LIKE filter on the form's resolved name field (viw_kg_field_dict, *Name); keyword doc clarified (only for locating a named record, empty for counts) - ErpClient.readForm: accepts filterField/filterValue -> bFilter - KgQueryTool.findForms: rank by bAiTool(curated) + table + flow-connectivity + name length so the top hit is usually the canonical form - SystemPromptService: hard 'always Simplified Chinese, no other language' rule (kills qwen language drift); prefer top form; read directly instead of over-asking - AgentConfig: qwen3 think(false)/returnThinking(false) -> fast + disciplined tool-calling, no leaked chain-of-thought - application-saaslocal.yml: chat model -> qwen3:14b Verified: no more foreign-language leaks; '多少客户'->92; keyword lookup finds 必胜客; proc-backed stock forms readable; greeting 2s, form lookup ~6s. Known limit: which form/columns get picked for a specific-field-of-specific-record query still varies (deferred KgSearch).
-
- RedisChatMemoryStore (ChatMemoryStore): persist per-conversation messages to Redis (chat:mem:{convId}, 30d TTL) -> survives restart; agent memory now Redis-backed via MessageWindowChatMemory.builder().chatMemoryStore(...) - ConversationService: per-user conversation metadata (chat:convs:{userId} hash), title from first message, list/create/delete/history - ConversationController: GET/POST/DELETE /api/agent/conversations + GET /{id}/messages - AgentChatController: touch conversation on each turn - chat.html: conversation sidebar (list/switch/new/delete), load history on switch, refresh on send Verified: multi-turn memory recalls facts; after full restart, conversation list + history + memory all persist (agent still recalls prior-turn facts). -
- ErpClient: thin HTTP client to ERP xlyEntry; dev-login (/checklogin admin) mints+caches a real session token, auto re-login on session expiry (code=-2); reads getBusinessDataByFormcustomId (read-only params only, never bUpdate) - ErpReadTool.readFormData(formId,moduleId): reads a form's real rows, renders Chinese column labels from viw_kg_field_dict, first 10 rows + total count - wired into the agent (tools = findForms + readFormData); system prompt now describes the findForms->readFormData flow (still read-only, writes still 'under development') - application-saaslocal.yml: erp.baseurl -> local xlyEntry :8697 + dev-login creds Verified: '我们有多少客户' -> agent finds the form, reads 92 real customers, renders a clean Chinese table (name/category/salesperson/tax).
-
- ReActAgent: AiServices streaming tool-loop (replaces 8-scene SceneSelector path) - AgentConfig: single agent = streaming Ollama(qwen2.5:14b) + KgQueryTool + per-conversation memory + L1-domain system prompt - SystemPromptService: injects L1 domain map (viw_kg_domain) into system prompt; degrades gracefully if KG views missing - KgQueryTool.findForms: read-only form-catalog lookup over local viw_ai_useful_forms (global metadata, no tenant) — interim formId resolver - AgentChatController: POST /api/agent/chat SSE stream ({type:token|reset|done|error}); reset drops pre-tool-call narration - chat.html: consume the new SSE endpoint (streamed), per-page conversationId, drop TTS-borrow path - application-saaslocal.yml: add to worktree (was master-only untracked -> profile silently hit remote DB) Verified running (saaslocal, :8099): streaming chat, tool loop returns real forms, write requests correctly report 'under development'. -
…tions, standalone chat page, always user-present, designated model cloud/local, shared-DB multi-brand); backups recorded; formId deferred; refresh open-items
-
…rminal + reconcile on reconnect; manual stays sync
-
… terminal (no chaining), validate-only
-
…oposeWrite ends turn on draft
-
…nistic /op/{id}/confirm endpoint (not a tool) -
…lt); ERP real-time exec via /ai/execStaging + two link modes
-
…on dropped, form-level only via sAuthsId; Query security simplified
-
…Ai authorization layer; Read not permission-safe + param whitelist; invoke not backend-safe
-
…oseWrite covers mutating button-actions
-
… ERP procs, Query = ad-hoc-only
-
…; NL2SQL security pending investigation
-
Roll L2 flow up to domain->domain adjacency (11 domains) with anchor forms, form/tool counts, and the matching AI-agent scene. This is the coarse L1 tier meant to live in the agent system prompt for routing; L2/L3 stay as on-demand tools. Domains align to ai_agent's 8 business 智能体 (+ 基础资料/设备/其他 -> ERP代理人).
-
Materialize each form's L2 neighbors (upstream/downstream documents) and L3 neighbors (referenced entity tables) so the retriever can expand a vector-recalled form-card into a subgraph context via a single JOIN. 702 forms have downstream, 476 upstream, 1467 reference entity tables.
-
Map 业务中文名 -> real column -> table (38342 mappings, 6075 terms, 539 tables), ranked by form-usage for same-name disambiguation, with FK target (sFkTable/sFkKey) attached from viw_kg_edge_ref so NL2SQL knows the join. This is the field-dictionary half of the GraphRAG package.
-
Assemble one structured, embeddable text card per form (1748) merging node metadata, upstream/downstream document names, and the field 中文名 list. export_form_cards.sh emits JSONL for vector-store ingestion (group_concat_max_len raised to avoid truncating 269-field forms). Generated corpus is gitignored — regenerate deterministically via script.
-
Parse gdsconfigformslave.sRelation (comma-separated field&table&pk&display hops) into FK edges: src_table.field -> tgt_table.pk, with the set of display columns each FK surfaces aggregated per edge. 1597 distinct FK edges / 368 source tables / 109 target tables (mostly ele*/sis* master data). Provides the join dictionary for NL2SQL grounding.
-
One row per useful form enriched with business domain (L1, derived from normalized data-source prefix + AI-scene override), data source/exec type, and upstream/downstream flow counts from viw_kg_edge_flow. 857 forms participate in document flow; 63 back an AI tool; 92% domain-classified.
-
Materialize source-form -->generate--> target-form flow edges from the low-code config. Resolves BtnCopyTo.<Action>.sActiveId to the target gdsmodule exactly as Sp_Ai_AddCommonAfterNew does (sActiveId is the real target; the .ActXxx suffix is only a label). 411 distinct edges / 405 (src,tgt) pairs / 51 self-loops; 404 endpoints hit viw_ai_useful_forms. Captures full order-to-cash and procure-to-pay chains plus cross-domain (order->workorder, quote->process-card).