• …p+CAS, tenant enforcement
    
    Audit findings 1-5 confirmed real; root causes were (a) identity self-reported by
    the client, (b) every failure path defaulting open.
    
    - ERP /ai/whoami (saas-8s+ @c8e0057 follow-up) resolves token → real user/tenant/type;
      AuthzService.resolveIdentity is now the single identity entry point. Client-supplied
      userid/brandsid/usertype removed from all request bodies and from chat.html.
    - dev-login is a real switch (erp.dev-login.enabled, default false) and its ERP creds
      no longer have built-in admin/666666 defaults; blank token in production → 401,
      never a silent fall back to the dev (sysadmin) account.
    - conversations/op/form endpoints require login; conversation ids are namespaced by
      user id and ownership-checked (403 otherwise); /op/pending no longer returns sPayload.
    - op confirm/cancel check the proposer, and confirm claims the draft via CAS so
      concurrent/repeat confirms cannot execute twice; bill numbers are regenerated at
      execution time instead of replaying the propose-time snapshot.
    - FK options take the tenant from the introspected identity and return empty rather
      than dropping the sBrandsId predicate.
    - secrets moved to env vars (DB_URL/DB_USERNAME/DB_PASSWORD/REDIS_*/LLM_*/ERP_BASEURL);
      allowMultiQueries=false. NOTE: the previously committed credentials must be rotated.
    - ids interpolated into ERP URLs are validated (safeId) to stop query/path injection.
    - update path rejects system columns, resolves FK names, and coerces by column type;
      numeric coercion now rejects unparseable input instead of writing 0/truncating, and
      proposal summaries show the value that will actually be written.
    - ResponseStatusException keeps its 401/403 status instead of being wrapped as 200.
    - anti-fabrication guard stays on when the intent gate itself fails.
    zichun authored
     
    Browse Code »
  • 作废/审核/修改 by bill number failed — locateRecord/lookupRecord always
    filtered on the %Name field which can't match bill numbers.
    zichun authored
     
    Browse Code »


  • Add a deterministic §5 intent stage before ReAct: extract {intent, form,
    entities, missing slots} via constrained JSON, then expose only the 3-5
    tools relevant to that intent instead of all 12.
    
    New:
    - agent/Intent, agent/ToolScope: intent + visible-tool-set model
    - service/IntentService: constrained-JSON intent/entity extraction
    - service/OllamaJsonClient: JSON-mode Ollama calls
    - service/SlotFillService: slot extraction/prefill of known values
    
    Wire through AgentFactory, SystemPromptService, QueryTool,
    ProposeWriteTool, ErpClient, AgentChatController, OpController, chat.html.
    zichun authored
     
    Browse Code »

  • Bug: '我要报价纸盒' -> agent put product '纸盒' into 客户名称, FK-lookup failed, then asked to CREATE a customer named 纸盒.
    
    - System prompt rule 6: 新增报价 ALWAYS collectForm first; never map product/spec/盒型 to 客户名称;
      unknown customer -> let user pick from dropdown, never invent or offer to create.
    - collectForm(entityKeyword, knownFieldsJson?): pre-fills the form from what the user already said
      (产品/数量/尺寸); customer/product render as dropdowns to pick real records.
    - FK-not-found message no longer implies creating (was '请确认是否已存在' -> LLM read as 'create it?');
      now '不是系统里已有的X,请从下拉选真实存在的X,不要新建'.
    
    Verified: '我要报价纸盒,1000个,长和宽50cm,高5cm' -> collectForm with 产品名称=纸盒(prefilled),
    长/宽=50/高=5/数量=1000(prefilled), 客户名称=empty dropdown (no more guessing/create-customer).
    zichun authored
     
    Browse Code »
  • User: form rendered every field as a plain text box; no type (dropdown/date/number), and
    FK fields didn't pull options from their source tables.
    
    - FormCollect schema now carries per-field type: fkselect | select | number | date | text
      (+ options for select, fkTable for fkselect, hint for placeholder).
      Types inferred from column data type for generic forms; curated for quote.
    - New GET /api/agent/form/options?table=&q=&brandsid= -> FK options from the source table
      (elecustomer/eleproduct/elematerials...), whitelisted to tables that appear as sFkTable in the
      field dict (rejects arbitrary tables e.g. gdslogininfo), tenant-filtered + LIMIT.
    - chat.html renderFormCollect renders by type: fkselect = searchable <input list=datalist> that
      fetches options on focus/typing; select = <select>; number = <input type=number>; date picker; text.
    - Fix: tolerant label matching (strip parentheticals) so '多数量' maps whether or not the LLM keeps
      the '(逗号分隔)' hint -> multi-qty rows now reliably created.
    
    Verified via agent: quote form shows 客户/产品/物料=fkselect, 单位/印刷/颜色/单双面=select,
    尺寸/数量/单价/系数=number; options endpoint returns real names (客户 q=Little -> Little Antelope);
    non-FK table rejected; full write incl 多数量 (BJD202607086 -> 2000/4000/6000).
    zichun authored
     
    Browse Code »
  • User: chat form showed only 7 fields vs ERP's full 报价单. Root cause: 报价 is a cross-table
    master-detail form; 印刷/颜色/单双面/部件/单价/系数/材料备注/多数量 live in slave tables, not the master.
    
    - FormResolverService.curatedFields(quote): 17 fields tagged by target table (master/slave/note/manyqtys);
      印刷/颜色/单双面 have no column -> written as note into slave sMaterialsMemo.
    - ProposeWriteTool.proposeQuote: routes fields to quoquotationmaster + quoquotationslave(印刷/部件, sParentId cascade) +
      quoquotationmanyqtys(多数量, one row per qty); FK name->id, type-coerce; emits __tables__ structured payload.
    - ErpClient.createMulti + OpController: multi-table addUpdateDelBusinessData (master + slaves in one call).
    - Pricing (核价) intentionally left to ERP (control/materials/process rows are engine-generated).
    
    Verified via agent (real write): collectForm shows all 17 fields; created BJD202607084 = master(Little Antelope/妇科三醇乳膏/1.8mm双灰板/5000/12x8x5/3.5) + slave(只/部件30x20/系数1/材料备注含印刷=胶印;颜色=彩色;单双面=单面) + manyqtys(1000/3000/5000).
    zichun authored
     
    Browse Dir »
  • Root cause of the reported failure:
    - collectForm sourced fields from gdsconfigformslave -> quote master's form config only exposes 3 system/header fields (失效时间/制单人/单据日期); user filled 单据日期(tCreateDate,datetime)='12' -> ERP type mismatch.
    - proposeCreate never coerced by column type, never FK-resolved names, used addBusinessData (no billNo).
    
    Fixes:
    - FormResolverService: businessFields() curated authoritative label->{col,fk} for quoquotationmaster (客户名称/产品名称/数量/长/宽/高/单价 -> real writable cols sCustomerId/sProductId(FK)/dQty/dLength/dWidth/dHeight/dPrice), field-dict fallback for other tables; columnTypes/coerce/typeDefault/resolveFk/nextBillNo(BJD+YYYYMM+seq); isSystemColumn excludes reserve/money/rate/discount/person/date/bool/tenant/maker; resolveMasterForm excludes param/slave/control/config/color secondary tables.
    - collectForm: fields from businessFields (excludes system/header); gdsconfigformslave fallback.
    - proposeCreate: label->{col,fk} via businessFields; FK name->id; type-coerce; skip system cols; auto sId/sFormId + generated sBillNo; type-correct required defaults.
    - ErpClient.createForm: addUpdateDelBusinessData(handleType=add)+moduleId (was addBusinessData).
    - ProposeWriteTool.resolveForm unified to FormResolverService.
    
    Verified via agent end-to-end (real write): collectForm shows correct fields; created quoquotationmaster BJD202607082 = Little Antelope / 妇科三醇乳膏 / qty5000 / dims / price, tenant+maker+date auto-injected.
    zichun authored
     
    Browse Dir »
  • …ormCollect + Skills + KgSearch
    
    - AgentIdentity + AgentFactory: build agent per request with token+form-allowlist carried in tool instances (per-call context; robust vs LC4j callback threading)
    - ErpClient: token-aware read/write/examine; user-token expiry does NOT silently re-login as dev-admin (no privilege escalation)
    - AuthzService: devIdentity()/userIdentity() resolve granted-form set (sAuthsId)
    - proposeExamine tool + OpController examine dispatch + forward user Authorization on confirm
    - InteractionTool.askUser (structured options), FormCollectTool.collectForm (real gdsconfigformslave schema)
    - SkillService + SkillTool.loadSkill + ai_skill digest in system prompt
    - KgQueryTool.kgSearch: L2 neighbors/flow + L3 field->table
    - tools ErpReadTool/ProposeWriteTool/QueryTool/FormCollectTool now per-request (not @Component)
    zichun authored
     
    Browse File »
  • …i_op_queue staging cols + ai_skill registry
    
    - proposeCreate tool -> ai_op_queue draft(payload) -> confirm executes addBusinessData
    - ai_op_queue: add sPayload/sSourceRef/bAutoExecute/sResultBillId/sErrorMsg/tExecutedDate/tExpireAt (create/examine/auto-flow ready)
    - ai_skill registry table + seed playbooks (Skills §6)
    - QueryTool: self-repair retry (feed SQL error back to model, up to 3x)
    - TracingChatModelListener: per-call LLM latency/token/error trace (Langfuse hook point)
    zichun authored
     
    Browse Code »
  • - 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.
    zichun authored
     
    Browse File »

  • - 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.
    zichun authored
     
    Browse File »