Insights are children of reports — make the URL hierarchy reflect that.
Endpoints:
- POST /v1/manager/reports/:reportId/insights (manager Create)
- GET /v1/reports/:reportId/insights (List)
- GET /v1/reports/:reportId/insights/:insightId (Get)
- PATCH /v1/reports/:reportId/insights/:insightId (Update)
- DELETE /v1/reports/:reportId/insights/:insightId (Delete)
`reportId` moves from optional body field to required path param. All
endpoints fetch the report first (404 REPORT_NOT_FOUND if missing or
in another project), then verify the insight's `reportInternalId`
matches before doing anything else.
Side effects:
- Event names nested: `reports.[reportId].insights.[insightId].create`
etc. Top-level `insights.*` event tree removed from events.php.
- Realtime channel parser handles the nested form: a `reports.{rid}`
event lights up `reports`, `reports.{rid}` channels; a nested
`reports.{rid}.insights.{iid}` event also lights up
`reports.{rid}.insights` and `reports.{rid}.insights.{iid}`.
- Audit resource paths nested similarly:
`report/{request.reportId}/insight/{response.$id}`.
- listInsights query validator drops `reportId` from
ALLOWED_ATTRIBUTES — it's path-scoped now, not a query filter.
Tests:
- E2E helpers `createInsight`/`getInsight`/`listInsights`/
`updateInsight`/`deleteInsight` all take `reportId` as the first
argument.
- New `createFixtureReport()` helper for standalone validation tests
that need a parent.
- Dropped `testCreateWithoutReport` — reportId is mandatory now.
- `testCreateRejectsUnknownReport` now exercises the path-level 404
rather than a body-level check.
- `testGet` and `testUpdateMissing` exercise the
wrong-reportId-but-valid-insightId case (returns
`report_not_found`).
- `testList` asserts every result carries the path's reportId, plus a
404 case for a nonexistent parent.
- `testCreateForEachEngine` and the standalone create-rejection tests
inline-create their own fixture report and clean up after.
- `testListSurvivesEmptyDatabase` renamed to
`testListSurvivesEmptyReport` and uses a fresh fixture report.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address review feedback on PR #12194:
- Pivot CTAs to pure descriptors (id/label/action/params). Drop the
server-side execution layer: Action interface, registry, the
databases.indexes.create CTA action, the params validator, the
/v1/insights/:id/ctas/:id/executions endpoint, the InsightCTAExecution
model, the INSIGHT_CTA_* errors, and the corresponding events. The
console invokes the existing public API directly with the descriptor's
action + params.
- Restore Databases\Indexes\Action.php to its pre-CTA shape and inline
the index-create body back into Create.php (the createIndex helper
was added solely for CTA reuse).
- Move insights collection from project DB to platform DB and add a
parent reports collection alongside it. Insights carry projectId /
projectInternalId for tenant scoping and an optional reportId for
grouping. List endpoints filter by projectInternalId; Get/Update/
Delete also enforce project ownership before touching the document.
- New Reports module with full CRUD (Create/Get/XList/Update/Delete),
Report response model, Reports query validator, REPORT_NOT_FOUND /
REPORT_ALREADY_EXISTS errors, reports.read / reports.write scopes,
and reports.* event tree. Delete cascades to child insights.
- Update.php now mutates the loaded document via setAttribute (instead
of passing a partial new Document), reuses CTAsValidator (instead of
the looser ArrayList<JSON> + isset check), and rejects duplicate CTA
ids.
- Create.php enforces unique CTA ids during normalization.
- CTAsValidator gained a configurable maxCount (default 16) so the
Create path matches the Update path and the DB column size, and
oversized payloads return a clean 400.
- Validator\Queries\Insights adds status and reportId to
ALLOWED_ATTRIBUTES so dismissal / report workflows are filterable.
- Realtime channel parser guards $parts[1] for both insights and
reports event names.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dismissal was a sub-resource (POST /v1/insights/:id/dismissals) but a
dismissal is just a state transition, not a thing the client creates.
Drop the dedicated endpoint and add a `status` enum (`active` |
`dismissed`) to the insights schema, settable via the existing PATCH
update route. The server still derives `dismissedAt` and `dismissedBy`
on transition for audit/sorting, but the client-facing API is just a
single status toggle.
- Schema: add `status` attribute (default `active`)
- Constants: add `INSIGHT_STATUSES`
- Update endpoint: accept `status` param, derive dismissedAt/By on
active <-> dismissed transitions
- Response model: add `status` rule
- Drop Insights/Dismissal/Create.php, the createInsightDismissal SDK
method, the `insights.[id].dismissals.create` event, and the
`insight.dismissal.create` audit
- E2E: replace testCreateDismissal with testDismissViaUpdate covering
both directions of the toggle
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move POST /v1/insights/:id/dismiss to /v1/insights/:id/dismissals
and POST /v1/insights/:id/ctas/:ctaId/trigger to
/v1/insights/:id/ctas/:ctaId/executions, with the corresponding
class moves into Http/Insights/Dismissal/Create.php and
Http/CTA/Execution/Create.php. Rename the response model to
InsightCtaExecution and update events.php to surface dismissal
and execution as resource events with create verbs. The reshape
matches the rest of the API where verbs hang off plural sub-resources.
Wires the platform glue for insights: the `insights` collection on the
project database, the `insights.read` / `insights.write` scopes, the
`insights.[insightId]` event tree (including the nested `ctas.[ctaId].trigger`
event), the typed exceptions, and the runtime CTA registry resource.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>