-
…expired key; preview save name-keyed + empty=abandon-change; previewId atomic claim; transactional batch queue + column truncation; digest zone token-budgeted; window-orphan tool_result trim; convId length cap; readFormData authoritative moduleId + /form/options permission gate (security audit); WRITE_CLAIM state-statement lookahead
-
…e shared kernel + PreviewService (previewId Redis binding, save-side re-validation, state legality checks) + queue-only OpService (sMakePerson dual-write, status=confirmed); retire ProposeWriteTool/OpController/AuditService/ErpClient writes; preview card frontend; skills/prompt/bench sync; D: checkBusinessData stub behind erp.dry-run.enabled
-
…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.
-
…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)
-
- 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.