Pending: 0 Building: 0 Running: 3 Failed: 269
Created Date Type Name Commit Description State Age Up Time Life Time Action
open [DOC] ADR-013 + ADR-032: catalogue speech models, settle language resolution [DOC] ADR-013: say which duration call_tts returns Section 4 ratified call_tts's return as {'audio_bytes': bytes, 'duration_ms': int} without saying which duration it means. The key name reads naturally as playback length, and the implementation returns wall-clock HTTP latency, so a reader could reasonably build on either. The module keeps the two apart deliberately: duration_ms is how long the vendor took to answer, audio_duration_ms is how long a recording plays. A 3 second call can carry 60 seconds of audio, so a consumer confusing them is wrong by orders of magnitude. call_tts bills by characters and computes no playback length, so it reports none. Section 4 now says all of this, because the contract is what a coder reads before writing the consumer, while a docstring is what they read after something already looks wrong. Failed
open [DOC] ADR-013 + ADR-032: catalogue speech models, settle language resolution [DOC] ADR-013 + ADR-032: catalogue speech models, settle language resolution ADR-013 section 4 named model_type gaining only 'tts' and said nothing about speech-to-text. That left transcription without a catalogued model, so it had no pricing, no routing and no place in the model catalogue - while reusing 'tts' for it is forbidden by that same section's rule against merging the two directions. Section 4 now grants both 'tts' and 'transcription'. The existing 'audio' capability flag is still reused; no new capability flag is introduced. The decisions directory is append-only, so the in-place amendment is recorded in ADR-013's own header and on its index row rather than left silent. ADR-032 settles how any AI modality resolves a language, deliberately modality-agnostic so chat, editor and search can cite it too: a four-tier ladder, auto-detect as advisory only, coverage declared as res.lang data on the model, ISO 639-1 normalisation applied in both directions, and failure rather than silent fall-back with language_any as the sole wildcard. Adding a language becomes a configuration act rather than a code change. The roadmap records that full-duplex conversation is deferred to v19 and names the seam it will attach to, so the deferral is a decision on the record rather than an omission. Failed
merged [DOC] roadmap + migration: reconcile with the relocated code [DOC] roadmap + migration: reconcile with the relocated code The 50 AI/Brain modules now live in Viindoo/tvtmaaddons (47) and Viindoo/erponline-enterprise (3). Reading both repos back against this roadmap turned up two claims that were not true of the code, plus six counts or references that had drifted. Corrected, each on the evidence it rests on: - M7-4 goes back to [ ]. The ADR-029 decision stands, but the execution never landed: viin_ai_chatbot on odoo-tvtma17's 17.0 HEAD is still version 1.0.1, carries only migrations/16.0.1.0.1/, and holds zero references to viin_ai_livechat. The planned 1.0.2 bump and migrations/17.0.1.0.2/ do not exist. migration.md described the same work as already done and now reads as the design it is. - M7-5 claimed the asset gate asserts an AI bundle under 500KB gzip. No such assertion exists in that test or anywhere else; only the viin_brain 450KB budget is real, so the AI-side bundle is ungated. - be53a191 is not a commit in any repo. The fix it credits is cdfd038519. - The skill catalogue splits 72 kw-* / 41 viin-*, not 62 / 51. The totals 113/13 and 115/14 were right. - SQL templates total 35 today; 23 was the last figure ever recorded. - Eight work routines ship, not seven. routine_dunning_weekly is named elsewhere in the doc but was never added to the tally. - Four line references in 2A W3.2 had slid 1-3 lines; symbols unchanged. - Wave 4 covers 46 icon files of which only 39 are byte-distinct - five viin_brain_* modules share one identical file. Add 9.1, which records a rule that fell on the floor during the move. Commit 4bbdabb6bc dropped every migration script and pinned each module to 1.0.0 because the ai17 repo had no deployment to protect, and made its own removal conditional on that changing. The rule lived in ai17/AGENTS.md 0.1, and merge 8d238c74 states plainly that repo-level files stayed behind. Neither destination repo carries it; neither has an AGENTS.md at all. Bare 1.0.0 turns out to be the house style there - 126 sibling modules use bare manifest versions with series-prefixed migration dirs - so nothing needs renumbering. What is missing is the migrations. No back-migration is owed either: no tag, no customer repo and no SaaS config shows any of these modules installed anywhere. That is absence of evidence rather than proof, and 9.1 says so. The invariant bites at the next schema change, and ADR-031's translate=True is the first one due. Where to write the rule down in a shared repo is left to the owner. Also flag the audio capability as declared but dead: provider_google_gemini and vendor_google both seed it while call_transcription is a bare NotImplementedError, so the UI advertises what a call would fail on. Killed Not finished
merged [DOC] roadmap + ADR-030 + ADR-031: close the observability gaps [DOC] roadmap + ADR-030 + ADR-031: close the observability gaps Reconciles the roadmap with the code now that both observability gaps are closed, and records the two decisions the work produced. ADR-030 covers the quality-cost report: the grain is the trace, whose UNIQUE(trace_id) makes it a database guarantee rather than a convention, and both n-sides collapse before the join because a naive one inflated the cost total up to twentyfold. It records why agent_id comes from the spine and not the usage log, why the base module declares empty seams the bridge fills, and why only a SQL string assertion can guard that boundary on a CI database that always carries the auto_install bridge. It also carries two corrections: the design's claim that an archived agent's label degrades for a plain user is false on 17.0, since a many2one label resolves as superuser; and its claim that the standalone guard runs correctly under a seam patch is false, because patching a seam changes the rendered SQL while the field stays in the registry. ADR-031 defers translate=True on the seeded-record fields and says why the deferral is deliberate: the columns holding what a user reads are varchar and text rather than jsonb, so a catalogue cannot reach them at all, and adding the flag converts a column on live customer data. It states the exclusion as a rule rather than a list - any field whose value is read by a machine must not be translatable, however much the text reads as prose - and records the first field that rule closes. The Phase-5 onboarding wizard item stays open; it was never in scope. Failed
merged [DOC] reconcile docs now that the code lives elsewhere [DOC] README: point version/migration summary at its owning section README's contributing section restated the version/migration rule inline; docs/conventions.md's Version & migration section already owns that rule. Link to it instead, so the repo has one source rather than two. Killed Not started Not finished
merged [REM] *: remove the 3 Enterprise bridge modules migrated to Viindoo/erponline-enterprise [REM] *: remove the 3 Enterprise bridge modules migrated to Viindoo/erponline-enterprise These modules now live in Viindoo/erponline-enterprise with full history preserved, imported by PR https://github.com/Viindoo/erponline-enterprise/pull/4179 (merge commit 64ec6dac there). This removal must NOT be merged before that PR has merged, otherwise the code would vanish from every repository at once. Failed
merged [REM] *: remove the 47 modules migrated to Viindoo/tvtmaaddons [REM] *: remove the 47 modules migrated to Viindoo/tvtmaaddons These modules now live in Viindoo/tvtmaaddons with full history preserved, imported by PR https://github.com/Viindoo/tvtmaaddons/pull/14355 (merge commit 8d238c74 there). This removal must NOT be merged before that PR has merged, otherwise the code would vanish from every repository at once. Failed
merged [FIX] viin_ai_brain, viin_ai_chat: tour selectors bind to stable identifiers, not translatable text [FIX] viin_ai_brain: correct quote style in tour selector for ESLint compliance The selector string on line 154 was incorrectly single-quoted, which violates the module's ESLint/prettier convention (double quotes for selectors without inner attributes). This lint-gate regression was introduced in 8d5240fb. Killed Not finished
merged [FIX] ai17: Vietnamese catalogues, the locale regressions they caused, and the defects that surfaced [FIX] viin_ai_ops: an unprovable proposal gets the strictest outcome, not the default Acceptance rejected the previous fix on six High-tier scenarios, and it was right. That fix moved the unknown-model case from auto-executed to pending, and stopped there. But an administrator's model-scoped block yields rejected for a proposal that names its model, while the same proposal with the field empty yielded pending - which one approver can wave through, after which it executes. Omitting impacted_model still downgraded a hard block into an approvable request. Two code reviews read that as defensible; the rule requires monotone OVERSIGHT, and rank 1 is not rank 2. The scan no longer aborts at the first policy it cannot rule out. It runs to completion and takes the maximum over the oversight lattice of every outcome still reachable: each policy that agrees on all the proposal's OTHER axes and is scoped to a model that cannot be excluded, plus the first unscoped policy reached (nothing behind it is reachable for any model), or the unmatched fallback when no catch-all is reached. Taking the first rather than the max is exactly what made the earlier attempt wrong. Nothing is recorded as the matching policy. The status stays "model unidentified" and policy is None, because the resolved action is a statement about the policy SET - nothing configured here permits less than this - not an attribution to one rule. Naming the strictest candidate would assert that a rule scoped to a model the proposal may never have touched governed it, which is a false statement in an auditable subsystem, and it would break the workflow bridge that reads this status to tell "no orchestration configured" from "the governing policy could not be determined". The sudo gate generalises the same way: allow_runs_as_sudo is now the AND over candidates, since an opt-in must not be borrowed from a rule that may not govern. An unranked action falls back to maximum oversight rather than raising. Both directions are held. The over-correction set - a permissive rule legitimately in front of a block, a block filtered out by risk or scope, another company's policy - passes at HEAD AND after the fix, which is what proves the change is not a blanket block. Reverting the model file reds 14 tests and errors 2 more; the S9 discriminator names the twins and the strictest outcome in its failure message. Two consequences stated rather than buried. A single model-scoped block now blocks every proposal recording no impacted_model, not only those that might have hit that model - a real widening, and what the rule's wording forces. And two legacy assertions changed, because they encoded the earlier partial fix. 96 tests green on two databases, plus the bridge, approval, approval_account and workflow suites. Test setup now archives pre-existing policies inside its own rolled-back transaction. A shared test database carried leftover fixtures that reddened 13 tests before any of this; the suite should not depend on which rows a previous run forgot. Succeed
merged [FIX] ai17: Vietnamese catalogues, the locale regressions they caused, and the defects that surfaced [FIX] viin_ai_approval: the AI advisory survives an upgrade on all three bridges This PR fixed install-only wiring for the nine Brain sidebar carriers and left the same defect standing in the three approval bridges. Half a class is worse than none - it reads as cleaned up. It was not theoretical. On a database built from this source, all three account approval types carried NO wiring at all, so the AI risk advisory was silently not installed on any of them, and no upgrade could repair it. The root cause is measurable, not inferred. viin_ai_approval_account finished installing at 11:02:21.228; the three account types were created at 11:02:27.327, the instant the chart of accounts landed - because the upstream generator skips a company with no chart_template. The install-only hook ran six seconds before the records it was meant to wire existed. The purchase bridge escaped by luck alone: its type was created at 11:02:22.757, before the bridge installed at 11:02:25.259. That ordering accident IS the defect class. Wiring now lives in one shared method on approval.request.type, called from a per-bridge data-file <function> - the pattern the Brain carriers proved, which Odoo runs on install AND on every upgrade. One detail is load-bearing: the <function> could NOT go in the existing seed file, because _tag_function returns early inside a noupdate block, which would have re-created the bug in silence. Each bridge gets a second, update-loaded file instead. The non-clobber guard is untouched - same has_executable_code, same two conditions, so a customer's own hook is still never overwritten. Only the caller moved. Idempotent by construction: once wired, the body IS executable code and the agent IS set, so a second pass writes nothing. Measured over three upgrade passes: the fragment appears exactly once. Guarded in both directions. Removing the mechanism reds five tests and leaves the database untouched, which is the defect reproduced. Dropping the file from the manifest while leaving it on disk reds the manifest assertion - and note the wiring test stays GREEN there, because convert_file drives the file regardless of the manifest. Without that assertion the bug would return with a green suite. test_advisory_account was not weakened to pass; its assertions are byte-identical. It is green because the three types now read wired after an upgrade, where they read unwired before. One gap deliberately left, so it is a decision rather than an oversight: a company created AFTER install gets fresh approval types with no advisory until the next upgrade. Same family, different trigger, and closing it needs a cross-bridge registry of which bridge owns which codes - the Brain cluster needed its own ADR for the equivalent. Killed Not finished
merged [FIX] ai17: Vietnamese catalogues, the locale regressions they caused, and the defects that surfaced [FIX] viin_ai_ops: duplicating an orphaned policy no longer widens it to every model A post-fix review found a fail-open the PREVIOUS fix created. When ondelete cascade became set null plus a stored mirror, duplicating a policy became possible for the first time - and copy_data passes model_id: False, so the create guard `if "model_id" in vals` fired and wiped the mirror. Duplicating an orphaned auto_execute rule produced one governing EVERY model, contradicting the help text that promised the mirror prevents exactly that. The guard is now `if vals.get("model_id")` on create: the picker wins whenever it names something, and an empty picker never clears an accompanying mirror. write() stays asymmetric on purpose - emptying the picker there IS the administrator widening the rule - and both docstrings say so. Two alternatives were checked against core rather than assumed. copy=False on the mirror is backwards: copy_data emits every copyable field, so copy=True is precisely what carries an orphan's scope across the duplicate. Preserving model_id is impossible for an orphan - the ir.model row is gone. The matcher's answer is now separate from its reason. Returning None conflated "no policy matched" with "matching stopped because the model is unknown", and the workflow bridge reads None as "no orchestration configured" - so a proposal a HUMAN HAD APPROVED stalled at approved forever. It now returns a status, and the bridge holds the approval rather than consuming it: filling in impacted_model and re-running resolves normally, and the stall logs what to do. Falling through to the unscoped catch-all was rejected - it would assert a governing policy the proposal never evidenced, and its chain can carry FEWER review steps than the model-scoped rule that could not be ruled out. The docstring claiming pending is never more permissive is corrected; it is monotone in OVERSIGHT, not in outcome, and replacing an unscoped block with a human decision is the one direction it is more permissive. match_summary now reflects the rule being built, in both directions - including the one nobody reported: emptying the picker on a saved rule is the administrator widening it, and the widget must say so before the save, not after. And the skipTest is gone. Both readings of the fleet tally were wrong: == 13 is falsely red on a partial fleet, while skipping protects nothing in the commonest install mode. It asserts a per-connector count for every installed connector, with a guard that those still sum to the signed-off 13. Nothing skipped, nothing falsely red. Proven falsifiable by deleting a template for real - the data file is noupdate, so the stale row had to go too. Killed Not finished
merged [FIX] ai17: Vietnamese catalogues, the locale regressions they caused, and the defects that surfaced [FIX] viin_ai_chat: 62 strings the extractor could never see Two assertions this PR added could not fail. 'Agent error: %s' is always a non-empty string and the controller never emits a traceback, so both held no matter what the code did - written while making tests locale-stable, and breaking the exact property that work existed to protect. They now assert the CAUSE survives into the reply. Proven twice: red when the content is emptied, and red when the friendly wording is kept but the cause dropped - the second is the regression the old form structurally could not catch. A fixture string was still wrapped in _(), shipping "No credits left." into the released catalogue. It is an opaque sentinel now, and the assertion binds to the exception CLASS surviving the round trip rather than to prose. The bigger item: 62 user-facing strings across six templates were written as t-esc="_t('...')" or t-att-title="...", neither of which Odoo's extractor can see - it reads element text and a fixed attribute allow-list, and the t-att- form is not in it. The tool-confirm dialog, the first-run banner, the session rail and the feedback widget were entirely absent from the catalogue. This PR had demonstrated the fix on ONE string and left the rest, and the new vi_VN.po then MASKED the gap by making the module look translated. All 62 move into component getters, and the escape hatch that enabled the pattern is deleted so it cannot return quietly. Catalogue: viin_ai_chat 95 -> 142 msgids, viin_ai_brain 77 -> 85, every new entry translated. Three comments this PR added were false. sql_template.description IS rendered - it has its own notebook page - so the decision to keep it non-translatable stands but the stated reason did not. And the note added to four manifests claimed a non-application module gets no Apps tile: core annotates 'images' as a WEBSITE key, its only reader is the theme-image updater, update_list never copies it, and the backend tile is icon_image for every module. The reviewer was right and the comment was wrong; it now says what is true. Two reds found while working and fixed: a dashboard test that had become ambient-DB-dependent since the usable-provider resolver landed - it resolves a provider before reaching the stub, and every provider on the test database is keyless - now carries its own fixture; and making "Reported" extractable would have reddened a tour selecting on that word, so the tour binds to a class instead. Killed Not finished
merged [FIX] ai17: Vietnamese catalogues, the locale regressions they caused, and the defects that surfaced [FIX] viin_ai_ops: an unidentified proposal cannot fall through to a permissive rule The model axis added earlier in this run skipped a model-scoped policy when the proposal recorded no impacted_model, and its docstring called that fail-closed. It was not. _match_proposal is first-match-wins over sequence, so skipping hands the proposal to the NEXT policy, which may be more permissive. With a block on account.move at sequence 10 and an unscoped auto_execute for low risk at 20, a proposal with no impacted_model was auto-executed - exactly what the administrator had blocked. That is the shape the feature's own motivating example requires, not a contrived config. A model-scoped policy that agrees on every other axis now STOPS the scan, and routing lands on the subsystem's documented default-safe pending. A policy whose own risk or scope filter already excludes the proposal does not stop it - it could not have governed the proposal under any model, so passing it by loses nothing. The result is monotone in oversight in both directions: where that policy would have governed, pending never auto-executes in its place; where it would not, pending is never more permissive than what the scan would have found. The old test created ONE policy, so it never reached the fall-through its own docstring described. The new one puts a model-scoped block ahead of an unscoped permissive rule and was watched to fail against the old code. Rejected alternatives, for the record: making the unknown model match every model-scoped policy would make the unknown case MORE permissive against an auto_execute rule; terminating at the model-scoped policy's own action would write a block record naming a model the proposal may never have touched, which is a false statement in a component whose purpose is auditable governance. model_id also loses ondelete=cascade. The same wave argued impacted_model must be a Char so a proposal survives uninstalling the application that provides its model - then let the POLICY be deleted on that event, and Odoo drops ir.model rows transiently during an upgrade too. An administrator's block rules were being destroyed with no audit trace, and reinstalling never brings them back. It is now set null plus a stored model_name mirror maintained in create/write, because PostgreSQL performs the SET NULL directly and never calls the ORM. The views show the technical name so an orphaned rule reads as orphaned rather than blank. A Vietnamese tooltip that promised the old cascade behaviour is corrected. Three more from the same review: The skill de-branding guard matched vendor names as WORDS, so the lowercase path reference/hubspot-scoring.md was invisible even to it - and three such companion files were shipping as live markdown links to documents that do not exist, injected verbatim into the system prompt. Fixed in the converter and regenerated; the guard now fails the build on any dangling companion reference anywhere in the catalogue, verified to flag exactly those three and to leave genuine customer-side vendor mentions alone. Two discuss-bot assertions had been loosened to "some new message appeared", which a generic error notice satisfies equally. The notice carries no author and no distinguishing subtype, so there is no language-independent discriminator; they use with_context(lang='en_US') like their siblings, and were proven still able to fail by replacing the production notice with a generic one. Twelve banned dash characters remained repo-wide, including one this run had propagated from a source file into two new catalogue entries. All fixed; upstream Apache-2.0 material under viin_ai_skill/research/ is left verbatim. Three test docstrings quoted the removed expression and cited a line that no longer holds it. They now describe what the matcher does. Failed
merged [IMP] M7 v1 release-hardening: Viindoo AI + Brain go-live gate (generic / multi-tenant) [FIX] test_viin_ai: WoW-demo work item state draft, not in_progress Runbot's full multi-tenant post-install run reds viin_ai_workflow's TestStuckWorkflowCockpitProvider.test_stuck_workflow_is_non_negative_int_at_zero_state (1 != 0). That module's stuck-workflow cockpit metric counts ALL in_progress work items as the user (a deliberate NO-UNDER-REPORT governance floor), so this PR's new wow_demo_cfo demo work item - seeded state='in_progress' - made the module's zero-state contract (0 stuck on a demo install) false. The demo item is scene-setting CONTEXT only (Beat 4 of the compound test creates the LIVE governed work item at runtime via routine._run()); set it 'draft' so it no longer inflates the governance metric. Verified: TestStuckWorkflowCockpitProvider 6/6 + TestWowDemoCompound 1/1 green, demo in_progress work-item count 1 -> 0. The pre-existing test + the stuck-workflow provider are untouched. Signed-off-by: David Tran <davidtran.hp@gmail.com> Succeed
merged [IMP] M7 v1 release-hardening: Viindoo AI + Brain go-live gate (generic / multi-tenant) [FIX] test_viin_ai: mock the provider in the WoW-demo tour (offline chat reply) TestWowDemoCfoTour drove the AI Chat waypoint against a live provider: the demo Chat action carries no agent_id, so the controller resolves the lowest-sequence agent (the keyless OpenAI demo agent), which hits a live 401 and never renders an assistant message - so the tour's '.o_viin_ai_chat_role_assistant' waypoint would time out on Runbot (real headless Chrome). It never surfaced in the co-install gate because the tour chrome-skipped under machine contention; a real-Chrome capture pass exposed it. Wrap the tour in the CD-0 VIIN_AI_MOCK_PROVIDER env hook (MockProviderEnvHookMixin) so every provider call returns the deterministic offline mock reply - the 'offline mock reply' the waypoint already asserts, and ADR-016's no-real-key contract. Signed-off-by: David Tran <davidtran.hp@gmail.com> Failed
merged [IMP] M7 v1 release-hardening: Viindoo AI + Brain go-live gate (generic / multi-tenant) [FIX] viin_brain_account_reports: move backlink patch into the lazy Brain bundle Runbot 'install all modules' crashed pregenerating the eager web.assets_backend: ValueError: Cannot create 'viin_brain_account_reports.None' because the template to inherit 'viin_brain.BacklinkPanel' is not found. viin_brain's CD-2 lazy split (654cd9d9) moved BacklinkPanel (JS component + QWeb template) into viin_brain.lazy_brain_app, but this module's backlink_panel_patch (JS patch + QWeb t-inherit) was still declared eager - an eager asset patching a now-lazy component/template, which fails eager-bundle compilation whenever both modules are co-installed. Move the patch into the SAME lazy bundle (the identical fix already applied to viin_ai_brain's fact-history override). A whole-repo sweep confirms these two were the ONLY cross-module eager->lazy BacklinkPanel edges. Signed-off-by: David Tran <davidtran.hp@gmail.com> Killed Not finished
merged [IMP] M7 v1 release-hardening: Viindoo AI + Brain go-live gate (generic / multi-tenant) [DOC] ai17: M7 v1 doc reconciliation + ADR-029 + roadmap (CD-7) Reconcile ai17 docs with the merged + M7-session code so the first production operator is not misled: (a) AGENTS.md ADR ledger reconciled (ADR-025/026 added; counts). (b) call_tts wording corrected (only call_transcription is a real stub). (c) ADR-013 -> Ratified. (d) ICP-key count + dead default_chat_model ICP reconciled. (e) E13 adjudicated: roadmap 'closed as a scope decision (PR #92)' and ADR-025 'Explicitly NOT fixed (technical gap, by design)' now explicitly agree. (f) docs/README.md counts + footer refreshed. (g) removed the stale KG-stub comment in viin_ai_brain/brain_tools.py (C10: comment ONLY, KG method bodies untouched) + stale test docstrings. (h) docs/migration.md rewritten (the migration lives in odoo-tvtma17/viin_ai_chatbot, not ai17; the data-map is preserved). + Authored ADR-029 (M7 v1 release-hardening: two-PR split + chatbot reframe/migration + AGENTS.md 0.1 INTACT + deploy-sequencing + Brain-in-scope + self-hosted embedding); ADR-028 left Proposed. + docs/roadmap.md updated to reflect M7 v1 delivered. + Run-flag doc-syncs: ADR-027 index rows -> accepted; docs/ai/security.md sibling-test citations tightened to exact paths; viin_brain_linkable docstring (project.task bound). AGENTS.md 0.1 (no-migration-script rule) UNMODIFIED (AC#1). Signed-off-by: David Tran <davidtran.hp@gmail.com> Failed
merged [IMP] viin_ai: ground answers on attached Sources, or refuse - and enforce the Assistant/Skill boundary [FIX] test_viin_ai: drive the end-to-end Sources runs as a real user A sibling fix (845d5355) made viin_ai_agent refuse to retrieve Sources under a superuser environment: the record-rule filter behind indexed passages is bypassed there, so a routine-driven assistant could otherwise read documents nobody may read. That guard is correct, but this module's two full-stack Sources-grounding tests drove agent.run() straight off the bare TransactionCase superuser env, so both started failing for the wrong reason - hitting the new guard instead of the behaviour they were written to prove. Each fixture now creates a plain internal user (base.group_user only, scoped to the test company) and drives every run through agent.with_user(that_user).run(...) instead of the superuser call - the same adaptation the sibling fix already applied to its own grounding suite. This is also the more honest fixture: every real surface (chat, livechat, controllers) drives an assistant as a real user, never as root. Neither test's assertions changed - only the actor did, so both still prove exactly what they were written to prove: a retrieved passage reaching the model for a grounded answer, and a chat-only-key configuration refusing with zero chat egress. The module's other full-stack fixtures were swept for the same pattern; none needed the same repair because none attach a source to an agent before calling run(), so the guard's no-sources fast path returns before it is ever reached. Verified on a fresh install of an ephemeral 17.0 instance: 0 failed, 0 errors of 42 tests, with no warnings tied to this change. Signed-off-by: David Tran <davidtran.hp@gmail.com> Succeed
merged Surface L4: drop cancelled viin_ai_api (+ webhook), ship viin_ai_livechat (ADR-027) [FIX] viin_ai_livechat: assert+mute the expected guard WARNINGs in the two refusal tests The visitor-path guards (tool-bearing agent / no least-privilege service user) correctly log a WARNING in production - those are genuine misconfigurations an admin should fix. But the two negative-path tests that deliberately construct such agents leaked that WARNING to the Runbot / go-live log. Wrap each guard-triggering call in assertLogs: this BINDS the 'refuse + warn + hand to human' contract (the test now reds if a future change drops the guard warning) AND keeps the expected warning out of the log (assertLogs sets the logger's propagate=False for the context). Production behavior is unchanged - test-only. Killed
merged Surface L4: drop cancelled viin_ai_api (+ webhook), ship viin_ai_livechat (ADR-027) [FIX] viin_ai_livechat: auto_install=True (clears the test_lint auto_install-default manifest gate) Runbot test_lint (to_base ManifestLinter) rejected 'auto_install': False as a redundant default-valued key. Owner decision: make it auto_install=True - the module auto-enables AI-in-livechat when the AI cluster (viin_ai_agent+viin_ai_chat) and live chat (im_livechat+website_livechat) are all present (connector auto-install pattern), and is inert until an admin adds an ai_agent chatbot step, so auto-install carries no visitor-facing behaviour on its own. ADR-027 §1 updated to match. The co-install footprint was already exercised green in the 718-test regression gate (viin_ai_livechat co-installed with the AI cluster + test_viin_ai). Failed
merged Surface L4: drop cancelled viin_ai_api (+ webhook), ship viin_ai_livechat (ADR-027) [ADD] viin_ai_livechat: AI in live chat (scripted ai_agent step + operator-assist) Surface-L4 module per ADR-027. (a) Autonomous scripted operator: chatbot.script.step gains step_type='ai_agent' + ai_agent_id; _process_step delegates to the agent + posts the answer to the visitor (over im_livechat's own bus; plaintext2html + message_post sanitize = no stored XSS); forward_operator fallback on defer. (b) Operator-assist reuses the shipped action_ai_draft (human reviews and sends). Double-reply gate: the viin_ai_chat channel-level auto-reply skips channels driven by an active chatbot script; non-script channels auto-reply as before. SECURITY (external-visitor boundary): the visitor turn runs as a constrained least-privilege service user (seeded user_livechat_ai_bot, base.group_user only) with env.su hard-asserted False so RAG/tool/memory reads are ACL-scoped (a visitor cannot pull internal records); PII redaction forced ON for the visitor path regardless of the company opt-out (ADR-022 precedent); tool-bearing agents and superuser/no-service-user runs are refused at runtime and handed to a human. Tool-less advisory seed. CE-safe (im_livechat/website_livechat LGPL). No viin_brain dep. 12 tests incl. 5 security controls; 0/0 of 718 on the co-install regression gate. Failed
merged Surface L4: drop cancelled viin_ai_api (+ webhook), ship viin_ai_livechat (ADR-027) [DOC] drop the cancelled viin_ai_api surface from roadmap + architecture viin_ai_api was a roadmap placeholder (external REST + Prometheus /metrics) with no ADR, no design and no named consumer; the owner cancelled it. Purge every reference (roadmap, architecture layer diagram + dep tree + execution-mode table + request-flow, observability, REPO_LAYOUT, adr-024/wi-008 forward-notes) and reconcile the product-view count 43->42 (viin_ai_livechat survives as the sole Surface-L4 module). Zero-trace: no cancellation breadcrumb. Raw-manifest counts unchanged (viin_ai_api never existed on disk). Killed Not finished
merged Purchase AI pilot: Route-H draft write tool + approval advisory; pulse per-company cost-freeze fix [DOC] reconcile docs + roadmap + module map with current code Whole-doc audit (not just this PR's delta) against the worktree code state; docs/history/* excluded (historical snapshots). No code behavior change. PR #99 delta reflected: add viin_ai_approval_purchase to the AGENTS.md module map, roadmap §2 shipped table, ADR-015 pilots (README + adr-015 append) and §7/§10 counts (48->49 manifests, 2->3 Concern-B pilots); viin_ai_purchase create_purchase_order_draft DEFERRED->SHIPPED (Route H) in map + roadmap §5 + manifest summary, with an ADR-019 'first Route-H reuse beyond account' append; viin_ai_pulse cost_saved_estimate_usd corrected in data-models.md (compute->freeze-at-rate stored accumulator) + ADR-007 supersede note. Pre-existing drift also fixed: roadmap §2A Wave-3 (ADR-025 settings re-architecture) flipped DEFERRED->SHIPPED (code shipped; ADR-025 ratified 2026-07-21); viin_ai_brain dep gloss +viin_ai_editor; data-models.md website content-generator names (fictional create_blog_post_draft/translate_page -> real weekly cron _cron_weekly_blog_draft); roadmap §8 ADR narrative +ADR-025/026. Killed
merged Purchase AI pilot: Route-H draft write tool + approval advisory; pulse per-company cost-freeze fix [ADD] viin_ai_approval_purchase: AI risk advisory on purchase approvals Concern-B advisory bridge mirroring viin_ai_approval_sale (ADR-015): thin auto_install module (depends viin_ai_approval + viin_ai_purchase + viin_approval_purchase) seeding a tool-less Haiku advisory agent + topic. post_init_hook wires the enqueue hook + ai_advisory_agent_id onto the per-company PURCHASE_ORDER approval type BY CODE (non-clobber). _advisory_signal_values overlays PII-free purchase signals (amount_total, currency, order_date, state, product-only line_count); never emits the vendor name. No viin_brain dependency (ADR-010). Killed Not finished
merged [FIX] Multi-company residuals (blog-draft + Pulse cost rate) + settings-hygiene + roadmap reconciliation [DOC] roadmap: reconcile M5 gate, milestone status, ADR/connector counts The M5 gate required a 4-model as_of demo, but stock.move has no mail.thread so its state can never be reconstructed as_of - an architectural boundary, not a deferred item. Change it to 3-model, fix the contradicting M5 status line, add evidence-grounded status lines for M1/M2/M4/M6, and correct the stale connector count (8 -> 9) and ADR count (24 -> 26). Killed
merged [FIX] viin_brain: seed Brain knowledge base per company (E12) [DOC] decisions,AGENTS: record ADR-026 (Brain per-company seed) ADR-026 records the per-company Brain seeding decision: the single graph copier and its two callers (the res.company.create() override and the per-carrier post_init_hook backfill), why the backfill lives on the carriers rather than on viin_brain alone (install ordering), the leak vectors it measured and remaps, idempotency keyed on the vault's natural (name, company_id) pair, what is deliberately left uncopied, and the owner's ruling that seeded vaults stay scoped to their app group. Indexes the new ADR in docs/decisions/README.md and corrects AGENTS.md's ADR count from 25 to 26. Signed-off-by: David Tran <davidtran.hp@gmail.com> Killed
merged [IMP] viin_ai + viin_brain: per-company settings storage (ADR-025) [DOC] decisions,AGENTS: record ADR-025 (company-scoped settings storage) ADR-025 records moving AI and Brain settings storage from global ir.config_parameter rows to per-company res.company columns: what moved and what deliberately did not, the banned `company.field or icp.get_param(...)` fallback and why it fails open inside a fail-safe, the cap-of-zero constraint kept on both surfaces rather than moved, per-company trace GC including archived companies, the PII fail-safe default, and the one-directional group implication (a system administrator is an AI administrator; the reverse stays forbidden) with its full blast radius stated. ADR-023 gains an append-only amendment so a reader of it alone cannot conclude the reverse implication exists. The decisions index row for ADR-025 moves from reserved to shipped; AGENTS.md's ADR count is corrected from 24 to 25 (it was already stale before this work). Signed-off-by: David Tran <davidtran.hp@gmail.com> Killed
merged [FIX] viin_ai: per-company automation seed (#88) + BYOK cost-cap governance (#93) [FIX] viin_ai_rag: relative import in cost-governance test (W8150) test_firecrawl_cost_governance_93 imported FIRECRAWL_SCRAPE_COST_USD via the absolute `odoo.addons.viin_ai_rag.models.source` path from inside its own module, which the Viindoo test_pylint gate flags as W8150 (odoo-addons-relative-import). Switch to the relative `..models.source` import. A repo-wide pylint_odoo scan confirms this was the only real W8150 in ai17 - the other same-module test imports are top-level and exempt by the checker's tests/ rule. Follow-up to #93. Signed-off-by: David Tran <davidtran.hp@gmail.com> Killed
merged [FIX] viin_ai: per-company automation seed (#88) + BYOK cost-cap governance (#93) [FIX] viin_ai_workflow: drain all due routines per heartbeat tick _cron_run_routines fetched a single hard-capped batch (limit=50) then post-filtered by _is_due(), so once #88 seeds 8 routines per company the window was exhausted past ~6 companies and due routines drifted to later ticks (cadence drift / partial starvation). limit is now a per-iteration BATCH size and the method loops until every due routine is drained, excluding already-seen ids so a not-due routine cannot spin the loop. Batch size and an optional wall-clock time budget are configurable via ir.config_parameter (cron_batch_size default 50, cron_time_budget_seconds default 0 = unlimited). Every existing safety property is preserved - per-routine cr.savepoint() isolation, with_user/with_company, failure tracking outside the savepoint - and no cr.commit is added. Adds a scale regression test: N due routines beyond one batch drain in a single tick. Follow-up to #88. Signed-off-by: David Tran <davidtran.hp@gmail.com> Killed Not finished
merged [FIX] viin_ai: per-company automation seed (#88) + BYOK cost-cap governance (#93) [FIX] viin_ai: close independent review findings on #93 cost governance An independent review of the #93 BYOK cost-cap governance PR approved it (81/100, zero CRITICAL/HIGH) but flagged 4 MED + 3 LOW to close before merge. This closes all of them except the review's suggested MED-1 migration script, which is deliberately NOT added (see below). MED-2: viin_ai_rag source.py _process_pending caught AICostCapExceeded in the generic except-Exception branch, landing a capped Firecrawl source on permanent status='error' - inconsistent with the sibling media.py fix in this same PR (status='pending', retryable). Adds a dedicated except clause. viin.ai.source has no 'pending' status value (unlike viin.ai.media) - 'draft' is the model's actual retryable state (the search domain that feeds this sweep already re-selects draft/processing), so the fix mirrors the INTENT (retryable, not stuck) rather than copying the literal status string, which would have raised on write. MED-3: the Firecrawl cap CHECK (_enforce_daily_cost_caps, reads self.env.company) and the usage BOOKING (_record_firecrawl_usage, keys on source.company_id) read two different ledgers in a multi-company DB. _process_pending now scopes each source's whole processing pass with_company(source.company_id or self.env.company), so both calls agree on one ledger. MED-4: adds RED-before-green regression tests for the 3 previously untested cap-breach callers from the prior commit - brain_tools.py (re-raise, not silent ILIKE degrade), ir_model_fields.py cron (stop the whole sweep, not a per-record swallow), media.py (retryable 'pending', not permanent 'error'). Each docstring states the RED rationale (reasoned statically, no instance run). LOW-2: documents the budget_cost_usd=0 inversion (0 = defer to system cap, the OPPOSITE of the ICP caps' 0 = block) in the field help text. LOW-3: agent.run() can return status='budget_exceeded' with empty content WITHOUT raising (the per-run Guard 3 path, distinct from the daily-cap AICostCapExceeded raise already handled) - the discuss bot silently posted nothing in that case. Now posts the same budget notice for either signal. MED-1 NOT applied as suggested: the review proposed a migration script for the ir_cron.xml noupdate user_id pins. This repo has no deployed database (AGENTS.md 0.1, no-migration policy) - every install is fresh, and a noupdate record is created WITH the pin on fresh install, so the pin already reaches every install. Added a one-line comment instead; no migrations/ file, no version bump. Also reconciles docs/ai/architecture.md and docs/ai/security.md cost-cap sections, which previously flagged #93 as an open/unmerged defect, to describe the now-fixed behavior (all 3 guards live, stored-0 rejected, Firecrawl metered, breaches surfaced not silently degraded). Signed-off-by: David Tran <davidtran.hp@gmail.com> Failed
merged [IMP] repo: no-migration policy, 1.0.0 version pin, and doc reconciliation [DOC] reconcile docs with this session's merged changes Prevent doc drift after four changes landed or were decided this session. Grounded on a full drift sweep; each edit cites the change it reflects. Menu audience (#87 / PR #92): every internal user is the AI app's audience, a menu is a navigation hint not an access control. ADR-023 gets an append-only "superseded in part" note (menu gating only; the config group gate is unchanged); architecture.md and the ADR index record that a system admin now also reaches Configuration; roadmap marks the E13 open question closed. Brain child-model rules (#86 / PR #91): brain/security.md 3.4 rewritten from a "depends-on-benchmark" sketch to the shipped concrete record rules; brain/data-models.md records the vault role resolver cap (editor via access group now resolves to viewer). No-migration policy: ai/data-models.md, conventions.md and ai/README.md drop instructions to write migration scripts or bump versions; every module version citation across roadmap.md and the module tables is now 1.0.0; migration.md's void banner is widened to cover its rollback and v18 sections. External dependency pins (viin_approval 0.2.2, viin_workflow_automation 0.1.1, viin_api_request_logger) are left intact. Cost governance (#93, filed, NOT merged): architecture.md, ai/data-models.md, viin_ai_website/README.md and roadmap W3.2 stop asserting per-run cost-cap enforcement and point at #93; they describe an as-yet-unfixed defect and do not claim it is fixed - the fix and its full doc update land with #93. Killed
merged [FIX] viin_ai: AI app menu audience matches what the backend enforces (#87) [IMP] viin_ai_rag: correct a stale claim in the menu test rationale The docstring justified the defensive root-reachability gate in _reachable_under() with a point-in-time claim - that the Operations bucket had no descendants - which stopped being true once the bucket gained children. A later reader could have removed the gate on the strength of a sentence that no longer held. Restate the reason as the invariant it always was: a bucket is in the menu tree only through its VISIBLE descendants, so it can disappear for one user while holding children for another, no matter how many the XML declares. Comment only. No assertion, fixture or expected value changed. Killed
merged [FIX] viin_brain: enforce record-level ACL on page child models [FIX] viin_ai_brain: guard record history against deleted records Asking for the history of a record that no longer exists raised a MissingError instead of reporting that it did not exist, which the tool's own contract forbids. Deleted and unknown identifiers now resolve to the same empty state the tool already returns for a record created after the requested timestamp. The defect surfaced during a co-install run on a database without the demo record the previous test relied on; the new test creates and removes its own record so it no longer depends on demo data. Signed-off-by: David Tran <davidtran.hp@gmail.com> Killed
merged [IMP] docs: reconcile roadmap SSOT with app-identity-menu design-wave [IMP] docs: reconcile roadmap SSOT with app-identity-menu design-wave - Add roadmap 2A tracking the app-identity/menu/settings design-wave: Wave 1-2 shipped, Wave 3 settings re-architecture deferred/owner-approved/unblocked, Wave 4 icon re-cut blocked on W4.0; warn design-wave axis != calendar-week W1-W30; point at the .odoo-ai SSOT that lives outside git - Fix 2 version drift: viin_ai_chat 0.3.0, viin_ai_ops 0.2.0, viin_ai_brain 0.1.1 - Relabel stale "BLOCKED W17" -> deferred/not-yet-built (blocker execution_mode= 'scheduled' shipped in PR #53; livechat/api still absent on disk) - Soften stale account_reports fresh-install claim (backlink patch xpaths static re-verified 2026-04-25, commit d3d76965; keep live-certify caveat) - Index ADR-025 (settings re-architecture, accepted Option D 2026-07-15, impl deferred to design Wave 3, amends ADR-023) Killed Not finished
merged [ADD] viin_ai + viin_brain: real app icons, conventional menus, Brain as a sellable App (Waves 1+2) [FIX] viin_ai_agent: make Overview CTA test co-install-robust (W2) The W2-close cluster co-install gate red'd on test_open_chat_cta_falls_back_to_agent_form_when_chat_absent: it asserted the chat-absent fallback while merely ASSUMING viin_ai_chat absent (true only when viin_ai_agent installs alone), so co-installing viin_ai_chat correctly returned the real chat action and the assertion failed. The production action_open_chat is correct and unchanged; the test's unenforced precondition was the defect. Split into two deterministic tests that patch Environment.ref to force the chat action unresolvable / resolvable, exercising BOTH branches regardless of what is co-installed. Match the repo's unsigned tag convention. (cherry picked from commit e474586fefc9d493a0f70cf84f3300833f045f92) Killed
merged [ADD] viin_brain: v1.1 UX-completion batch - tag hierarchy+heatmap, db-view filter/sort, backlink Related, ShareDialog tour [FIX] viin_brain: _t()-wrap pre-existing 'Captured by' + model-search placeholder + locale-stable embed tour hooks Killed
merged [ADD] viin_brain: v1.1 UX-completion batch - tag hierarchy+heatmap, db-view filter/sort, backlink Related, ShareDialog tour [FIX] viin_brain: i18n hygiene (English-only _t()-wrapped source) + locale-robust new tours (no assert on translated labels) Killed Not finished
merged [ADD] viin_brain: v1.1 UX-completion batch - tag hierarchy+heatmap, db-view filter/sort, backlink Related, ShareDialog tour [FIX] viin_brain: locale-independent EmbedPicker + embed-recordset tour (model search by technical name, class-hook footer/tour triggers; was English-only 'Contact'/'Insert', red under vi_VN co-install) The viin_brain_embed_recordset_tour drove the EmbedPicker by matching the ENGLISH display name 'Contact', but res.partner's ir.model.name is translatable ('Liên hệ' under vi_VN), so the picker's name-only search surfaced nothing and Viindoo Runbot's vi_VN large co-install reddened the tour. A second, previously-hidden break: the picker footer's OWL text 'Insert' is localized by Odoo's merged client-side translation dict ('Chèn' under vi_VN), so the tour's :contains('Insert') trigger also missed - the run reached it only after the model fix. Fix (locale-independent, also a real UX/testability improvement): - embed_picker.js: onModelSearch now matches BOTH the translatable name AND the locale-stable technical 'model' field, so any user can find a model by typing 'res.partner' in any language. - embed_picker.xml: expose each row's technical model via data-model; add stable o_brain_embed_next_btn / o_brain_embed_insert_btn classes to the footer action buttons. - tour: select the res.partner row by [data-model='res.partner'] and drive Next/Insert by their stable classes instead of translated label text. The 'Captured by' assertion is kept (it is a plain JS string literal in slash_commands.js, never _t()-wrapped, so it is locale-safe). Verified GREEN under BOTH en_US and vi_VN (admin lang vi_VN + mass_mailing co-install, res.partner='Liên hệ'): 0 failed, 0 error of 2 tests each. Pre-existing tour/component bug, byte-identical on origin/17.0; not introduced by this batch. Failed
merged [17.0][ADD] AI First-Run + MVP Chat - single-key any-vendor resolution, onboarding, chat sessions [FIX] viin_ai: has_api_key compute must not warm shared api_key cache (co-install #393211) _compute_has_api_key read presence via bool(r.sudo().api_key). That ORM sudo attribute read populates the transaction-wide cache for the group_system api_key field; the cache is shared across sudo/non-sudo envs and the field groups ACL is only enforced on a cache MISS, so a later low-privilege record.api_key attribute access returned the cached secret WITHOUT the ACL check - defeating the very ACL the docstring promised to preserve. The WI-1 usable-provider resolver (_is_usable -> has_api_key / _resolve_active_default) calls this in the provider-resolution hot path that runs just before a Brain query-embedding call. Decisive base-vs-head repro (Runbot subbuild #393211, co-install viin_ai_brain -> viin-brand-snailmail): on HEAD a low-privilege brain semantic search (test_semantic_search_ilike_ fallback_*, uid 12/13) read the key from the warmed cache and fired a REAL external embedding call, blocked by the test egress guard ("External requests verboten"); the error-logging path then hit a rolled-back user (MissingError -> viin_api_request_logger "Failed to create API request log" ERROR + provider "could not write error usage.log" WARNING -> Runbot red). On BASE (ef0d57fc) the api_key field ACL correctly denied the read and forced the ILIKE fallback, no external call, no error. Tests PASS on both series (0 failed/113); the regression is LOGGED-ERROR-only - Runbot reds on the ERROR line, the assertion never fails. Root cause is in viin_ai (this PR's own module), not viin_ai_brain (out of scope) nor viin_api_request_logger (a tvtmaaddons dependency that merely amplified the exposed call). Fix: compute presence straight from the row via SQL (flush first so an uncommitted api_key write is reflected; NewId records keep the plain cache read), so the safe boolean never pulls the secret into the shared cache. Behaviour-preserving: has_api_key value unchanged, and the low-privilege Brain search degrades to ILIKE exactly as before this PR. Regression guard: TestProviderApiKeyAclCacheLeak asserts the deterministic invariant (api_key never enters the shared cache via has_api_key / _is_usable) - RED on the old bool(sudo().api_key) body (2 failed), GREEN on the fix. The downstream attribute-access bypass itself is prefetch-order sensitive so the cache-state invariant is the reliable oracle. Verified: HEAD-with-fix co-install re-run -> verboten/MissingError/ Failed-to-create-API-log all 0 (was 2/4/2), api_key AccessDenied restored, 0 failed/113; full viin_ai (398) + viin_ai_rag (53) suites 0 failed. Killed
merged [17.0][ADD] AI First-Run + MVP Chat - single-key any-vendor resolution, onboarding, chat sessions [FIX] viin_ai: make TestProviderResolverUsable hermetic - neutralize demo mock in test_usable_key_wins_over_keyless_seed + test_chat_pin_does_not_affect_embedding (co-install gate, Runbot #393210) Killed Not finished