|
|
|
merged
[ADD] viin_ai_helpdesk, viin_ai_crm: Phase 4 AI connectors (helpdesk seed + CRM advisory tools)
|
[REM] docs, viin_ai_account, viin_ai_skill, viin_brain_account_reports: drop superseded TT200 accounting refs
TT200 (Thong tu 200/2014/TT-BTC) is superseded by TT99 (Thong tu 99/2025).
TT133 (Thong tu 133/2016, for SMEs) and TT99 both remain in effect and are kept.
- Replace every TT200 reference with its successor TT99: docs (roadmap,
architecture diagram, data-models), README, the account connector manifest
+ seed comment, the accounting skill pack, and the brain account-reports
bridge. The architecture box-drawing diagram keeps its column alignment.
- Keep TT133 and TT99 (both current): the accounting skill pack teaches
TT99 (enterprises) / TT133 (SMEs).
- Attribute receivable-provision rates to the current doubtful-debt
provisioning regulation generically, not to the accounting regime.
The "200h" effort figure in adr-008 is hours, not a circular, and is left
untouched (ADRs are append-only).
|
Succeed
|
|
|
|
|
|
|
|
merged
[ADD] viin_ai_helpdesk, viin_ai_crm: Phase 4 AI connectors (helpdesk seed + CRM advisory tools)
|
[IMP] viin_ai_agent, viin_ai_crm, viin_ai_helpdesk: strip HTML from AI tool payloads
Connector tools fed raw Html-field values (lead/ticket description,
activity note, message body) straight into the payload the LLM reads -
HTML tags are token noise and can confuse the model.
- viin_ai_agent: add _tool_plain_text on ir.actions.server, a sibling of
_tool_result that runs a value through html2plaintext ('' for falsy),
callable from a server-action code body (safe_eval). Mirrors the
existing viin_ai_chat plain-text helper; no viin_brain coupling.
- viin_ai_crm: strip crm.lead.description and mail.activity.note.
- viin_ai_helpdesk: strip viin.helpdesk.ticket.description and
mail.message.body (strip first, then truncate so the cap applies to
plain text).
- Surveyed the other connectors (account/sale/sale_crm/stock) and the
AI/Brain tool surface: no other tool returns a raw Html field
(Brain already returns the content_plain T1 representation).
- Tests: red-green behaviour tests assert the payload value carries no
'<' and preserves the readable text, driven through a real
ir.actions.server.run() as the tool's low-privilege group.
- docs: note the _tool_plain_text sibling next to _tool_result in
ai/architecture.md so future connectors strip Html-field values.
|
Killed
|
|
Not finished
|
|
|
|
|
|
merged
[ADD] viin_ai_helpdesk, viin_ai_crm: Phase 4 AI connectors (helpdesk seed + CRM advisory tools)
|
[IMP] viin_ai_agent, viin_ai_crm, viin_ai_helpdesk: strip HTML from AI tool payloads
Connector tools fed raw Html-field values (lead/ticket description,
activity note, message body) straight into the payload the LLM reads -
HTML tags are token noise and can confuse the model.
- viin_ai_agent: add _tool_plain_text on ir.actions.server, a sibling of
_tool_result that runs a value through html2plaintext ('' for falsy),
callable from a server-action code body (safe_eval). Mirrors the
existing viin_ai_chat plain-text helper; no viin_brain coupling.
- viin_ai_crm: strip crm.lead.description and mail.activity.note.
- viin_ai_helpdesk: strip viin.helpdesk.ticket.description and
mail.message.body (strip first, then truncate so the cap applies to
plain text).
- Surveyed the other connectors (account/sale/sale_crm/stock) and the
AI/Brain tool surface: no other tool returns a raw Html field
(Brain already returns the content_plain T1 representation).
- Tests: red-green behaviour tests assert the payload value carries no
'<' and preserves the readable text, driven through a real
ir.actions.server.run() as the tool's low-privilege group.
|
Killed
|
|
Not finished
|
|
|
|
|
|
merged
[ADD] viin_ai_helpdesk, viin_ai_crm: Phase 4 AI connectors (helpdesk seed + CRM advisory tools)
|
[ADD] viin_ai_helpdesk, viin_ai_crm: Phase 4 AI connectors (helpdesk seed + CRM advisory tools)
Phase 4 (Track C) per-app AI connectors, unlocked by M3:
- viin_ai_helpdesk (NEW): AI connector for viin_helpdesk. Seeds 1 agent
(Helpdesk Assistant) + 1 topic (Helpdesk Triage) + 3 READ-only tools on
viin.helpdesk.ticket: triage_ticket, suggest_canned_response,
generate_faq_from_thread. Data-XML, zero Python (Layer-3 convention).
- viin_ai_crm (IMP): adds 3 advisory READ tools (qualify_lead,
next_action_suggest, email_draft_for_lead) wired into the Lead
Qualification topic.
- All tools: requires_confirmation=False (read-only), runs_as_sudo=False,
group_ids ACL-gated; model_id write-gate satisfied (group has perm_write
on the target model). run()-driven boundary tests prove the safe_eval +
write-gate path.
- docs: AGENTS.md + docs/roadmap.md reflect Phase 4 progress; reconcile a
pre-existing roadmap drift (helpdesk dependency named the Odoo-EE
'helpdesk' module instead of Viindoo 'viin_helpdesk').
|
Killed
|
|
Not finished
|
|
|
|
|
|
merged
[IMP] viin_ai_base, viin_ai_agent: bidirectional data-driven message adapter (close #64 #62 #63)
|
[IMP] viin_ai_base, viin_ai_agent: bidirectional data-driven message adapter (close #64 #62 #63)
Symmetric follow-up to the outgoing adapter (#59): make the INCOMING response
normalizer data-driven via viin.ai.message.adapter, add a per-vendor empty-content
knob, and route the streaming path through the adapter with a contract guard.
- #64: viin.ai.message.adapter gains a `direction` discriminator (outgoing|incoming)
+ incoming response-map knobs; viin.ai.provider._normalize_completion_response is
rewired to dispatch data-driven incoming primitives (output byte-stable for the 3
seeded protocols). An admin can hotfix a vendor response-key rename with no release.
- #62: per-vendor `empty_content_repr` knob (empty_string|null|omit), default
empty_string (byte-identical to prior behaviour) for strict gateways (Azure OpenAI).
- #63: viin_ai_agent._do_llm_stream redacts-then-adapts before transport + a contract
guard test that fails if a future streaming multi-turn path bypasses the adapter
(also patches a latent streaming PII-redaction gap).
- Hardening (post code-review): incoming rows must declare all leaf-key paths
(finish/usage/text/tool); incoming rows for an unsupported protocol are rejected at
write time (no silent mis-parse); clearer parse-error diagnostics; tightened
guard tests (per-location PII, value-switch data-driven proof, assertRaises).
- ADR-018 (bidirectional data-driven message adapter); docs/roadmap/module-map
reconcile (32 modules, PR #57/#59/#60 history, ADR ledger, adr-015/016 status sync).
Tests: Odoo native, no-API-key; 302 non-tour tests green (normalize/wire byte-stable
non-regression + incoming data-driven proof + #62 matrix + #63 guard + multiturn loop
+ new validation guards). flake8 clean on the Runbot lint gate. Pre-existing HttpCase
browser tours need a live http server and fail identically on base 17.0 under --no-http.
Claude-Session: https://claude.ai/code/session_01MpghN1mdjfEzHd1yuSqdut
|
Succeed
|
|
|
|
|
|
|
|
merged
[IMP] viin_ai_base, viin_ai_agent: bidirectional data-driven message adapter (close #64 #62 #63)
|
[IMP] viin_ai_base, viin_ai_agent: bidirectional data-driven message adapter (close #64 #62 #63)
Symmetric follow-up to the outgoing adapter (#59): make the INCOMING response
normalizer data-driven via viin.ai.message.adapter, add a per-vendor empty-content
knob, and route the streaming path through the adapter with a contract guard.
- #64: viin.ai.message.adapter gains a `direction` discriminator (outgoing|incoming)
+ incoming response-map knobs; viin.ai.provider._normalize_completion_response is
rewired to dispatch data-driven incoming primitives (output byte-stable for the 3
seeded protocols). An admin can hotfix a vendor response-key rename with no release.
- #62: per-vendor `empty_content_repr` knob (empty_string|null|omit), default
empty_string (byte-identical to prior behaviour) for strict gateways (Azure OpenAI).
- #63: viin_ai_agent._do_llm_stream redacts-then-adapts before transport + a contract
guard test that fails if a future streaming multi-turn path bypasses the adapter
(also patches a latent streaming PII-redaction gap).
- Hardening (post code-review): incoming rows must declare all leaf-key paths
(finish/usage/text/tool); incoming rows for an unsupported protocol are rejected at
write time (no silent mis-parse); clearer parse-error diagnostics; tightened
guard tests (per-location PII, value-switch data-driven proof, assertRaises).
- ADR-018 (bidirectional data-driven message adapter); docs/roadmap/module-map
reconcile (32 modules, PR #57/#59/#60 history, ADR ledger, adr-015/016 status sync).
Tests: Odoo native, no-API-key; 302 non-tour tests green (normalize/wire byte-stable
non-regression + incoming data-driven proof + #62 matrix + #63 guard + multiturn loop
+ new validation guards). Pre-existing HttpCase browser tours need a live http server
and fail identically on base 17.0 under --no-http (not part of this change).
Claude-Session: https://claude.ai/code/session_01MpghN1mdjfEzHd1yuSqdut
|
Failed
|
|
|
|
|
|
|
|
merged
[IMP] viin_ai_base, viin_ai_agent: bidirectional data-driven message adapter (close #64 #62 #63)
|
[IMP] viin_ai_base, viin_ai_agent: bidirectional data-driven message adapter (close #64 #62 #63)
Symmetric follow-up to the outgoing adapter (#59): make the INCOMING response
normalizer data-driven via viin.ai.message.adapter, add a per-vendor empty-content
knob, and route the streaming path through the adapter with a contract guard.
- #64: viin.ai.message.adapter gains a `direction` discriminator (outgoing|incoming)
+ incoming response-map knobs; viin.ai.provider._normalize_completion_response is
rewired to dispatch data-driven incoming primitives (output byte-stable for the 3
seeded protocols). An admin can hotfix a vendor response-key rename with no release.
- #62: per-vendor `empty_content_repr` knob (empty_string|null|omit), default
empty_string (byte-identical to prior behaviour) for strict gateways (Azure OpenAI).
- #63: viin_ai_agent._do_llm_stream redacts-then-adapts before transport + a contract
guard test that fails if a future streaming multi-turn path bypasses the adapter
(also patches a latent streaming PII-redaction gap).
- ADR-018 (bidirectional data-driven message adapter); docs/roadmap/module-map
reconcile (32 modules, PR #57/#59/#60 history, ADR ledger, adr-015/016 status sync).
Tests: Odoo native, no-API-key; 74-class net green (normalize/wire byte-stable
non-regression + incoming data-driven proof + #62 matrix + #63 guard + multiturn loop).
Claude-Session: https://claude.ai/code/session_01MpghN1mdjfEzHd1yuSqdut
|
Failed
|
|
|
|
|
|
|
|
merged
[ADD] KG temporal Wave-2 (ADR-006): _as_of reconstruction + live KG tools + memory-brain bridge
|
[FIX] viin_ai_brain: revert record_historian as-of boundary to inclusive-at-ts + lock-in test
verify2 (review iter-2) flagged a silent, undocumented boundary flip in
record_historian._as_of_record Step-4: the tracking-message revert filter had
been changed from ('date','>',ts) to '>=', making a change stamped exactly at
ts get reverted (exclusive-at-ts) - the opposite of the whole-codebase
convention (viin_brain._find_active_at inclusive-at-ts; memory recall <= as_of;
the Step-2 create_date boundary in the same method). "State as of ts" must
reflect a change effective at ts, so revert only strictly-after-ts changes.
- record_historian.py: ('date','>=',ts) -> ('date','>',ts) + convention comment.
- test_record_historian.py: add boundary-exact lock-in test H7
(RED under '>=' AssertionError partner_before reverted; GREEN under '>').
Claude-Session: https://claude.ai/code/session_01EVjERtZ9dnk5fhZHns8vLv
|
Succeed
|
|
|
|
|
|
|
|
merged
[ADD] KG temporal Wave-2 (ADR-006): _as_of reconstruction + live KG tools + memory-brain bridge
|
[FIX] viin_ai: PR #60 review-2 - W8150 lint blocker + record_state_as_of ACL guard + temporal test hardening
- viin_ai_brain: fix Runbot test_pylint W8150 (module-level relative import in
test_record_historian.py); add caller-env ACL gate (check_access_rights/rule)
before record_state_as_of reconstruction, return {} on AccessError; RED-GREEN
test as low_priv; perm_write=1 lock-in test + rationale.
- viin_ai_memory: neutralize-proof UUID tokens in as-of recall tests; defensive
str as_of normalize via fields.Datetime.to_datetime + cross-module contract comment.
- viin_brain: boundary-exact active-at fixtures (valid_to==ts / valid_from==ts)
guarding the inclusive operator; expression.AND for temporal domain combine.
- viin_ai_memory_brain: explicit required=False/default=False on vault_id.
- docs: propagate _search_active_at -> _find_active_at; correct stale 2-condition
active-at domain to the shipped 3-condition predicate (ADR-006 append-only Wave-2 note).
Claude-Session: https://claude.ai/code/session_01EVjERtZ9dnk5fhZHns8vLv
|
Killed
|
|
Not finished
|
|
|
|
|
|
merged
[ADD] KG temporal Wave-2 (ADR-006): _as_of reconstruction + live KG tools + memory-brain bridge
|
[FIX] viin_ai: PR #60 review-2 - W8150 lint blocker + record_state_as_of ACL guard + temporal test hardening
- viin_ai_brain: fix Runbot test_pylint W8150 (module-level relative import in
test_record_historian.py); add caller-env ACL gate (check_access_rights/rule)
before record_state_as_of reconstruction, return {} on AccessError; RED-GREEN
test as low_priv; perm_write=1 lock-in test + rationale.
- viin_ai_memory: neutralize-proof UUID tokens in as-of recall tests; defensive
str as_of normalize via fields.Datetime.to_datetime + cross-module contract comment.
- viin_brain: boundary-exact active-at fixtures (valid_to==ts / valid_from==ts)
guarding the inclusive operator; expression.AND for temporal domain combine.
- viin_ai_memory_brain: explicit required=False/default=False on vault_id.
- docs: propagate _search_active_at -> _find_active_at; correct stale 2-condition
active-at domain to the shipped 3-condition predicate (ADR-006 append-only Wave-2 note).
Claude-Session: https://claude.ai/code/session_01EVjERtZ9dnk5fhZHns8vLv
|
Killed
|
|
Not finished
|
|
|
|
|
|
merged
[FIX] viin_ai_base: provider-native outgoing message adapter (#59)
|
[FIX] viin_ai_base: provider-native outgoing message adapter (#59)
The agentic tool loop shipped turn-2 canonical history (assistant tool_calls,
tool role tool_results) to the wire verbatim - no outgoing adapter existed, only
an incoming normalizer. A real tool-using turn returned HTTP 400 on
anthropic_native/openai_compat and silently dropped the tool result on
google_native. The same payload builder dropped the system prompt on all 3
protocols, and the PII redactor skipped tool-turn free-text (tool_calls
arguments, tool_results result/error).
Add a data-driven, UI-editable viin.ai.message.adapter (protocol-keyed with an
optional vendor override, noupdate seed) that reshapes the canonical history into
each provider's native wire format via vetted code primitives - zero eval/template
on the BYOK egress path. A write-time coherence constraint rejects incoherent
(protocol, shape/strategy) admin edits. System-prompt placement is handled per
protocol (anthropic top-level system, google systemInstruction, openai
role=system) with the system kwarg as the single source of truth, which also
fixes viin_ai_search Path B on anthropic/google. PII redaction is extended to
tool-turn free-text and runs before the adapt step (redact-then-adapt).
Add honest tests that stub only the transport seam and assert the built outgoing
payload per protocol (the seam #59 slipped through), plus data-driven,
coherence-gate, and PII-redaction tests.
Claude-Session: https://claude.ai/code/session_0128GTfwboHEZqJZ5xgfkxoL
|
Succeed
|
|
|
|
|
|
|
|
merged
[ADD] KG temporal Wave-2 (ADR-006): _as_of reconstruction + live KG tools + memory-brain bridge
|
[FIX] viin_ai: address PR #60 review (temporal_diff ACL, BFS N+1, as-of test gaps, conventions)
H-A: add ACL post-filter in tool_brain_temporal_diff - batch-search readable
page ids after edge set-diff, drop edges where either endpoint is unreadable
(same D6 fix class applied to graph_traverse in Wave-1).
H-B: batch the fallback BFS _acl_filter call - collect all candidate to_page
ids per BFS step BEFORE the ACL check (single Page.search per step, not one
per link, mirroring the primary delegate path).
H-C: add test_observation_recall_as_of_filters - protects the third temporal
leg (created_at <= as_of on observations); RED-then-GREEN verified.
M1: rename _search_active_at -> _find_active_at in viin.brain.link and all
call sites in brain_tools.py and tests (avoids ORM _search_<field> prefix
collision, no active_at field exists).
M2: fix assertIsNotNone(link_a.superseded_at) -> assertTrue (Odoo Datetime
null is False, not None; assertIsNotNone(False) passes silently).
M3: replace (6, 0, [...]) legacy tuples with Command.set([...]) in
viin_ai_memory_brain/tests/test_vault_id_isolation.py (AGENTS.md #7).
M4: move imports (datetime, fields.Datetime/Date) from inside function body
to module top-level in brain_tools.py (python.md imports rule).
Claude-Session: https://claude.ai/code/session_01JTYAzu8ndxtrQGJeTNa3pf
|
Failed
|
|
|
|
|
|
|
|
merged
[ADD] KG temporal Wave-2 (ADR-006): _as_of reconstruction + live KG tools + memory-brain bridge
|
[ADD] viin_ai_memory_brain: AI memory <-> Brain vault bridge (KG temporal Wave-2)
New auto-install bridge (depends viin_ai_memory + viin_brain). Adds an
optional vault_id Many2one('viin.brain.vault') to viin.ai.memory so a memory
can be scoped to a Brain vault, plus a GLOBAL ir.rule that restricts memory
visibility to vaults the user can access (member or access-group), while
vault-less memories remain governed by the existing per-owner rule.
The vault rule is GLOBAL (AND-combines = restricts), not non-global: a
non-global rule with an OR(vault_id=False, ...) disjunct would OR-union with
the owner rule and leak every vault-less memory across owners. Tests cover
cross-vault, cross-company, owner-only vault-less, and the cross-owner
vault-less regression.
Part of ADR-006 KG temporal Wave-2.
Claude-Session: https://claude.ai/code/session_01JTYAzu8ndxtrQGJeTNa3pf
|
Failed
|
|
|
|
|
|
|
|
merged
[REF] viin_ai: standardize AI tool server-action return envelope as ir.actions.client
|
[REF] viin_ai_stock: return AI tool results via the standard envelope helper
The 3 stock tools (get_stock_level, get_low_stock_products, get_product_moves)
now use env['ir.actions.server']._tool_result(payload); tests assert the
ir.actions.client envelope (tag viin_ai_tool_result) under params['data'].
Claude-Session: https://claude.ai/code/session_0128GTfwboHEZqJZ5xgfkxoL
|
Killed
|
|
Not finished
|
|
|
|
|
|
merged
[FIX] viin_ai_account/stock: ADR-012 seed runtime crashes + review-driven hardening
|
[IMP] viin_brain: reconcile linkable docstring with ADR-012 carrier reality
The viin_brain_linkable docstring still claimed the per-app bridges "explicitly
inherit this mixin" (their concrete models were deleted) and that "WI-7 will set
installable:False" (contradicted by the ADR-012 revive). Correct both claims; the
_brain_form_sidebar flag and all logic are untouched.
Claude-Session: https://claude.ai/code/session_01JTYAzu8ndxtrQGJeTNa3pf
|
Succeed
|
|
|
|
|
|
|
|
merged
[REF] viin_ai: standardize AI tool server-action return envelope as ir.actions.client
|
[REF] viin_ai_search: tidy stale arguments wording in web_query tool comment/docstring
Replace residual 'tool_web_query(arguments)' prose (which implied 'arguments'
is a safe_eval dispatch-scope variable) with 'tool_web_query(...)'. The bare
'arguments' is the method parameter name, not a server-action context variable.
Comment/docstring only - no behavior change.
Claude-Session: https://claude.ai/code/session_0128GTfwboHEZqJZ5xgfkxoL
|
Killed
|
|
|
|
|
|
|
|
merged
[REF] viin_ai: standardize AI tool server-action return envelope as ir.actions.client
|
[REF] viin_ai_search: return web_query result via the standard envelope helper
Rewrite the web_query server-action body to build its result through
`env['ir.actions.server']._tool_result(...)`, returning a valid
ir.actions.client action instead of the non-standard `tool_result` dict.
Update the header comment and the server-action test to assert the new
envelope while preserving the domain/fallback_used assertions.
|
Killed
|
|
Not finished
|
|
|
|
|
|
merged
[IMP] ADR-012: per-app seed master data - 9 modules (AI connectors + Brain bridges)
|
[DOC] AGENTS,docs,README: reconcile full ADR-012 per-app seed (9 modules) + correct tool names + viin_brain_sale rename
|
Succeed
|
|
|
|
|
|
|
|
merged
[IMP] ADR-012: per-app seed master data - 9 modules (AI connectors + Brain bridges)
|
[DOC] AGENTS,docs,README: reconcile ADR-012 CRM+Sale seed state + viin_brain_sale rename across docs
|
Killed
|
|
Not finished
|
|
|
|
|
|
merged
[FIX] viin_ai_brain: tool server-actions read args from context (fix NameError through agent loop)
|
[FIX] viin_ai_search: correct stale tool-args injection comment
The comment claimed `viin_ai_tool_args` is mapped to `arguments` via a
`_get_eval_context` override; no such override exists. The server-action
already reads its arguments via env.context.get('viin_ai_tool_args', {}).
Comment only - no code change.
|
Killed
|
|
Not finished
|
|
|
|
|
|
merged
[IMP] ADR-012: per-app seed master data - 9 modules (AI connectors + Brain bridges)
|
[DOC] AGENTS,docs,README: reconcile ADR-012 CRM+Sale seed state + viin_brain_sale rename across docs
|
Revoked
|
|
|
|
|
|
|
|
merged
[IMP] ADR-012: per-app seed master data - 9 modules (AI connectors + Brain bridges)
|
[DOC] AGENTS,docs,viin_brain: rename viin_brain_sale_management -> viin_brain_sale references + ADR-012 addendum
|
Killed
|
|
Not finished
|
|
|
|
|
|
merged
[IMP] ADR-012: per-app seed master data - 9 modules (AI connectors + Brain bridges)
|
[IMP] viin_ai_sale_crm: cross-domain handoff topic + targeted auto_install (ADR-012)
|
Killed
|
|
Not finished
|
|
|
|
|
|
merged
[ADD] AI Cognitive Wave-1: Memory L0-L3 + Skill runtime + Pulse cost gate + KG temporal (supersedes #53)
|
[FIX] viin_ai: clear Runbot errors and warnings (PR #54)
Resolve every Runbot build error and warning on the Cognitive Wave-1
branch without masking any behavior. Each fix is root-cause proven and
verified by a real Odoo 17 test run on a fresh DB (--skip-auto-install).
- viin_ai_chat: fix stale mock signature in test_user_error_passed_through.
_user_err now matches _collect_allowed_tools(self, applied_skills=None),
which viin_ai_skill's _resolve_run_skills override calls positionally.
This was the only build-breaking failure.
- viin_ai_base: guard the _record_error_usage separate-cursor write so a
provider_id/model_id not yet visible to that cursor is set NULL instead
of raising a ForeignKeyViolation. Removes the odoo.sql_db ERROR noise
while preserving the trace-durability invariant. Add a regression test
that verifies via a fresh cursor (the row is committed by a separate
cursor, invisible to the REPEATABLE READ test cursor).
- viin_ai_approval: remove the dead advisory tool ACL-gap warning in
_advisory_agent_has_tools (unreachable - every caller runs sudo, so it
could never detect a real gap). Assert the H8 executable-tools refusal
warning via assertLogs. Add mail_notify_force_send=False to
TestAdvisoryInfra.setUpClass.
- viin_ai_memory: align the promotion.log / observation owner ir.rule perms
with ir.model.access.csv (promotion.log read-only, observation
append-only), clearing the to_base record-rule validator warning. Add
ACL regression tests driven via with_user. Downgrade the BYOK
no-provider summarize-skip log from WARNING to INFO (expected
steady-state when no key is configured), and keep the cron test asserting
its real contract (zero external call) rather than a data-state-dependent
log.
- viin_ai_agent: assert the sudo-escalation refusal security warning via
assertLogs so the expected signal is captured, not leaked to the log.
- viin_ai_ops / viin_ai_ops_brain / viin_ai_approval_sale: add the
mail_notify_force_send=False test-context guard (OpsBaseTestCase and the
evidence/advisory-sale test classes) so confirm/route/notify flows do not
attempt a synchronous mail send that fails on a runner with no
mail.catchall ICP. These tests assert routing/state, not email delivery.
|
Succeed
|
|
|
|
|
|
|
|
merged
[ADD] AI Cognitive Wave-1: Memory L0-L3 + Skill runtime + Pulse cost gate + KG temporal (supersedes #53)
|
[FIX] viin_ai: clear Runbot errors and warnings (PR #54)
Resolve every Runbot build error and warning on the Cognitive Wave-1
branch without masking any behavior. Each fix is root-cause proven and
verified by a real Odoo 17 test run on a fresh DB (--skip-auto-install).
- viin_ai_chat: fix stale mock signature in test_user_error_passed_through.
_user_err now matches _collect_allowed_tools(self, applied_skills=None),
which viin_ai_skill's _resolve_run_skills override calls positionally.
This was the only build-breaking failure.
- viin_ai_base: guard the _record_error_usage separate-cursor write so a
provider_id/model_id not yet visible to that cursor is set NULL instead
of raising a ForeignKeyViolation. Removes the odoo.sql_db ERROR noise
while preserving the trace-durability invariant (the error usage.log is
still committed independently). Add a regression test that verifies via
a fresh cursor.
- viin_ai_approval: remove the dead advisory tool ACL-gap warning in
_advisory_agent_has_tools (unreachable - every caller runs sudo, so it
could never detect a real gap); this clears the false-positive log.
Assert the H8 executable-tools refusal warning via assertLogs instead of
letting it leak. Add mail_notify_force_send=False to
TestAdvisoryInfra.setUpClass to stop the mail.catchall send ERROR.
- viin_ai_memory: align the promotion.log / observation owner ir.rule perms
with ir.model.access.csv (promotion.log read-only, observation
append-only), clearing the to_base record-rule validator warning. Assert
the BYOK no-provider skip warning via assertLogs. Add ACL regression
tests driven via with_user.
- viin_ai_agent: assert the sudo-escalation refusal security warning via
assertLogs so the expected signal is captured, not leaked to the log.
|
Failed
|
|
|
|
|
|
|
|
merged
[ADD] AI Cognitive Wave-1: Memory L0-L3 + Skill runtime + Pulse cost gate + KG temporal (supersedes #53)
|
[FIX] viin_ai: clear Runbot errors and warnings (PR #54)
Resolve every Runbot build error and warning on the Cognitive Wave-1
branch without masking any behavior. Each fix is root-cause proven and
verified by a real Odoo 17 test run on a fresh DB (--skip-auto-install).
- viin_ai_chat: fix stale mock signature in test_user_error_passed_through.
_user_err now matches _collect_allowed_tools(self, applied_skills=None),
which viin_ai_skill's _resolve_run_skills override calls positionally.
This was the only build-breaking failure.
- viin_ai_base: guard the _record_error_usage separate-cursor write so a
provider_id/model_id not yet visible to that cursor is set NULL instead
of raising a ForeignKeyViolation. Removes the odoo.sql_db ERROR noise
while preserving the trace-durability invariant (the error usage.log is
still committed independently). Add a regression test that verifies via
a fresh cursor.
- viin_ai_approval: remove the dead advisory tool ACL-gap warning in
_advisory_agent_has_tools (unreachable - every caller runs sudo, so it
could never detect a real gap); this clears the false-positive log.
Assert the H8 executable-tools refusal warning via assertLogs instead of
letting it leak. Add mail_notify_force_send=False to
TestAdvisoryInfra.setUpClass to stop the mail.catchall send ERROR.
- viin_ai_memory: align the promotion.log / observation owner ir.rule perms
with ir.model.access.csv (promotion.log read-only, observation
append-only), clearing the to_base record-rule validator warning. Assert
the BYOK no-provider skip warning via assertLogs. Add ACL regression
tests driven via with_user.
- viin_ai_agent: assert the sudo-escalation refusal security warning via
assertLogs so the expected signal is captured, not leaked to the log.
|
Failed
|
|
|
|
|
|
|
|
merged
[ADD] AI Cognitive Wave-1: Memory L0-L3 + Skill runtime + Pulse cost gate + KG temporal (supersedes #53)
|
[DOC] docs,AGENTS: reconcile Cognitive Wave-1 + ADR status drift
ADR-009 ratified; module maps + roadmap + AGENTS.md status; ADR-015/016
append-only Accepted; testing.md verify cmd.
|
Failed
|
|
|
|
|
|
|
|
merged
[ADD] AI Cognitive Wave-1: Memory L0-L3 + Skill runtime + Pulse cost gate + KG temporal (supersedes #53)
|
[DOC] docs,AGENTS: reconcile Cognitive Wave-1 + ADR status drift
ADR-009 ratified; module maps + roadmap + AGENTS.md status; ADR-015/016
append-only Accepted; testing.md verify cmd. (= PR #53 docs reconcile.)
|
Killed
|
|
Not finished
|
|
|
|
|
|
closed
[ADD] Phase 3.9 Cognitive Wave-1: Memory + Skill + KG temporal + Pulse + CoWork bundle
|
[DOC] docs,AGENTS: reconcile Cognitive Wave-1 + ADR status drift (PR #53)
PR #53 (Cognitive Wave-1) shipped viin_ai_memory/skill/pulse + KG temporal
signature + CoWork 5-pack and ratified ADR-009, but the living status docs
stayed stale. Reconcile across the count-bearing + map docs:
- AGENTS.md §3: Track A now reflects M3 achieved (PR #50) + P-OPS-3/4 shipped;
Phase 3.9 Cognitive Wave-1 marked shipped (PR #53), not future.
- AGENTS.md §2 + docs/ai/architecture.md: add viin_ai_memory/skill/pulse
(+ viin_ai_workflow) to the module / dependency maps with real depends.
- docs/roadmap.md: ADR-009 footnotes/open-list resolved (3 separate modules,
Accepted PR #53); W13-W17 drift-note (Wave-1 code shipped); ADR counts
reconciled to 16 ADR (14 accepted + 2 proposed).
- docs/decisions/README.md: ADR-015/016 status proposed -> accepted (PR #47);
index date bumped.
- adr-015/adr-016: append-only Status update line (Accepted, PR #47).
- docs/ai/testing.md: add the 3 cognitive modules to the no-key verify command.
Docs-only, no code touched.
|
Killed
|
|
Not finished
|
|
|
|
|
|
closed
[ADD] Phase 3.9 Cognitive Wave-1: Memory + Skill + KG temporal + Pulse + CoWork bundle
|
[FIX] viin_ai_memory,viin_ai_skill: Runbot lint (default manifest key + W391)
- viin_ai_memory/__manifest__.py: remove auto_install=False (equals the default;
the Viindoo manifest linter test_manifests rejects redundant default keys).
- viin_ai_skill/tests/test_cowork_bundle.py: strip trailing blank line (flake8 W391).
|
Killed
|
|
Not finished
|
|
|
|
|
|
closed
[ADD] Phase 3.9 Cognitive Wave-1: Memory + Skill + KG temporal + Pulse + CoWork bundle
|
[FIX] viin_ai cluster,viin_brain: full-width text fields across form views (colspan sweep)
Repo-wide sweep of the same layout bug fixed earlier: a free-text/tag field
that is the sole (or last) child of a default two-column <group> renders
squeezed into a one-character-wide column. The canonical Odoo idiom is
colspan="2" (core sale_order_views.xml note field). Added it to 8 more fields:
- viin_ai_agent: system_prompt (agent form), ai_prompt (ir.actions.server form)
- viin_ai_base: final_response (trace form)
- viin_ai_ops: payload (action proposal form), description (goal form)
- viin_ai_approval: prompt_snapshot (action proposal form)
- viin_brain: ambiguous_candidate_ids (link form)
- viin_ai_skill: description (skill pack form)
Pure view-XML (no SCSS/JS). Found by a form-view scan, pre-checked for layout
edge cases (all default col=2 groups; no sibling-field regression), and
verified full-width by live render after -u.
|
Failed
|
|
|
|
|
|
|
|
closed
[ADD] Phase 3.9 Cognitive Wave-1: Memory + Skill + KG temporal + Pulse + CoWork bundle
|
[FIX] viin_ai_skill,viin_ai_memory,viin_ai_pulse: full-width text fields in form views
The Level-1/Level-2 description fields (skill), the summary (memory) and the
signal payload/evidence fields rendered squeezed into a one-character-wide
column. A nolabel field inside a default two-column <group> lands in the narrow
label slot; the canonical Odoo idiom (e.g. sale.order note, sale_order_views.xml)
is to add colspan="2" so the field spans the full group width.
- viin_ai_skill: description, manifest_text
- viin_ai_memory: summary, access_group_ids
- viin_ai_pulse: signal payload, evidence_refs
Pure view-XML fix (no SCSS/JS). Verified full-width by live render.
|
Failed
|
|
|
|
|
|
|
|
closed
[ADD] Phase 3.9 Cognitive Wave-1: Memory + Skill + KG temporal + Pulse + CoWork bundle
|
[FIX] viin_ai_base,viin_ai_agent,viin_ai_memory: Cognitive Wave-1 agent-loop remediation
Independent review of the cluster surfaced 1 install blocker plus agent-loop
defects that left Wave-1 tool/skill execution non-functional in production.
- viin_ai_memory: declare the missing viin_ai_agent dependency. Standalone
install crashed at Python import (the module imports prompt_safety and
_inherits viin.ai.agent); it only worked when co-installed with skill/pulse.
- viin_ai_base: _normalize_completion_response now emits stop_reason in all
three provider branches (anthropic/google/openai). Without it the agent loop
terminated after iteration 1 and no tool ever executed in production.
- viin_ai_base: normalize tool_call shape to canonical {id,name,arguments} for
anthropic (input->arguments) and openai (hoist function.name/arguments) so
tools dispatch end-to-end on every provider, not just google_native.
- viin_ai_agent: _execute_tool_call injects viin_ai_tool_args into the action
context (tools were receiving empty args) and surfaces the tool_result data
payload instead of a hardcoded 'executed'.
Each fix ships with a RED-first test that drives the real production path
(real normalizer, transport-only stub), closing the prior harness blind spot
where mocks injected the missing stop_reason and masked the defect.
|
Failed
|
|
|
|
|
|
|
|
closed
[ADD] Phase 3.9 Cognitive Wave-1: Memory + Skill + KG temporal + Pulse + CoWork bundle
|
[FIX] viin_ai cluster: Cognitive Wave-1 review remediation (10 HIGH + MED)
End-of-wave code review (6 modules) found 0 CRITICAL, 10 HIGH, 14 MED.
This pass fixes all 10 HIGH plus the cheap in-file MED, verified by a real
Odoo native test run (all fix-target tests RED-before-green, now GREEN).
Runtime / security HIGH:
- viin_ai_memory: _bump_reference iterated to fix "Expected singleton" crash
on a multi-record (L0|L1) recall set.
- viin_ai_memory: L0 identity text now wrapped via wrap_untrusted_context()
before system-prompt injection (prompt-injection surface, AGENTS.md #10).
- viin_ai_base: google_native response normalizer now extracts functionCall
parts (Gemini tool calls were silently dropped despite WI-H sending tools).
Test-integrity HIGH (fixed in-scope, not deferred):
- viin_ai_base: replaced tautological constraint-embed assertion with a real
'[constraints:' check; removed a vendor-free test that never called
production code and relocated it to viin_ai_agent calling the real
_tool_schema(); restored the real copy-before-normalize mutate-guard
(3 protocols, self-contained).
- viin_ai_skill: removed TestCoworkBundleWebQueryToolExists (always-FAIL CI
gate not controlled by the module under test).
- viin_brain: added TestBrainLinkTemporalSupersession covering create/write
auto-stamp + idempotency.
Perf / seam HIGH:
- viin_brain: batched _stamp_superseded_at (was N+1 writes).
- viin_ai_agent: _recall_memory gains trace_id param (passed at both run /
run_streaming call sites) so Memory stamps observations under the run trace
without a second agent.py touch; removed a hasattr() presence probe on a
hard-dependency method.
MED: model-ordering (_sql_constraints before fields in skill/pulse), memory
cron noupdate + child_of->in company_ids + promotion.log owner rule + decay
date pre-filter, skill group_ids now enforced in the resolver (+test),
pulse @api.depends + PASS-branch sync comment, google_native combiner
stripping, brain partial index columns (from_page_id, valid_from).
|
Failed
|
|
|
|
|
|
|
|
merged
[FIX] AI cluster Wave-1: pre-existing security remediation (4 CRITICAL + 13 HIGH)
|
[FIX] viin_ai_base,viin_ai_agent,viin_ai_approval,viin_ai_workflow: PR #52 review fixes (CI lint + findings)
CI blocker (was the red Runbot flake8 + an error-log trip):
- viin_ai_approval/tests/test_advisory_acl.py: add mail_notify_force_send=False
to setUpClass (mirrors test_approval_adapter) so confirmed-request tests no
longer trip Runbot had_error_log; drop the unused `req` local (flake8 F841).
Security (RED-test-first, behavior tests with independent oracles):
- viin_ai_approval: _build_advisory_messages now routes the untrusted block
through the SSOT wrap_untrusted_context() instead of a hand-rolled fence, so an
attacker </untrusted_context> close tag / jailbreak phrase in a display_name or
signal can no longer escape the sandbox. New test_advisory_prompt_safety.
- viin_ai_workflow: _scan_target company isolation uses child_of (the routine
company + its branch descendants), not parent_of (ancestors). parent_of both
dropped the routine's own branch rows (false negative) and pulled the parent
company's rows into scope (upward cross-tenant leak). New branch-hierarchy test.
Robustness / accuracy:
- viin_ai_base usage_log: action_replay fallback narrowed by (id > pre_max) so a
concurrent replay of the same source cannot resolve the wrong log. New test.
- viin_ai_base provider: call_embedding decodes response.json() once;
_record_error_usage comment corrected to match the real flush ordering.
- viin_ai_approval: advisory cron `limit` docstring (per company per tick);
diagnostic warning when an advisory agent's tools read empty (ACL gap).
- viin_ai_agent: _wrap_tool_result docstring corrected (args is FE-display-only
on the pending-confirmation early return, not "trusted"); fix the stale test
name in the finding-to-test map.
Verified: odoo-bin --test-enable on a fresh pg14 DB - 0 failed / 0 error of 291
tests across viin_ai_base/agent/approval/workflow; flake8 (Runbot config) clean
on all changed lines (F841 resolved).
|
Killed
|
|
Not finished
|
|
|
|
|
|
merged
[FIX] AI cluster Wave-1: pre-existing security remediation (4 CRITICAL + 13 HIGH)
|
[FIX] viin_ai_approval,viin_ai_base: end-of-wave review fixes (H7 effective scoping, error-log savepoint)
Independent end-of-wave review caught two HIGH issues in the wave-1 fixes:
- H7 RESIDUAL: the WI-3 advisory-cron company scope used ('company_id','in',
self.env.companies.ids), but in the cron context env.companies = ALL companies
(cron user has no allowed_company_ids) -> the scope was a no-op = cross-tenant
processing leak, and its test was tautological (it pre-set allowed_company_ids).
Fixed: pin the advisory cron user_id, and restructure _cron_run_ai_advisory to
iterate companies explicitly with a literal ('company_id','=',company.id) filter
under with_company(company) - isolation no longer depends on env.companies. The
test now runs from the real all-company env and goes RED on the old code
(scan saw companies [1,2,3,4]) -> GREEN.
- error-log savepoint: _record_error_usage now wraps the separate-cursor create in
env.cr.savepoint(), matching the canonical api_request_mixin.log_request pattern.
132/132 viin_ai_base + 95/95 viin_ai_ops/approval tests pass on real odoo-bin.
|
Failed
|
|
|
|
|
|
|
|
merged
Fix dashes
|
Fix dashes
|
Killed
|
|
Not finished
|
|
|
|
|
|
merged
[ADD] P-OPS-4 Ops Cockpit + AI Operations Manager governance (opens M3)
|
[FIX] viin_ai_base: add base.user_admin to demo company B (multi-company invariant)
The viin_ai_base DEMO creates a second res.company ('AI Ops Demo Co B') for the
cross-tenant isolation demo, but a static demo <record> does NOT add the acting
user to the new company the way a real res.company.create does - so it stranded
base.user_admin (Mitchell Admin) outside that company. On the shared Runbot
post-install DB this broke an UNRELATED Viindoo-Standard test with zero static
code coupling:
viin_helpdesk auto-creates one 'General' team per company; 'AI Ops Demo Co B'
sorts before 'YourCompany' (res.company _order='sequence,name', both seq=0) so
its team is created first and gets the lowest id; viin_helpdesk_project's
TestAccessTicket.test_user_who_following_task_or_project_read_ticket does
team.search([], limit=1) AS base.user_admin and the GLOBAL
helpdesk_team_company_rule (no groups -> applies to admin) denies read of a
team in a company admin is not a member of -> AccessError at
helpdesk_ticket.py:336 (team.stage_ids).
Fix: add base.user_admin to company_ai_ops_demo_b.company_ids, restoring the
invariant that the admin/superuser setup account is a member of every company
present in the install. The ops-manager demo user stays main-company-only, so
the cross-tenant isolation demo is unaffected.
Proven by confirm-by-toggle on a live DB (adding admin -> the helpdesk test runs
green; removing the demo company -> green) and by an A/B install (helpdesk_project
alone passes; helpdesk_project + viin_ai_base fails). Guarded by a new red-before-
green regression test TestDemoCompanyMembership (RED without the fix: '1 failed').
Verified: helpdesk test 0 failed/0 error of 1; full viin_ai_base suite 0 failed/0
error of 122 tests (166 total).
|
Succeed
|
|
|
|
|
|
|
|
merged
[ADD] P-OPS-4 Ops Cockpit + AI Operations Manager governance (opens M3)
|
[FIX] viin_ai_approval,viin_ai_ops: suppress force-sent approver mail in routing tests
Runbot marks a build FAILED when the captured test log contains any ERROR
record (odoo lower_logging -> result.had_error_log), even with 0 assertion
failures. Three new test classes drive the real approval-routing path
(action_route -> _submit_to_approval -> request.action_confirm), which
message_post()s an approver notification that is force-sent synchronously.
The tests create fresh res.company / users with no email and run without a
mail.catchall.domain ICP, so the send has no sender address and mail_mail
logs 'failed sending mail ... mail.catchall.domain' at ERROR (20 records),
tripping the Runbot gate although every assertion passes.
These tests assert routing / state / counts / multi-company isolation, NOT
email delivery, so set mail_notify_force_send=False on the class env in
setUpClass: the notification mail stays queued (never synchronously sent in
the test transaction) so no spurious error is logged. Inherited by every
sub-env (all build from self.env.context), so it reaches the message_post
inside action_route().
Affected: TestCockpitPendingApprovalsProvider, TestMultiCompanyApprovalRouting
(viin_ai_approval), TestOpsCockpitAggregation (viin_ai_ops).
Verified locally (fresh DB, no catchall): 20 -> 0 'failed sending mail'
ERROR records, 0 ERROR-level logs, full viin_ai_approval+viin_ai_ops suite
0 failed / 0 error of 85 tests.
|
Failed
|
|
|
|
|
|
|
|
merged
[ADD] P-OPS-4 Ops Cockpit + AI Operations Manager governance (opens M3)
|
[FIX] viin_ai_ops: prettier-format cockpit JS for eslint web/tooling check
Runbot test_pylint/test_eslint (eslint --no-eslintrc -c web/tooling/_eslintrc.json,
prettier 2.8.x, trailingComma es5) flagged 8 prettier/prettier violations on the
new cockpit JS:
- ops_cockpit.js: collapse a single-string _t() call to one line; drop two es5
trailing commas in doAction() argument lists.
- cockpit_tour.js: reformat an assignment ternary (break after '=', deepen indent).
Formatting only, no logic change. Verified clean with prettier 2.8.8
(--tab-width 4 --print-width 100 --trailing-comma es5).
|
Failed
|
|
|
|
|