Pending: 0 Building: 0 Running: 3 Failed: 269
Created Date Type Name Commit Description State Age Up Time Life Time Action
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). Killed
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
merged [ADD] P-OPS-4 Ops Cockpit + AI Operations Manager governance (opens M3) [FIX] viin_ai_ops_brain: sudo Brain reads in approval-rationale evidence write The 'Generate Approval Rationale' button is callable by any user with write on the proposal (no group on the view) and writes an internal audit page under sudo. But the Brain-config reads on that path ran as the calling user: the rationale template content_html, the evidence vault auto_approve flag, and the originating viin.ai.trace business id. A non-Brain (or non-author) user hit AccessError on the vault/template, and the T5 trace attribution silently degraded to the no-trace sentinel under the trace own-records rule. Read all three under sudo (consistent with the already-sudo'd page + link create) so evidence is written with full-fidelity attribution regardless of who triggers it. Adds a red-before- green test: a non-author non-manager trigger records the REAL trace_id. Failed
merged [ADD] P-OPS-4 Ops Cockpit + AI Operations Manager governance (opens M3) [ADD] viin_ai_ops: P-OPS-4 Ops Cockpit dashboard + ops-manager governance gate OWL client action 'Ops Cockpit' under AI > Reporting: a COO/CFO governance pane-of-glass with 6 ACL-respecting metric tiles (pending_approvals, stuck_workflow, fail_rate, cost_per_goal, stale_evidence, ai_action_backlog) via retrieve_ops_cockpit() running as the user (read_group/search_count, no sudo, no raw SQL). Cross-module metrics flow through a decoupling provider hook so viin_ai_ops gains zero dependency edges. Company-wide view gated on group_ai_ops_manager (not base.group_system); tiles drill into the same records within the user's scope. Semantic v17 design tokens (no hardcoded color), AA contrast, responsive. Failed
merged [ADD] viin_ai_workflow: P-OPS-3 Operating Layer workflow adapter + heartbeat routines [FIX] viin_ai_workflow,viin_ai_workflow_automation: capture expected error-path logs in tests Runbot marks a build FAILED when the captured test log contains any WARNING/ERROR record (odoo lower_logging -> result.had_error_log), even with 0 assertion failures. Two tests exercise deliberate error-paths whose operator-facing logs leaked into the captured log: - test_cron_savepoint_isolates_a_failing_routine (WF-8): the poisoned routine raises, _cron_run_routines logs its traceback at ERROR (fault-isolation signal). - test_execute_approved_no_published_version_stays_approved: the unpublished workflow logs two WARNINGs (graceful no-fake-execute signal). Wrap each triggering call in self.assertLogs() so the expected records are captured (propagate=False -> no leak to Runbot) AND asserted to have fired - stronger than mute_logger: the test now proves the except-branch / degradation path actually ran. Production logging is unchanged (the logs are correct and valuable in operation; only the tests own their expected output now). Verified locally (no API key): 0 failed / 18 tests, 0 leaked WARNING/ERROR. Killed
merged [ADD] viin_ai_workflow: P-OPS-3 Operating Layer workflow adapter + heartbeat routines [DOC] docs,decisions: P-OPS-3 shipped + ADR-017 two-module split - roadmap.md: P-OPS-3 marked done (PR #49), module inventory 38 -> 39 (base viin_ai_workflow + bridge viin_ai_workflow_automation), M3 status (P-OPS-1/2/3 done; remaining P-OPS-4 cockpit + 3 heartbeat), ADR table. - operating-model.md 4.5: split tiering (Community base + EE bridge) refines the 4.3 single-module sketch. - ADR-017 (Accepted): two-module split rationale + super() parity invariant + no-exact-pin + Reports menu root owned by viin_ai_base. Runtime-verified Community 11 + Enterprise 18 tests, no API key. Failed
merged [ADD] viin_ai_workflow: P-OPS-3 Operating Layer workflow adapter + heartbeat routines [FIX] viin_ai_workflow,viin_ai_workflow_automation: runtime install + test fixes Found by a full Community + Enterprise install/test run (static review missed these): - work_routine_data.xml: drop name_vi_VN from viin.ai.agent / viin.ai.goal records - those models have no name_vi_VN field (ValueError at load). Routine names (viin.ai.work.routine.name is translate=True) are translated via i18n/vi.po instead; agent/goal names stay English per cluster precedent. - work_routine.py: the scan-domain safe_eval was passed the raw 'datetime' module, which safe_eval forbids. Use the wrapped datetime from odoo.tools.safe_eval (the core ir_filters domain-eval idiom). - viin_ai_workflow_automation form views: add <field workflow_instance_id invisible='1'/> so the stat-button 'invisible' modifier can resolve it (Odoo 17 requires modifier fields present in the view). - test_work_routine WF-3: the base-no-override check used a prefix that also matched the bridge package (viin_ai_workflow vs viin_ai_workflow_automation); anchor it with a trailing dot so the bridge's legitimate override is allowed. Verified: Community 0 failed/11 tests; Enterprise 0 failed/18 tests (no API key). Killed Not finished
merged [ADD] viin_ai_workflow: P-OPS-3 Operating Layer workflow adapter + heartbeat routines [ADD] viin_ai_workflow,viin_ai_workflow_automation: P-OPS-3 workflow adapter Operating Layer P-OPS-3 - wires decide -> orchestrate. viin_ai_workflow (Community base): - viin.ai.work.routine model + heartbeat cron (ar_overdue_review_daily, stock_anomaly_daily, cashflow_summary_weekly): scan -> work_item -> agent analysis -> Brain evidence hook -> optional governed proposal. - Adds routine_id on viin.ai.work.item. Multi-company (with_company, record rule); cron uses savepoint-per-routine, no manual commit. - Work Routines config menu + Routine Activity report; demo data with time-relative dates; vi_VN translations. viin_ai_workflow_automation (Enterprise auto_install bridge): - Adds workflow_instance_id (work_item/proposal) + control_policy.workflow_id. - Overrides viin.ai.action.proposal._execute_approved: route to a workflow.instance when a policy targets one, else super() to the P-OPS-2 single-step path (Community parity preserved). - workflow_id constrained to workflows targeting viin.ai.action.proposal. - depends viin_workflow_automation without an exact version pin. Killed Not finished
merged [ADD] viin_ai_workflow: P-OPS-3 Operating Layer workflow adapter + heartbeat routines [ADD] viin_ai_workflow,viin_ai_workflow_automation: P-OPS-3 workflow adapter Operating Layer P-OPS-3 - wires decide -> orchestrate. viin_ai_workflow (Community base): - viin.ai.work.routine model + heartbeat cron (ar_overdue_review_daily, stock_anomaly_daily, cashflow_summary_weekly): scan -> work_item -> agent analysis -> Brain evidence hook -> optional governed proposal. - Adds routine_id on viin.ai.work.item. Multi-company (with_company, record rule); cron uses savepoint-per-routine, no manual commit. - Work Routines config menu + Routine Activity report; demo data with time-relative dates; vi_VN translations. viin_ai_workflow_automation (Enterprise auto_install bridge): - Adds workflow_instance_id (work_item/proposal) + control_policy.workflow_id. - Overrides viin.ai.action.proposal._execute_approved: route to a workflow.instance when a policy targets one, else super() to the P-OPS-2 single-step path (Community parity preserved). - workflow_id constrained to workflows targeting viin.ai.action.proposal. - depends viin_workflow_automation without an exact version pin. Killed Not finished
merged [ADD] P-OPS-2 approval governance + no-key AI test framework [FIX] viin_ai_approval: mute expected ai_prompt warning in adapter test test_executes_when_action_runs creates an ir.actions.server with state='ai_prompt' but no ai_model_id/ai_prompt/ai_target_field_id (only to pass the executable-action filter); the spy calls through to run(), which logs the expected missing-fields warning. Mute that logger for the test, as test_server_action_ai_prompt already does. Killed
merged [ADD] P-OPS-2 approval governance + no-key AI test framework [FIX] viin_ai_approval_sale: pin QUOTE type to order company in e2e test test_end_to_end_confirm_cron_advisory_posted raised UserError 'Incompatible companies' on a multi-company DB: _make_quote_request searched the QUOTE approval type with limit=1 unfiltered, so it could pick another company's seeded type and fail _check_company at request create. Filter by the order's company (allowing company-less) for FIRST-deterministic behavior regardless of how many companies exist. Verified: 0 failed, 0 error(s) on a full demo DB. Failed
merged [ADD] P-OPS-2 approval governance + no-key AI test framework [FIX] viin_ai_approval: P-OPS-2 review fixes (XSS/PII/multi-company/hasattr) Post-review fixes for the P-OPS-2 approval governance + no-key test PR. CRITICAL - viin_ai_approval: stored XSS in _format_advisory_html - LLM content was interpolated into HTML then wrapped in Markup() at the end (no escaping). Rebuilt all 3 paths with Markup(template) %% arg so every LLM-sourced value (summary/recommendation/signal label+value, fallback raw) is auto-escaped. HIGH - viin_ai_approval: advisory cron processed requests cross-company; sudo() bypasses the company ir.rule. Run each request via with_company(company_id) so its own agent/provider/BYOK key resolves. - viin_ai_approval: ir_cron_advisory.xml missing noupdate=1 - upgrades reset the cron and silently re-enable LLM calls. Added noupdate=1. - viin_ai_approval_sale: payment_term_id.name (free-text PII) was sent to the LLM. Replaced with a PII-free payment_term_category derived from term lines. - viin_ai_ops_brain: evidence page landed ai_review_status='n_a' instead of honouring vault.auto_approve_ai_content; and page/link create lacked sudo() (AccessError for non-Brain-editor users). Set status explicitly + sudo(). hasattr existence-probe cleanup (ref Viindoo/odoo-mcp-client#63) - viin_ai_approval_sale: commercial_partner_id does not exist on sale.order - hasattr was always False, so credit-headroom always used the wrong partner. Fixed to partner_id.commercial_partner_id (real hidden bug). - account credit fields are guaranteed in the closure -> direct access. - margin/margin_percent (sale_margin soft dep, not in closure) -> use 'f' in source._fields. Base generic probes -> 'f' in source._fields too. MED/LOW - Dedup _has_executable_code/_has_real_code into viin_ai_approval/hooks.py. - _stub_provider patches the registry class (was import-path) like _stub_llm. - account: date.today()->fields.Date.today(); fix invalid payment_state key in topic prompt. ops_brain: goal fallback model + inheriting view id/name. Tests - Added stored-XSS regression tests, real PII-absent assertions (account+sale), ai_review_status-follows-vault-policy test. - Replaced duck-typed-fake sale signal tests with real-record ORM tests + two helpers (FIRST-deterministic skipTest when sale_margin/account absent). Killed Not finished
merged [ADD] viin_ai_approval(+account,+sale,+ops_brain): P-OPS-2 approval governance + AI advisory on human approvals [ADD] viin_ai_approval,viin_ai_ops_brain,test_viin_ai: P-OPS-2 approval governance + no-key AI test framework Phase 3.8 P-OPS-2 on top of viin_ai_ops, wired to the existing OmniApproval engine (viin_approval, tvtmaaddons17), workflow-independent (behaviour + tests identical with or without viin_workflow_automation), plus a cross-cutting no-key AI/LLM test framework. Status docs (roadmap + AGENTS) reconciled. SSOT: docs/ai/operating-model.md, ADR-015/016. == P-OPS-2 approval governance == Concern A - "Approval OF AI" (viin_ai_approval + viin_ai_ops_brain): a side-effecting viin.ai.action.proposal (medium/high risk, or a runs_as_sudo tool) is routed to a human viin.approval.request; on the human decision the proposal state syncs (approved/rejected/ draft) and the approved action is handed to execution. State sync is DATA-DRIVEN via the approval type's code_* hooks (code_validate_post / code_refuse_post / code_cancel_post) calling the proposal's public callbacks - NOT action_* method overrides (the idiomatic OmniApproval way, which is why post-approval execution is environment-independent). Captures trace_id / prompt_snapshot / iteration_index into approval evidence. viin_ai_ops_brain writes an attributed "Approval Rationale" Brain page (T5: is_ai_authored + agent + trace_id + confidence; html_escape on untrusted text). Concern B - "AI IN approval" (viin_ai_approval infra + per-app pilots): posts an AI risk- advisory card (risk score + summary + recommendation + signals) to a human approval request's chatter, for the approver - ADVISORY-ONLY (no auto-approve). Data-driven trigger: code_confirm_post enqueues (none -> pending) and wakes a cron; the cron runs the LLM out-of-band, using cr.savepoint() per record + the ir.cron row-lock for non-concurrency (no manual cr.commit). Graceful 'skipped' when no BYOK key is configured; silent 'error' on failure. Per-app pilots (ADR-015 convention: auto_install bridge, data-XML agent/topic seed, non-clobbering post_init_hook, PII-minimised signal builder): viin_ai_approval_account (CUSTOMER_INVOICE / VENDOR_PAYMENTS / CUSTOMER_REFUNDS) + viin_ai_approval_sale (QUOTE). == No-key AI/LLM test framework == L1 - shared unit-test helpers in viin_ai_base/tests/ (not on the runtime path): canonical FakeResponse, result builders (make_llm_call_result / make_completion_result / make_stream_chunks / make_fake_embed_fn), and AIStubMixin (_stub_llm / _stub_stream / _stub_provider patching the registry-class agent seam) + seed_pgvector_rows. 10 legacy test files converge onto these; 5 disconnected stub patterns (3 _FakeResponse copies, 2 identical fake_embed, 3 raw-SQL pgvector blocks) are deduplicated. The provider-inheritance test is hardened to assert the resolved MRO instead of the _inherit attribute. L2 - test_viin_ai: a separate TEST-ONLY module (Odoo test_* convention) the customer NEVER installs (installable, no auto_install, in no product dependency closure; dev/demo/CI only -> customer prod carries zero fabrication code). Two roles: a gated mock LLM provider (overrides one seam, make_request, for api_protocol='mock'; every protocol-keyed helper falls through to the openai_compat shape so cost-cap / PII redaction / normalize / usage.log / trace all run real; a fail-closed gate in models/gate.py - @api.constrains on the vendor + a refusal in make_request - so no fabrication is reachable when the gate is closed, preserving the BYOK invariant) AND the home for test cases that cannot live in a production module (cross-module / full-stack / mock-dependent). == Docs == ADR-015 (Concern B per-app advisory pattern), ADR-016 (shared AI test framework + gated test_viin_ai placement + L3 rejection), docs/ai/testing.md (no-key testing SSOT: decision tree, helper API, coverage fence, @tagged rule, Runbot lint/test commands). Pointers from AGENTS.md §6, docs/ai/README.md, docs/decisions/README.md. roadmap.md + AGENTS.md §3 reconciled: P-OPS-2 shipped (flagging that the original spec assumed action_* overrides while the shipped path is data-driven code_* hooks); gate M3 still needs P-OPS-3 (viin_ai_workflow) + P-OPS-4 (ops cockpit) + heartbeat routines. == Verification == No-key: Community 376/376 + EE 304/304 (with viin_workflow_automation present), 0 failed / 0 error. Code-quality (test_lint + test_pylint, exact Runbot config): flake8 + pylint_odoo clean, no lint suppressions; manifest/RST clean. Live demo: a real sale-quotation approval confirm -> cron -> agent.run -> mock provider yields a genuine advisory card (no network, no key, no canned message_post). Failed
merged [ADD] viin_ai_approval(+account,+sale,+ops_brain): P-OPS-2 approval governance + AI advisory on human approvals [ADD] viin_ai_*: no-key AI/LLM test framework (helpers + test_viin_ai module + docs) Give every viin_ai_* module a way to test LLM-dependent logic deterministically without an API key and without network, and unlock a live no-key demo - without weakening the BYOK invariant (no fabrication code reachable in a customer prod DB). A code-grounded audit found 5 disconnected stub patterns across 15 test files (3 _FakeResponse copies, 2 byte-identical fake_embed, 3 raw-SQL pgvector blocks, no canonical response shape). See docs/ai/testing.md + ADR-016 for the rationale. L1 - shared unit-test helpers in viin_ai_base/tests/ (not on the runtime path): - fake_response.py: one canonical FakeResponse. - builders.py: make_completion_result / make_llm_call_result / make_stream_chunks / make_fake_embed_fn - single-sourced result shapes (Appendix A). - common.py: AIStubMixin with _stub_llm / _stub_stream / _stub_provider context managers patching the registry-class agent seam (_do_llm_call / _do_llm_stream), plus seed_pgvector_rows. 10 legacy test files converge onto these; the duplicated _FakeResponse / fake_embed / inline result dicts and raw-SQL blocks are deleted. L2 - test_viin_ai: the cluster's TEST-ONLY module (Odoo test_* convention, cf. test_pylint) the customer NEVER installs (installable but no auto_install, in no product dependency closure, nothing depends on it; dev/demo/CI only -> customer prod carries zero fabrication code). Two roles: (1) a gated mock LLM provider; (2) the home for test cases that cannot live in a production module (cross-module / full-stack / mock-dependent). It overrides one seam (viin.ai.provider.make_request) for api_protocol='mock'; every protocol-keyed helper already falls through to the openai_compat shape, so cost-cap, PII redaction, normalize, usage.log and trace all run real - only the network call is faked (a local _MockResponse; models/ never imports the tests-only FakeResponse). Fail-closed gate (SSOT in models/gate.py): an @api.constrains on the vendor (a 'mock' vendor cannot persist outside test/demo) AND a refusal in make_request (never fabricates when the gate is closed). Gate open iff --test-enable OR registry test-mode OR ir.config_parameter test_viin_ai.enabled='1' (demo data sets it). 11 tests: 6 fail-closed gate proofs + 5 no-key full-stack proofs (real agent.run / call_embedding -> mock -> usage.log written). Also harden test_provider_inherits_mixin_and_thread: it asserted on viin.ai.provider._inherit (which reports only the last-loaded module's own declaration, so test_viin_ai legitimately flipped it) - rewritten to assert the resolved MRO + the capabilities the bases contribute; still fails if a base is genuinely removed. Docs: docs/ai/testing.md (SSOT decision tree + helper API + coverage fence + @tagged rule + Runbot lint/test commands with the W503/W504 trap called out), ADR-016 (decision + gated-placement rationale + L3 rejection), pointers from AGENTS.md §6, docs/ai/README.md, docs/decisions/README.md. Verified no-key: Community 376/376 + EE 304/304 (with viin_workflow_automation present), 0 failed 0 error; flake8 + pylint_odoo (exact Runbot config) clean, 10.00/10, no lint suppressions. Live demo: a real sale-quotation approval confirm -> cron -> agent.run -> mock provider yields a genuine advisory card (no network, no key, no canned message_post). Killed Not finished
merged [ADD] viin_ai_approval(+account,+sale,+ops_brain): P-OPS-2 approval governance + AI advisory on human approvals [ADD] viin_ai_*: no-key AI/LLM test framework (helpers + test_viin_ai module + docs) Give every viin_ai_* module a way to test LLM-dependent logic deterministically without an API key and without network, and unlock a live no-key demo - without weakening the BYOK invariant (no fabrication code reachable in a customer prod DB). A code-grounded audit found 5 disconnected stub patterns across 15 test files (3 _FakeResponse copies, 2 byte-identical fake_embed, 3 raw-SQL pgvector blocks, no canonical response shape). See docs/ai/testing.md + ADR-016 for the rationale. L1 - shared unit-test helpers in viin_ai_base/tests/ (not on the runtime path): - fake_response.py: one canonical FakeResponse. - builders.py: make_completion_result / make_llm_call_result / make_stream_chunks / make_fake_embed_fn - single-sourced result shapes (Appendix A). - common.py: AIStubMixin with _stub_llm / _stub_stream / _stub_provider context managers patching the registry-class agent seam (_do_llm_call / _do_llm_stream), plus seed_pgvector_rows. 10 legacy test files converge onto these; the duplicated _FakeResponse / fake_embed / inline result dicts and raw-SQL blocks are deleted. L2 - test_viin_ai: the cluster's TEST-ONLY module (Odoo test_* convention, cf. test_pylint) the customer NEVER installs (installable but no auto_install, in no product dependency closure, nothing depends on it; dev/demo/CI only -> customer prod carries zero fabrication code). Two roles: (1) a gated mock LLM provider; (2) the home for test cases that cannot live in a production module (cross-module / full-stack / mock-dependent). It overrides one seam (viin.ai.provider.make_request) for api_protocol='mock'; every protocol-keyed helper already falls through to the openai_compat shape, so cost-cap, PII redaction, normalize, usage.log and trace all run real - only the network call is faked (a local _MockResponse; models/ never imports the tests-only FakeResponse). Fail-closed gate (SSOT in models/gate.py): an @api.constrains on the vendor (a 'mock' vendor cannot persist outside test/demo) AND a refusal in make_request (never fabricates when the gate is closed). Gate open iff --test-enable OR registry test-mode OR ir.config_parameter test_viin_ai.enabled='1' (demo data sets it). 11 tests: 6 fail-closed gate proofs + 5 no-key full-stack proofs (real agent.run / call_embedding -> mock -> usage.log written). Also harden test_provider_inherits_mixin_and_thread: it asserted on viin.ai.provider._inherit (which reports only the last-loaded module's own declaration, so test_viin_ai legitimately flipped it) - rewritten to assert the resolved MRO + the capabilities the bases contribute; still fails if a base is genuinely removed. Docs: docs/ai/testing.md (SSOT decision tree + helper API + coverage fence + @tagged rule + Runbot lint/test commands with the W503/W504 trap called out), ADR-016 (decision + gated-placement rationale + L3 rejection), pointers from AGENTS.md §6, docs/ai/README.md, docs/decisions/README.md. Verified no-key: Community 376/376 + EE 304/304 (with viin_workflow_automation present), 0 failed 0 error; flake8 + pylint_odoo (exact Runbot config) clean, 10.00/10, no lint suppressions. Live demo: a real sale-quotation approval confirm -> cron -> agent.run -> mock provider yields a genuine advisory card (no network, no key, no canned message_post). Failed
merged [ADD] viin_ai_approval(+account,+sale,+ops_brain): P-OPS-2 approval governance + AI advisory on human approvals [ADD] viin_ai_*: no-key AI/LLM test framework (helpers + gated viin_ai_mock + docs) Give every viin_ai_* module a way to test LLM-dependent logic deterministically without an API key and without network, and unlock a live no-key demo - without weakening the BYOK invariant (no fabrication code reachable in a customer prod DB). A code-grounded audit found 5 disconnected stub patterns across 15 test files (3 _FakeResponse copies, 2 byte-identical fake_embed, 3 raw-SQL pgvector blocks, no canonical response shape). See docs/ai/testing.md + ADR-016 for the rationale. L1 - shared unit-test helpers in viin_ai_base/tests/ (not on the runtime path): - fake_response.py: one canonical FakeResponse. - builders.py: make_completion_result / make_llm_call_result / make_stream_chunks / make_fake_embed_fn - single-sourced result shapes (Appendix A). - common.py: AIStubMixin with _stub_llm / _stub_stream / _stub_provider context managers patching the registry-class agent seam (_do_llm_call / _do_llm_stream), plus seed_pgvector_rows. 10 legacy test files converge onto these; the duplicated _FakeResponse / fake_embed / inline result dicts and raw-SQL blocks are deleted. L2 - viin_ai_mock: a SEPARATE gated module the customer NEVER installs (auto_install False, in no product dependency closure; installed only on dev/demo/CI). Overrides one seam (viin.ai.provider.make_request) for api_protocol='mock'; every protocol-keyed helper already falls through to the openai_compat shape, so cost-cap, PII redaction, normalize, usage.log and trace all run real - only the network call is faked (a local _MockResponse; models/ never imports the tests-only FakeResponse). Fail-closed gate (SSOT in models/gate.py): an @api.constrains on the vendor (a 'mock' vendor cannot persist outside test/demo) AND a refusal in make_request (never fabricates when the gate is closed). Gate open iff --test-enable OR registry test-mode OR ir.config_parameter viin_ai_mock.enabled='1' (demo data sets it). 11 tests: 6 fail-closed gate proofs + 5 no-key full-stack proofs (real agent.run / call_embedding -> mock -> usage.log written). Also harden test_provider_inherits_mixin_and_thread: it asserted on viin.ai.provider._inherit (which reports only the last-loaded module's own declaration, so viin_ai_mock legitimately flipped it) - rewritten to assert the resolved MRO + the capabilities the bases contribute; still fails if a base is genuinely removed. Docs: docs/ai/testing.md (SSOT decision tree + helper API + coverage fence + @tagged rule + Runbot lint/test commands with the W503/W504 trap called out), ADR-016 (decision + gated-placement rationale + L3 rejection), pointers from AGENTS.md §6, docs/ai/README.md, docs/decisions/README.md. Verified no-key: Community 376/376 + EE 304/304 (with viin_workflow_automation present), 0 failed 0 error; flake8 + pylint_odoo (exact Runbot config) clean, 10.00/10, no lint suppressions. Live demo: a real sale-quotation approval confirm -> cron -> agent.run -> mock provider yields a genuine advisory card (no network, no key, no canned message_post). Killed Not finished
merged [ADD] viin_ai_approval(+account,+sale,+ops_brain): P-OPS-2 approval governance + AI advisory on human approvals [ADD] viin_ai_approval,viin_ai_approval_account,viin_ai_approval_sale,viin_ai_ops_brain: P-OPS-2 approval governance + AI advisory on human approvals Phase 3.8 P-OPS-2. Two cleanly separated concerns on top of viin_ai_ops, wired to the existing OmniApproval (viin_approval) engine. Behaviour + tests do NOT depend on viin_workflow_automation (identical with or without it installed). Concern A - "Approval OF AI" (viin_ai_approval, viin_ai_ops_brain): - Routes a side-effecting viin.ai.action.proposal (medium/high risk, or a runs_as_sudo tool) to a human viin.approval.request; on the human decision the proposal state syncs (approved/rejected/draft) and the approved action is handed to execution. State sync is data-driven via the approval type's code_* hooks (code_validate_post / code_refuse_post / code_cancel_post) calling the proposal's public callbacks - NOT via action_* overrides (the viin_approval idiom). - Post-approval execution is environment-independent (no workflow-engine gate; P-OPS-3 viin_ai_workflow overrides execution later). - viin_ai_ops_brain writes an attributed "Approval Rationale" Brain page (T5: is_ai_authored + agent + trace + confidence; html_escape on untrusted text). Concern B - "AI IN approval" (viin_ai_approval infra + per-app pilots): - Posts an AI risk-advisory card (risk score + summary + recommendation + signals) to the chatter of a human approval request, for the approver. Advisory only (no auto-approve). Data-driven trigger: code_confirm_post enqueues (none->pending) and wakes a cron; the cron runs the LLM out-of-band and posts the card. - The cron uses cr.savepoint() per record for failure isolation and relies on the ir.cron row-lock for non-concurrency - NO manual cr.commit(). Graceful 'skipped' when the BYOK key is not configured; silent 'error' on failure (no chatter noise). - Per-app pilots (ADR-015 convention; auto_install; data-XML agent/topic seed + a non-clobbering post_init_hook wiring the approval type by stable code; PII-minimised signal builder): viin_ai_approval_account (CUSTOMER_INVOICE / VENDOR_PAYMENTS / CUSTOMER_REFUNDS) and viin_ai_approval_sale (QUOTE). - UX: band-coloured chatter card (AA-contrast, "AI advisory - not a decision" disclaimer), a form status badge, and a list AI-risk column + "High risk" filter. Cluster decoupling kept: only viin_ai_approval depends viin_approval; the per-app modules reach it through the bridge. ADR-015 records the per-app advisory convention (decisions/README updated). Tests: Odoo native TransactionCase/@tagged, behaviour-protecting (no live LLM - the provider boundary is stubbed at agent.run / _do_llm_call). 36 tests, 0 failed and 0 error in BOTH a Community addons set AND a full Viindoo-EE set (with viin_workflow_automation). flake8 + the full test_pylint enable-list both pass with NO lint suppressions. Killed Not finished
merged [ADD] viin_ai_approval(+account,+sale,+ops_brain): P-OPS-2 approval governance + AI advisory on human approvals [REF] viin_ai_approval: advisory cron uses savepoint isolation, not manual commit Replaces the dangerous manual self.env.cr.commit() in the advisory cron (and the pylint disable that hid it) with the idiomatic Odoo pattern: - The ir.cron row lock (FOR NO KEY UPDATE SKIP LOCKED, ir_cron.py) already prevents the cron from running concurrently with itself, so the manual 'claim' commit was never needed. - Per-record isolation now uses 'with self.env.cr.savepoint()'; the ir.cron wrapper commits once on clean return (the pattern viin_account_auto_transfer / viin_zalo use). At-least-once retry on the next tick; no transaction breakage. - Tests no longer patch cr.commit (that patch was a symptom of the bad design). Also resolves R8180 without a disable: Concern A + Concern B are now one viin.approval.request model class (one class per model per module), the two concerns kept in clearly-marked sections; advisory_runner.py merged in + removed. Fixed the SALE-6 fixture to map partner/currency/user (the OmniApproval origin sync would otherwise null the sale.order partner_id). 36/36 tests pass; flake8 + full pylint_odoo enable-list both exit 0 with NO disables. Killed Not finished
merged [ADD] viin_ai_approval(+account,+sale,+ops_brain): P-OPS-2 approval governance + AI advisory on human approvals [FIX] viin_ai_approval,viin_ai_approval_account,viin_ai_approval_sale: pass Runbot test_pylint lint Runbot Code-Quality stage (test_pylint: pylint_odoo + flake8) was red while local --test-enable passed (the lint suite is not run by --test-enable). Resolved every reported violation: - E8102 invalid-commit (advisory_runner cron commits) -> documented + pylint disable - W8150 odoo-addons-relative-import (tests importing own module) -> relative imports - R8180 consider-merging-classes-inherited (Concern A/B separate _inherit) -> documented disable - E741 ambiguous 'l', F401 unused imports, E303, F841 unused var - W503 line break before binary operator -> restructured so no operator sits at a line boundary Verified: exact test_flake8 + full test_pylint enable-list both exit 0; 36/36 tests still pass. Killed Not finished
merged [DOC] docs,decisions: ADR-013 voice modality + ADR-014 dual deployment BYOK/managed [DOC] docs,decisions: ADR-013 voice modality + ADR-014 dual deployment BYOK/managed Tài liệu hoá voice agent modality cho cụm AI (doc-only, chưa code). - ADR-013 (Proposed): voice loop audio in -> STT -> agentic loop CŨ -> TTS -> audio out, BYOK-first. Stack tham chiếu license sạch thương mại + tiếng Việt (PhoWhisper/faster-whisper, Chatterbox, Pipecat/LiveKit), framing reference-default + swappable. Loại VibeVoice (vendor khuyến cáo không dùng thương mại + watermark + supply risk + voice cloning vướng Luật 91/2025). Ranh giới: user-initiated voice = execute theo ACL user (không proposal); AI-proactive = proposal-approve-workflow. Anti-misfire cho tool requires_confirmation (TTS readback + STT ack + nonce gate). - ADR-014 (Proposed, CẦN CEO ratify): phân định #11 BYOK - cấm proxy LLM vendor cloud giữ nguyên; managed compute cho model open-source self-host là phạm trù riêng (bán compute Viindoo). BYOK trước; managed Phase 9 (cuối roadmap, gap Phase 8). Invariant chặn managed proxy LLM cloud. - Cập nhật architecture (voice_interactive mode), data-models (call_tts, model_type=tts, usage_log audio billing, deployment_model), operating-model, security (§5.5 voice/biometric, PII forced-on external STT), roadmap (Phase 7.1 voice + Phase 9 managed), conventions (audio codec), integration (voice evidence -> Brain), AGENTS pointer #11 (CHƯA HIỆU LỰC tới khi ratify). Killed Not finished
merged [ADD] viin_ai_approval(+account,+sale,+ops_brain): P-OPS-2 approval governance + AI advisory on human approvals [DOC] docs,decisions: ADR-013 Concern-B per-app advisory pattern Records the reusable viin_ai_approval_<app> convention (auto_install bridge, data-XML agent/topic seed, non-clobbering post_init_hook by stable code, PII-minimised signal builder, 5-test checklist) extracted from the two shipped pilots (account + sale). Status: proposed, pending owner sign-off. Failed
merged [ADD] viin_ai_approval(+account,+sale,+ops_brain): P-OPS-2 approval governance + AI advisory on human approvals [REF] viin_ai_approval: data-driven state sync via approval-type code_* hooks Follow the viin_approval idiom: do NOT override action_validate/refuse/cancel in Python to trigger side-effects. Instead the seeded approval.request.type carries code_validate_post / code_refuse_post / code_cancel_post inline hooks (safe_eval) that call the proposal's public callbacks _on_approval_validated/refused/cancelled. - viin_approval_request.py: remove the action_* overrides + _propagate_to_work_item; keep only the evidence/link fields (ai_proposal_id, ai_trace_id, ai_prompt_snapshot, ai_iteration_index). - action_proposal.py: add the public callbacks (approved + work-item start + honest single-step execute; rejected; back-to-draft). - data/approval_request_type.xml: the three code_* hooks dispatch to the callbacks via ai_proposal_id. Verified identical green (0 failed of 10) in BOTH Community and full-EE addons sets; grep confirms no action_* override remains. Tests assert business outcomes (proposal approved/rejected/draft after the decision), mechanism-agnostic. Killed Not finished
merged [ADD] viin_ai_approval(+account,+sale,+ops_brain): P-OPS-2 approval governance + AI advisory on human approvals [ADD] viin_ai_approval,viin_ai_ops_brain: P-OPS-2 approval adapter + Brain evidence Wire the propose -> DECIDE -> remember steps of the AI operating model (Phase 3.8, opens gate M3). Two new Community-compatible modules + small reconciles: * viin_ai_approval (depends viin_ai_ops, viin_approval 0.2.2): maps viin.ai.action.proposal <-> viin.approval.request. action_route submits a pending proposal to an approval request; action_validate/refuse/cancel overrides sync state back to the proposal (+ work item). Sudo/high-risk proposals are forced through approval (never auto-execute). Community fallback executes an approved proposal single-step via its mapped ir.actions.server (state='ai_prompt') under sudo, and only marks 'executed' when a side effect actually ran - otherwise it stays 'approved' (execution deferred to P-OPS-3 / the Enterprise viin_workflow_automation when present). Evidence (trace, prompt snapshot, iteration index) captured on the request. * viin_ai_ops_brain (depends viin_ai_ops, viin_brain): generates a Brain "Approval Rationale" evidence page per proposal with mandatory T5 attribution (is_ai_authored + agent + trace + confidence), untrusted text html-escaped, linked back to proposal/work item/goal. Idempotent. * viin_ai_ops: base proposal form now provides one shared oe_button_box (first child of sheet) so adapter stat buttons hoist to a single control-panel box. * viin_ai_agent: deprecated stub anchors (viin.ai.schedule, viin.ai.approval.chain) docstrings point to the shipped viin_ai_approval adapter; field anchors retained for v17 schema compatibility (removal deferred). Tests: 54 Odoo-native tests pass (ACL, 2-way sync, sudo/high-risk enforce, Community fallback honesty + Enterprise-defer, T5 attribution, cross-module integration). The workflow-dependent fallback tests force _workflow_automation_installed deterministically (FIRST-compliant) so they pass whether or not the Enterprise module is installed. Visual UI/UX review PASS (single button box, AA contrast, theme-faithful, console clean). Killed Not finished
merged [ADD] viin_ai_approval(+account,+sale,+ops_brain): P-OPS-2 approval governance + AI advisory on human approvals [ADD] viin_ai_approval,viin_ai_ops_brain: P-OPS-2 approval adapter + Brain evidence Wire the propose -> DECIDE -> remember steps of the AI operating model (Phase 3.8, opens gate M3). Two new Community-compatible modules + small reconciles: * viin_ai_approval (depends viin_ai_ops, viin_approval 0.2.2): maps viin.ai.action.proposal <-> viin.approval.request. action_route submits a pending proposal to an approval request; action_validate/refuse/cancel overrides sync state back to the proposal (+ work item). Sudo/high-risk proposals are forced through approval (never auto-execute). Community fallback executes an approved proposal single-step via its mapped ir.actions.server (state='ai_prompt') under sudo, and only marks 'executed' when a side effect actually ran - otherwise it stays 'approved' (execution deferred to P-OPS-3). Evidence (trace, prompt snapshot, iteration index) captured on the request. * viin_ai_ops_brain (depends viin_ai_ops, viin_brain): generates a Brain "Approval Rationale" evidence page per proposal with mandatory T5 attribution (is_ai_authored + agent + trace + confidence), untrusted text html-escaped, linked back to proposal/work item/goal. Idempotent. * viin_ai_ops: base proposal form now provides one shared oe_button_box (first child of sheet) so adapter stat buttons hoist to a single control-panel box. * viin_ai_agent: deprecated stub anchors (viin.ai.schedule, viin.ai.approval.chain) docstrings point to the shipped viin_ai_approval adapter; field anchors retained for v17 schema compatibility (removal deferred). Tests: 53 Odoo-native tests pass (ACL, 2-way sync, sudo/high-risk enforce, Community fallback honesty, T5 attribution, cross-module integration). Visual UI/UX review: PASS (single button box, AA contrast, theme-faithful, console clean). Failed
merged [DOC] Vision/mission first-read + ADR-012 bridge re-purpose + reconcile drift [DOC] docs,AGENTS: vision/mission first-read + ADR-012 bridge re-purpose + reconcile drift Anti-drift: add a single SSOT "Vision & Mission" block at the top of AGENTS.md (auto-loaded; CLAUDE.md/GEMINI.md @import it) and make it the first mandatory read in section 0, plus a vision-first pointer in docs/README.md. When code/roadmap drift, vision is the anchor. ADR-012 (new): seed master data per application. Brain viin_brain_<app> modules are re-purposed as seed-master-data carriers per app (vault/page/template), and each viin_ai_<app> connector seeds AI master data (agent/topic/tool/SQL template) per app. This overrides ONLY the "bridges redundant -> removal at v1.2" clause of ADR-001; the mail.thread universal-sidebar decision stands. ADR-001 gets a partially-superseded-by pointer; decisions index updated. Reconcile drift with shipped state: - roadmap §2: add viin_ai_ops (P-OPS-1) + viin_ai_editor (ADR-010) + 5 connector stubs; fix viin_ai_agent version 0.2.1; ADR count 8->10. - roadmap §7: add viin_ai_editor row; bridges re-scoped to seed carriers; total 31->38. - roadmap §8: add ADR-010/012 rows; WI count 4->8; clarify ADR-009 (no file yet). - roadmap §10/§11 + Track B timeline + M2 gate: drop stale "remove v1.2" wording. - AGENTS.md §3 status: Track A stopped at P-OPS-1, Track B (P16/ADR-010/WI-007) DONE. Doc-only; no code changes. Reviving bridges + writing seed data is a later code phase. Killed Not started Not finished
merged [IMP] viin_brain: Brain editor UI/UX polish (#44) [IMP] viin_brain: Brain editor UI/UX polish - white canvas, AA contrast, inline icon, symmetric padding, mobile collapse, declutter chrome, a11y labels (#44) Addresses issue #44 (P16 follow-up): white-paper canvas, WCAG-AA breadcrumb + property labels, inline page icon, symmetric editor padding, mobile single-column collapse, decluttered context-pane chrome (overflow menu), deduped vault selector, inline record-mention chips, property input label/id association. Includes the code-review cleanup and tour updates for the new overflow navigation. Killed Not finished
merged [IMP] viin_brain: Brain editor UI/UX polish (#44) [IMP] viin_brain: Brain editor UI/UX polish - white canvas, AA contrast, inline icon, symmetric padding, mobile collapse, declutter chrome, a11y labels (#44) Addresses issue #44 (P16 follow-up). Includes code-review cleanup (shadow token, deduped chip styling, t-foreach overflow menu, centralised property a11y attrs, structural header layout, label-for only on input-bearing types), eslint/prettier formatting, and tour updates for the overflow tab navigation. Failed