|
|
|
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
|
|
|
|
|
|
|
|
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, and label-for only on input-bearing property types.
|
Failed
|
|
|
|
|
|
|
|
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)
|
Killed
|
|
Not finished
|
|
|
|
|
|
merged
[IMP] P16: Editor Extension Framework - modular Brain/AI editor (OdooEditor migration)
|
[DOC] docs,AGENTS: ADR-010 editor framework + reconcile docs with shipped P16
ADR-010 Editor Extension Framework + WI-007/WI-008; reconcile brain/ai
architecture, ai-bridge, ui-design, conventions, roadmap and decision index
with the shipped code; AGENTS module map adds viin_ai_editor + engine desc.
|
Killed
|
|
|
|
|
|
|
|
merged
[IMP] P16: Editor Extension Framework - modular Brain/AI editor (OdooEditor migration)
|
[IMP] P16 review supplement: fix review findings + Brain editor UX polish (#2)
* [FIX] viin_ai_ops: null-guard ListRenderer.setDefaultColumnWidths against intermittent DOM race
Intermittent TypeError "Cannot read properties of null (reading 'style')"
in core Odoo 17 ListRenderer.setDefaultColumnWidths() surfaces on the
Action Proposals list when the view re-renders with zero records (isEmpty
toggle races thead commit to DOM, querySelector returns null).
Adds a defensive monkey-patch in viin_ai_ops that overrides the method
with an identical but null-safe implementation: if headerEl is null the
column iteration step is skipped instead of crashing. This matches the
upstream fix strategy applied in Odoo 18 via useMagicColumnWidths.
BACKPORT FROM: Odoo 17 core list_renderer.js (upstream fixed in 18.0)
REMOVE WHEN UPGRADING TO: 18.0
* [FIX] viin_ai_editor: surface UserError message in AI block error state
Odoo 17 RPCError shape wraps the real UserError text in err.data.message
while err.message is always the opaque "Odoo Server Error" wrapper.
The catch in AiBlock._generate now reads err.data.message first so users
see the actionable UserError (e.g. "No AI agent configured") instead of
the unhelpful wrapper.
- ai_block.js: prioritise err.data.message || err.message || fallback _t()
- conventions.md: add JSON-RPC error handling convention (SSOT: chat_panel.js)
- test_ai_block_state_machine.js: add 2 cases covering RPCError shape and
plain-Error fallback
* [IMP] viin_web_editor,viin_brain,viin_ai_brain: dedup powerbox native commands (WI-007)
Introduce a generic suppress-native registry in viin_web_editor so downstream
modules can declaratively remove native OdooEditor/Wysiwyg powerbox commands that
they replace, without the engine hardcoding any consumer-specific names.
Changes:
- viin_web_editor/powerbox_registry.js: export new registry
viin_web_editor.powerbox_suppress_native with suppressor shape
{matcher, contextGate?}
- viin_web_editor/wysiwyg_patch.js: filter upCommands (native) through all
registered suppressors before concatenating extra commands; exceptions inside
suppressors are caught per-entry (fail-safe)
- viin_brain/slash_commands.js: register five suppressors (headings/quote/code/
bullet/numbered, all context-gated to .o_brain_editor_body_edit); fix
brain_bullet_list and brain_numbered_list to use toggleList (identical to
native, preserves toggle-off semantics)
- viin_ai_brain/powerbox_commands.js: register suppressor for native ChatGPT
(fa-magic) gated on bridge.available=true
- docs/decisions/wi-007-powerbox-suppress-native.md: new append-only WI,
Refines ADR-010
* [FIX] viin_ai_brain: multi-company filter agent lookup (B1)
Extract the Knowledge Base Assistant agent lookup into a single SSOT
helper `viin_ai_brain/controllers/common.py::find_kba_agent()` that
enforces `company_id in [env.company.id, False]` on every search.
Before this fix wysiwyg.py `_get_agent()` searched by name only
with no company filter, leaking cross-tenant agent records. ai_block.py
already had the filter but in a local function. Both controllers now
import `find_kba_agent` from `controllers/common.py`, eliminating the
duplicate logic and the security gap.
* [IMP] viin_ai_brain: topic-based agent lookup + CTA (MED-2)
Update `controllers/common.py::find_kba_agent` to resolve the KBA agent
via the `viin_ai_brain.topic_brain_assistant` topic xmlid instead of the
display name. This makes lookup name-independent: renaming the agent for
i18n or branding purposes does not break any controller path.
When no agent with the topic is found, raises UserError with an
actionable CTA pointing to AI > Configuration > Agents
(viin_ai_agent.action_viin_ai_agent).
Frontend changes:
- ai_bridge_service.js: pass topicXmlId instead of hard-coded topicName
for ChatPanel so the chat panel can resolve the topic server-side.
- ai_editor_backend_service.js: remove "Knowledge Base Assistant"
hard-coded fallback for agent_name; server is the SSOT (empty string
sentinel on RPC success avoids masking real lookup failures).
Tests (Odoo native TransactionCase, @tagged post_install):
- TestKbaAgentLookupMultiCompany: B1 isolation - company A env cannot
resolve company B's KBA agent; global agents (company_id=False) are
visible from any company; missing-company agent raises UserError+CTA.
- TestKbaAgentTopicLookup: rename-safe lookup (MED-2); detaching topic
raises UserError+CTA; wysiwyg endpoint surfaces CTA end-to-end.
- Updated TestBrainBridgeCommon to attach the KBA topic to the fixture
agent, and updated _ensure_brain_agent in tour tests likewise.
- Updated test_commit_attribution_raises_when_no_kba_agent: detach topic
(not rename) to simulate missing agent under topic-based lookup.
Verified: 0 failed, 0 error of 57 tests (wave_wid_test ephemeral DB).
* [IMP] viin_brain: fix AI badge wording to reflect installed-not-ready semantics
Badge text changed from "AI ready" to "AI features available" and tooltip
updated to "AI features are installed - configure a provider & agent to use
them." to avoid implying the AI is operational before a provider and agent
are configured (Option B wording-only fix, WI-E).
* [DOC] docs: WI-008 PR #43 deferred follow-ups tracking + roadmap corrections
Create docs/decisions/wi-008-pr43-deferred-followups.md (append-only WI):
- WI-F (MED-3): Provider Test Connection button -> viin_ai_base Layer 0 backlog
- WI-G: L2 active-default policy (needs ADR-011), L3 dashboard CTA (W12 P-OPS-4),
L4 admin empty-state guidance (Phase 5/onboarding)
- WI-007-onboarding: Phase 5 ADR + wizard (M4 gate); seed agent stub explicitly
excluded (model_id required+restrict constraint); CTA error already done in WI-D2
- B2: CRDT pitch guidance - interim relay is NOT OT real-time collab (ADR-002 intact)
- B3: SQL template count corrected to 15 (roadmap said 11 pre-viin_ai_crm baseline;
review cited 13; correct per CEO 2026-06-06 = 15)
Update docs/roadmap.md:
- Fix SQL template count 11 -> 15 with WI-008 B3 reference
- Add Layer 0 backlog note after M1 gate (WI-F, WI-G L2)
- Add AI Onboarding wizard item to Phase 5 W16-W19 (gated M4)
- Add WI-005, WI-006, WI-008 rows to ADR/WI cross-reference table
- Mark ADR-010 DONE 2026-06-04; add ADR-011 candidate for active-default policy
Update docs/decisions/README.md:
- Add wi-008 row to WI index
- Update date to 2026-06-06
* [FIX] viin_ai_brain,viin_ai_editor,viin_brain,viin_web_editor: eslint prettier formatting on WI changes
* [REF] viin_ai_brain: Command.* in WI-D tests + drop dead chat prop (review LOW)
* [FIX] viin_brain: powerbox suppressor context-gate timing (Phase V blocker)
Root cause: _getPowerboxOptions() runs synchronously inside startEdition().
The DOM markers (.o_brain_editor_body_edit[data-page-id]) are applied in the
startWysiwyg callback AFTER startEdition() resolves, so _isBrainContext()
always returned false during powerbox build - all 5 suppress-native rules
were no-ops, causing 7 native commands to appear twice (Heading 1/2/3,
Text, Numbered list, Quote, Link).
Fix: set wysiwyg.__brainPageContext = true on the wysiwyg instance BEFORE
calling startEdition() in page_editor.js. Update _isBrainContext() to check
this early-signal flag (Tier 1) before falling back to the DOM closest()
check (Tier 2). Flag is Brain-PageEditor-only; mail/website editors are
unaffected.
Bonus: deduplicate powerbox "AI" category registrations. Three WI phases
(WI-A1, WI-B3, WI-007) each registered a separate category key all named
"AI", producing three UI separators. Guard each registration behind a check
for all known AI category keys so only one "AI" category appears in the
powerbox when both viin_ai_editor and viin_ai_brain are installed.
* [FIX] viin_web_editor,viin_brain,viin_ai_brain: suppress native powerbox at merge point + Text/Link + AI category (Phase V)
- wysiwyg_patch.js: add Tier 2 powerboxFilters injection to suppress OdooEditor-level
commands (Heading 1/2/3, Bulleted list, Numbered list, Text) that bypass
_getPowerboxOptions and cannot be caught by Tier 1 alone. Use _viinCustom sentinel
to distinguish consumer commands from native ones so suppressors never accidentally
remove Brain's own commands that share the same fontawesome icon.
- slash_commands.js: add viin_brain.suppress_text (fa-paragraph) and
viin_brain.suppress_link (fa-link) suppressors for the native Text and
Link/Button commands that brain_paragraph and brain_external_link replace.
- brain_editor_registry_commands.js: document that key "brain_ai" is intentional
(viin_ai_editor uses .add("AI") unconditionally; using the same key here would
throw on duplicate). Powerbox.open() deduplicates categories by name at runtime.
- powerbox_commands.js (viin_ai_brain): same documentation for key "brain_ai_write".
- wi-007-powerbox-suppress-native.md: update to describe two-tier architecture,
add Text/Link suppressor table rows, add AI category safety analysis.
* [FIX] viin_ai_brain: group AI powerbox commands under single category (Phase V)
Root cause: _t("AI") at module-evaluation time returns LazyTranslatedString
*objects* (extends String). Different _t() calls across modules produce
different object instances never === each other. Powerbox._groupCommands()
matches command.category === category.name with strict equality, so the match
always fails for cross-module LazyTranslatedString instances. Each AI command
fell into the remaining-categories path and got its own "AI" header, rendering
3 separate "AI" separators instead of one.
Fix: use plain string literal "AI" (not _t("AI")) for both category name and
command category across all 3 AI-contributing modules. Plain strings compare
correctly with ===. "AI" is locale-invariant so no i18n value is lost.
All 3 modules now register under canonical key "AI":
- viin_ai_editor: add("AI", ..., {force:true}) - prevents DuplicatedKeyError
when brain/ai_brain registers first
- viin_ai_brain, viin_brain: guarded by !contains("AI") - avoid overwrite
warning when viin_ai_editor loads first
WI-007 decision doc updated to document the real root cause and the fix.
* [FIX] viin_brain: vault page resequence no longer clears the tree (P16 review)
Root cause: _reorderPage called _invalidateParent(null) after the ORM writes.
_invalidateParent deletes childrenByParent.get(parentId) then re-fetches only
when parentId is in expandedIds. The root parent (null) is never in
expandedIds, so the root list was permanently deleted and the sidebar showed
"No page in this vault" until the user navigated away or reloaded the page.
Server data was saved correctly — only the UI was affected.
Fix: optimistic reorder. Swap sequence values directly in the in-memory
pageCache before awaiting the ORM writes, re-sort the affected parent's child-id
array in childrenByParent using the updated sequences, then bump the flatRows
cache. The list is never cleared between the drop event and the server
response. A try/catch around the ORM writes restores consistency via
_invalidateParent + an explicit _loadRoots call only in the error path.
A new QUnit unit test (no-mount harness, same pattern as the WI-8 suite) pins
the regression: after _reorderPage the root list must still exist, contain both
page ids, and be re-sorted by the new sequences.
* [IMP] viin_brain: Brain editor UX polish - editor border, powerbox default style, placeholder hints, clickable breadcrumb (P16 review)
Issue 2 (editor border): fix specificity loss in .o_brain_editor_body_edit
note-editable border override. Add compound selector &.note-editable + !important
backstop so Brain wysiwyg canvas never shows the textarea-like border from
web_editor.backend_assets_wysiwyg regardless of stylesheet load order.
Issue 3 (powerbox style): remove Brain-specific powerbox SCSS overrides
(_powerbox.scss). The custom grey background / washed-out description text
diverged from Odoo default. Revert to Odoo default white-background crisp
powerbox. Command injection via wysiwyg_patch.js / powerboxCommandsRegistry
is completely unaffected - only visual overrides removed.
Issue 4 (placeholder hint): wire OdooEditor native placeholder option in
_buildWysiwygOptions() so empty pages show "Type / for commands, [[ to link
a page, @@ to mention" hint via .oe-hint::before. Also add
o_brain_editor_placeholder class to the editable in startWysiwyg() as CSS
fallback for :empty:not(:focus)::before defined in _app_shell.scss.
Issue 5 (breadcrumb + header lean): make vault breadcrumb segment clickable
(role=button, tabindex=0, cursor pointer, hover highlight, onBreadcrumbVaultClick
handler calls onVaultSelect prop bound from BrainApp.onVaultSelect). Tighten
header layout: margin-bottom 16px->10px, breadcrumb mb 8px->4px, sep padding
0 6px->0 4px, title font 28px->26px, icon 24px->22px, padding 4px->2px 0.
* [FIX] viin_brain: show editor placeholder hint (CSS cascade fix, P16 review)
Drag-handle rule `> p::before { content: "⋮⋮" }` was overriding
`.oe-hint::before { content: attr(placeholder) }` due to equal specificity
and source order. Fix: narrow the drag-handle selector to
`> p:not(.oe-hint)` so the hint paragraph is excluded from the drag-handle
block entirely. Also add explicit `content: attr(placeholder)` to the
`.oe-hint::before` rule to ensure it always wins regardless of cascade order,
and bump opacity from 0.38 to 0.45 for better readability.
* [FIX] viin_brain: eslint prettier formatting on P16 UI fixes
* [FIX] viin_brain,viin_ai_brain: scope Brain powerbox commands to Brain editor context (P16 review)
Brain-specific powerbox commands were registered globally and leaked into
every editor that loads the powerbox patch (mail templates, CRM description,
website builder, etc.), causing UI noise and potential runtime errors when
Brain-specific callbacks (wikilink, embed, database, record mention) fire
outside a Brain page.
Fix: add isDisabled(wysiwyg) => !isBrainContext(wysiwyg) to all Brain-specific
commands so they are hidden by the Powerbox engine outside a Brain editor.
isBrainContext uses a two-tier check (wysiwyg.__brainPageContext early-signal
flag + DOM .o_brain_editor_body_edit[data-page-id] fallback) identical to the
existing WI-C suppress-native contextGate.
SSOT: extract shared predicate to viin_brain/static/src/utils/brain_context.js
(web.assets_backend eager bundle, no @web_editor dep). Three consumers:
- slash_commands.js (wysiwyg lazy bundle) - all 14 Brain commands gated
- brain_editor_registry_commands.js (eager) - brain_ai_block stub gated
- viin_ai_brain/registries/powerbox_commands.js - brain_ai_write + brain_ai_chat
now require both Brain context AND bridge.available
brain_ai_write / brain_ai_chat: previous _isDisabledWhenNoBridge only checked
bridge availability; these commands also leaked into non-Brain editors when
bridge was active. New _isDisabledWhenNoBridgeOrOutsideBrain combines both.
Not gated: viin_ai_editor ai_block command is a general AI editor feature
(not Brain-specific); its isDisabled already gates on backend.available.
ai_block_conflict_dedup.js WI-C dedup mechanism preserved unchanged.
WI-007 suppress-native contextGate logic preserved unchanged.
* [FIX] viin_ai_editor: eslint prettier trailing comma
* [FIX] viin_ai_brain: address Codex review on PR #43 supplement
Two P2 findings from the automated Codex review:
1. find_kba_agent preferred global over company-specific agents.
order='company_id desc' sorts SQL NULLs (global, company_id=False)
FIRST under PostgreSQL DESC, so a company with its own KBA agent was
still routed through the shared global one. Replaced with a two-step
preference (company-specific search first, global fallback) - DB-agnostic
and provably correct. Added TestKbaAgentCompanyPreference regression tests
(company-specific wins; global still used as fallback).
2. Native ChatGPT (fa-magic) powerbox command was suppressed in EVERY
editor once viin_ai_brain was installed, because the suppressor gated only
on bridge.available (globally true). In non-Brain editors (mail/CRM/website)
the replacement brain_ai_write is itself disabled, leaving no AI write
command. Added isBrainContext(wysiwyg) to the suppression contextGate so
the native command is only replaced inside Brain editors.
|
Failed
|
|
|
|
|
|
|
|
merged
[IMP] P16: Editor Extension Framework - modular Brain/AI editor (OdooEditor migration)
|
[FIX] viin_brain: restore pylint-odoo consider-merging-classes-inherited disable
Reverts the removal in b93db89, which was based on a faulty reproduction
against VANILLA Odoo (no pylint-odoo loaded -> the inline disable read as
unknown-option-value W0012). The Viindoo Runbot lint is NOT Odoo-core
test_lint: it runs the tvtmaaddons test_pylint module, which loads
pylint_odoo and ENABLES consider-merging-classes-inherited (R8180). So the
disable is required: viin.brain.page is intentionally re-opened in both
viin_brain_page_tag.py and viin_brain_page_extension.py for readability, and
R8180 asks to merge them.
Verified against the period-correct Odoo 17 stack (pylint 2.15.10 /
astroid 2.13.5 / pylint-odoo 8.0.22) using the Runbot module's exact
ENABLED_CODES: with the disable restored, all 21 viin modules report 0
findings (R8180 suppressed). Runbot build 222101 failed on exactly this
one message (1 failed of 24 tests).
|
Killed
|
|
|
|
|
|
|
|
merged
[IMP] P16: Editor Extension Framework - modular Brain/AI editor (OdooEditor migration)
|
[FIX] viin_web_editor,viin_brain,viin_ai_editor,viin_ai_brain: Runbot test_lint/test_pylint green
Reproduced the Runbot failures locally against Odoo 17 test_lint (period-correct
pylint 3.3.7) instead of guessing, and fixed the real root causes:
- test_pylint (Python): viin_brain/models/viin_brain_page_tag.py carried
'# pylint: disable=consider-merging-classes-inherited'. That message belongs
to the pylint-odoo plugin, which Odoo core test_pylint does NOT load, so the
inline disable resolves to unknown-option-value (W0012) - not suppressed by
'--disable=all,useless-option-value' - making pylint exit non-zero. The
message is not even in test_pylint's enable list, so the disable was inert.
Removed it. Verified pylint exit 0 across all 21 viin modules.
- test_eslint (JS): 417 prettier/prettier errors across P16 JS under the repo
.eslintrc.json (extends plugin:prettier/recommended, tabWidth 4, printWidth
100). The husky hook only formats staged files, so P16 files committed
earlier were never formatted. Ran eslint --fix; verified 0 errors against
both the repo prettier config and Odoo core eslintrc.
- test_manifests: viin_ai_editor set 'auto_install': False, which equals the
default manifest value (assertNotEqual hard-fail). Removed the key.
No behavior change: JS edits are pure formatting; Python edits delete an inert
key and an inert lint pragma. package-lock.json kept at baseline.
|
Failed
|
|
|
|
|
|
|
|
merged
[IMP] P16: Editor Extension Framework - modular Brain/AI editor (OdooEditor migration)
|
[FIX] viin_web_editor,viin_brain,viin_ai_editor,viin_ai_brain: lint clean for Runbot (prettier/eslint format, unused vars, manifest, pot, flake8)
|
Failed
|
|
|
|
|
|
|
|
merged
[IMP] P16: Editor Extension Framework - modular Brain/AI editor (OdooEditor migration)
|
[FIX] viin_brain: repair slash-focus tour (AI category priority outranks Brain)
ROOT CAUSE (tour fragility, not product code regression):
viin_ai_editor registers an "AI" powerbox category at priority 95 and an
"AI block" command at priority 90. The "Brain" category is registered at
priority 90. The OdooEditor Powerbox sorts categories by priority desc then
name asc, so "AI" (p=95) renders before "Brain" (p=90). Pressing Enter
after a bare "/" selected the "AI block" command instead of "Heading 1",
and the assertion :has(h1,h2,h3) failed.
Evidence: failure screenshot showed the AI block component rendered (the
"AI block - Empty - type a prompt or pick a template" UI) instead of an h1.
The Brain heading-via-slash command itself works correctly when explicitly
selected; the tour was testing an incidental ordering assumption.
FIX (tour fix, not product code):
Changed the powerbox step to type "/heading" instead of bare "/" before
pressing Enter. After insertText("/heading") + a keyup event, Powerbox.
_onKeyup() runs patienceDiff and filters filteredCommands to only the three
Brain heading commands (Heading 1 p=100, Heading 2 p=90, Heading 3 p=80).
"Heading 1" is then the active/first command. Tightened the final assertion
from :has(h1,h2,h3) to :has(h1) since the filter + priority guarantee h1.
FAILURE 2 (TestBrainTimelineTour): already green in the worktree; 0 failed
on isolation re-run. No code change needed.
Wikilink tour: confirmed still green (3/3 pass, 0 errors).
|
Failed
|
|
|
|
|
|
|
|
merged
[IMP] P16: Editor Extension Framework - modular Brain/AI editor (OdooEditor migration)
|
[FIX] viin_ai_editor: valid XML comments in demo data (double-hyphen)
|
Failed
|
|
|
|
|
|
|
|
merged
[DOC] ADR-010 Editor Extension Framework (modular Brain/AI editor) — Proposed
|
[DOC] docs: ADR-010 Editor Extension Framework (modular Brain/AI editor)
Refine ADR-003 with the structural HOW for the OdooEditor migration (P16):
- Keep viin_web_editor as the neutral propagation base (patch HtmlField/
Wysiwyg once, driven by global registries) - it is the keystone that lets
Brain and AI editor plugins depend on it without coupling to each other
(AGENTS.md cam ky #2). Deleting it was considered and rejected.
- Move the AI block out of viin_brain into a new AI-cluster module
viin_ai_editor (generic, works in any html field); bridge viin_ai_brain
wires AI output to Brain attribution + resolves Brain x AI conflicts.
- Grounded in verified Odoo 17 web_editor seams (options.plugins,
_getPowerboxOptions, HtmlField.wysiwygOptions, no native NodeView,
setupCollaboration, '/'-only input rule).
Tracker odooeditor-migration-progress.md re-organised from 23-features-in-Brain
to module-split + a 4-phase wave plan (framework -> plugins -> bridge ->
verify+visual) with odoo-ui-reviewer visual gates. Status Proposed - awaiting
sign-off before execution.
|
Killed
|
Not started
|
Not finished
|
|
|
|
|
|
merged
[DOC] ADR-010 Editor Extension Framework (modular Brain/AI editor) — Proposed
|
[DOC] docs: ADR-010 Editor Extension Framework (modular Brain/AI editor)
Refine ADR-003 with the structural HOW for the OdooEditor migration (P16):
- Keep viin_web_editor as the neutral propagation base (patch HtmlField/
Wysiwyg once, driven by global registries) - it is the keystone that lets
Brain and AI editor plugins depend on it without coupling to each other
(AGENTS.md cam ky #2). Deleting it was considered and rejected.
- Move the AI block out of viin_brain into a new AI-cluster module
viin_ai_editor (generic, works in any html field); bridge viin_ai_brain
wires AI output to Brain attribution + resolves Brain x AI conflicts.
- Grounded in verified Odoo 17 web_editor seams (options.plugins,
_getPowerboxOptions, HtmlField.wysiwygOptions, no native NodeView,
setupCollaboration, '/'-only input rule).
Tracker odooeditor-migration-progress.md re-organised from 23-features-in-Brain
to module-split + a 4-phase wave plan (framework -> plugins -> bridge ->
verify+visual) with odoo-ui-reviewer visual gates. Status Proposed - awaiting
sign-off before execution.
|
Killed
|
|
Not finished
|
|
|
|
|
|
open
[DOC] Reconcile ADR-002 CRDT with shipped interim relay (OT-final after P16)
|
[DOC] docs: reconcile ADR-002 CRDT with shipped interim relay
Recon + Odoo native test (TestCollabStepBuffer/Controller +
TestBrainRealtimeCollab -> 0 failed of 15) cho thấy CRDT thực tế đã ship
~90% qua launch-hardening (WI-9 snapshot-relay + WI-12 presence +
WI-13 flag), KHÔNG phải 'sắp làm W7-W11' như roadmap/ADR-002 cũ.
- ADR-002: thêm §Implementation Status — interim snapshot-relay đã ship +
verified; Decision (Option 1 OdooEditor native OT) GIỮ NGUYÊN làm đích;
OT-final re-sequenced SAU P16 (cần PageEditor wrap OdooEditor). Ghi rõ
gap: relay step.html_snapshot, chưa gọi setupCollaboration, chưa set
collaborative=true.
- roadmap + brain/README: phản ánh interim DONE sớm, OT-final sau P16.
Docs-only; không đụng code. Khớp quyết định: giữ mục tiêu OT thật.
|
Killed
|
|
|
|
|
|
|
|
merged
[DOC][TEST] Brain stabilization: CRDT v1 + ADR-003 OdooEditor + WI-006 daily-note ACL deny
|
[TEST] viin_brain: close 7 residual daily-note ACL cells as deny (WI-006)
Convert 7 @_odoo_xfail conformance-gap tests to hard DENY assertions
(AccessError): viewer/commenter create (AC-9.3a/b, 14.3a), commenter
message_post (AC-14.5a/b), group-resolved editor write (AC-19.2b),
vault-admin unlink (AC-24.4) on own daily note. No production/security
change — deny-preserving per WI-006. Remove dead _odoo_xfail helper.
Verified: odoo-bin --test-enable on the 4 daily-note ACL classes ->
0 failed, 0 error of 45 tests.
|
Killed
|
|
|
|
|
|
|
|
merged
[FIX] viin_brain,viin_web_editor: Brain core launch-hardening (T-1/T-2/T-3)
|
[FIX] viin_brain,viin_web_editor: Brain core launch-hardening (T-1/T-2/T-3)
- T-2 privacy: daily notes can never be shared via link (ValidationError in
_check_page_shareable); share comment state no longer hardcoded (honors
comment_auto_approve).
- T-3 ACL: complete the 40-cell matrix in tests — non-owner (incl brain/vault
admin) cannot read/write another user's daily note; commenter cannot write
own daily body. No rule change (rule_page_daily_global already enforces).
- T-1 concurrency: deterministic version token (isoformat ms) + re-enable
first-save optimistic-concurrency check in page_editor (realtime untouched).
- Tests: viin_web_editor powerbox-registry QUnit, trigger-regex edge cases,
block_parser round-trip tour + wrapper.
- Docs: CHANGELOGs, brain/security.md ACL matrix, decisions/wi-005 (privacy-first).
|
Killed
|
|
|
|
|
|
|
|
merged
[FIX] viin_brain,viin_web_editor: Brain core launch-hardening (T-1/T-2/T-3)
|
[FIX] viin_brain,viin_web_editor: Brain core launch-hardening (T-1/T-2/T-3)
- T-2 privacy: daily notes can never be shared via link (ValidationError in
_check_page_shareable); share comment state no longer hardcoded (honors
comment_auto_approve).
- T-3 ACL: complete the 40-cell matrix in tests — non-owner (incl brain/vault
admin) cannot read/write another user's daily note; commenter cannot write
own daily body. No rule change (rule_page_daily_global already enforces).
- T-1 concurrency: deterministic version token (isoformat ms) + re-enable
first-save optimistic-concurrency check in page_editor (realtime untouched).
- Tests: viin_web_editor powerbox-registry QUnit, trigger-regex edge cases,
block_parser round-trip tour + wrapper.
- Docs: CHANGELOGs, brain/security.md ACL matrix, decisions/wi-005 (privacy-first).
|
Killed
|
|
Not finished
|
|
|