Skip to content

AI Opportunity triage tools (ai 1.6.0) — Production Readiness Audit

FieldValue
Date2026-09-15
Re-audited2026-09-15 (all code-fixable findings closed)
StatusGo for production — remediations closed; Backend + Docs; no SPA/Mobile code
ScopeAsk EloSync Opportunity tools: get_opportunity, get_opportunity_stages, confirmed writes update_opportunity_stage / assign_opportunity / add_opportunity_note; catalog ai 1.5.0 → 1.6.0
CompanionAI deployment · AI production readiness (platform) · AI Task triage · AI Help Desk triage · AI tools · Tenant AI API · User guide · Opportunities · CHANGELOG

Executive summary

Additive AI tools depth on the existing AIGateway + AIToolRegistry + PendingAiActionService path. Reads: fetch one deal + list pipeline stages. Writes: three low-risk tools that propose pending actions; mutations run only after POST /ai/actions/{id}/confirm. Stage changes use integer stage_id (HTTP POST …/stage parity, tenant-scoped exists). Assign uses opportunities.assign + EligibleOpportunityAssignee at propose and confirm. Notes are text-only with confirm-time max:5000. No AiToolAnyOfPermissions (Opportunities has no complete/close permission). No new Spatie permissions, no Opportunities catalog bump, no Frontend/Mobile changes.

Go / No-Go: Go. Cutover: migrate-only catalog bump to 1.6.0, entitle ai + opportunities, staging smoke propose→confirm.

GateResult
Platform freeze (no parallel AI stack)Pass
UUID opportunity lookup; soft-deleted excludedPass
Stage propose/confirm: opportunities.update + policy update + tenant stage existsPass
HTTP POST …/stage tenant-scopes stage_id (matches store/update)Pass
get_opportunity_stages + search stage_id / assignee idPass
Assign gated by opportunities.assign + eligible assignee (propose and confirm)Pass
Note gated by opportunities.update + policy update + body max 5000 on confirmPass
Propose does not mutatePass
Catalog migrate-only ai 1.6.0 + CatalogSeeder syncPass
Docs (tools, API, user tip, Opportunities cross-links, overview, ops, roadmap, changelog, upgrade, VitePress)Pass
Pest write confirmation + registry + HTTP cross-tenant stagePass
Playwright AI e2eN/A — no SPA surface change

Locked-decision matrix

DecisionBackendFrontendMobileDocs
Read tools include stages list + get one (Opportunity Copilot HTTP unchanged)PassPass
Confirmed writes via existing pending actionsPassPass (generic UI)Pass (generic UI)Pass
Stage via stage_id integer (not slug/name)PassPass
No complete/close any-of permissionsPassPass
Catalog MINOR ai 1.6.0 only (not opportunities)PassPass

Findings

Closed (audit remediations)

IDSeverityFindingResolution
R1LowConfirm-time deny without opportunities.update untestedPest: confirm stage as view-only + ai.confirm → 403
R2LowInvalid stage_id untestedPest: propose and confirm invalid id → validation; no mutation
D1LowOpportunities overview / ops omitted Ask EloSyncOverview bullet + ops companion link
A2MediumHTTP ChangeOpportunityStageRequest exists was not tenant-scopedTenant where like store/update; Pest cross-tenant stage → 422; AI assertStageExists also filters tenant_id
A3Mediumsearch_opportunities serialized stage/assignee as names onlyPayload now includes stage_id, assigned_to (user id), assignee_name; stage name kept
A4MediumNo stages list toolget_opportunity_stages (read-only, opportunities.view); stage write description points at it
T1LowStale HTTP Pest expected owner assign to 422 (owners are eligible outside Leads)Assert suspended assignee → 422 instead

Accepted (intentional / ops — not defects)

IDSeverityNotes
A1InfoWon/Lost are stage flags; moving to Won/Lost uses the same opportunities.update gate as HTTP. Inventing a close permission would break API parity.
A5OpsPlatform AI still needs Central ai_api_key + adequate FPM timeouts (unchanged from AI platform readiness).

Authz map (HTTP ↔ AI)

ActionHTTPAI tool / confirm
List stagesGET /opportunity-stages + viewget_opportunity_stages
Get oneview (assignee-scoped unless assign / superadmin)get_opportunity
Change stageupdate + POST …/stage (tenant-scoped stage_id)update_opportunity_stage → permission + Gate::update + stage exists
Assign / unassignassignassign_opportunity + EligibleOpportunityAssignee at propose and confirm
Note (text)updateadd_opportunity_note + confirm max:5000

Code review notes

CheckResult
OpportunityAiSupport::resolveByUuid excludes deleted_atPass
Writes return pending_confirmation; mutation only in confirm armsPass
Confirm injects OpportunityService (changeStage / assign / addNote)Pass
Registry: search → pipeline → stages → get → writesPass
Risk ReadOnly / LowRiskWrite; never DestructivePass
Opportunity Copilot HTTP endpoints unchangedPass

Done (engineering)

ItemResult
Support helper + get/stages/search ids + write tools + confirm pathPass
Tenant-scoped HTTP + AI stage existsPass
Migration + CatalogSeeder ai 1.6.0Pass
Docs same-PR + sidebar discoverabilityPass
Pest (AI write/authz/read + HTTP cross-tenant stage)Pass

Test evidence

bash
php artisan test --compact --filter="opportunity" tests/Feature/Tenant/Ai/AiWriteConfirmationTest.php tests/Feature/Tenant/Ai/AiAuthorizationTest.php tests/Feature/Tenant/Ai/AiReadToolsTest.php tests/Feature/Tenant/Opportunity/OpportunityTest.php
vendor/bin/pint --dirty --format agent

Result (2026-09-15): 42 passed. Pint: passed.


Upgrade / staging smoke

After migrate (2026_09_15_060000_bump_ai_module_version_to_1_6_0):

  1. Confirm central catalog ai.version = 1.6.0 (do not db:seed).
  2. Workspace has AI + Opportunities entitled; user has ai.use, ai.confirm, and Opportunity domain perms as needed.
  3. Ask EloSync: list stages → fetch opportunity → propose stage/assign/note → Confirm.
  4. User without opportunities.assign never gets assign tool; view-only never gets stage/note writes (still gets get_opportunity_stages).
  5. Optional: propose assign, suspend assignee, Confirm → 422.
  6. Optional: propose invalid stage id → validation error; no mutation.

See Upgrade Guide · AI deployment.


Operator remaining

#ActionOwnerStatus
1Merge Backend + Docs PRs (if not already on main)EngTodo
2Deploy Backend; migrate catalog ai → 1.6.0OpsTodo
3Staging smoke (stages / stage / assign / note)OpsTodo
4Confirm Central AI API key + FPM timeouts (A5)OpsTodo

Rollback

Redeploy previous Backend release. Catalog version may remain 1.6.0 (display-only); tools disappear with the code rollback. Pending actions for removed tools fail confirm with unsupported tool — cancel or let expire (24h).


Monitoring

Unchanged from AI platform readiness: credit wallet / ai_usage_events, ai.chat_completed, HTTP 402 when credits exhausted. Opportunity domain events still fire on confirm mutations (opportunity_stage_changed, opportunity_assigned, opportunity_note_added).


Verdict

Go for production after migrate-first Backend deploy + staging smoke. All code-fixable findings closed (R1–R2, D1, A2–A4, T1). A1 remains HTTP authz parity; A5 is operator configuration.

Official documentation for the EloSync SaaS Platform.