Disambiguate the platform-level collection name. Field/request-param
remains `ctas` (the embedded array on the insight response).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Embedding CTAs as a 16384-byte JSON blob on `insights` was the wrong
shape — they're real documents with their own lifecycle. Move them out.
Schema:
- New platform `ctas` collection. Each row carries `projectInternalId`,
`projectId`, `insightInternalId`, `insightId` (backref), plus the
CTA fields: `key`, `label`, `service`, `method`, `params`.
- Indexes: `(projectInternalId, insightInternalId)` for the subquery
lookup and a UNIQUE `(insightInternalId, key)` so the per-insight
uniqueness invariant lives at the DB layer (not just in PHP).
- The `ctas` field on `insights` becomes a virtual attribute backed by
a new `subQueryInsightCTAs` filter that joins child docs at read
time. Consumers still get CTAs embedded on the insight response —
one round-trip from their perspective.
- The CTA descriptor's within-insight identifier renamed `id` → `key`
(clashed with the document `$id`). Validator updated.
Endpoints:
- Manager Create now persists CTAs as separate `ctas` documents after
the parent insight, then re-fetches the insight so the response
carries the freshly-joined CTA list.
- User Update trimmed to user-controlled state only (`severity`,
`status`). `title`, `summary`, `payload`, `ctas`, and `analyzedAt`
are analyzer-controlled — analyzers re-ingest by deleting and
POSTing again to the manager endpoint.
- Insight Delete cascades to CTAs.
- Report Delete cascades through Insights → CTAs.
Response model:
- InsightCTA gains the standard document headers (`$id`,
`$createdAt`, `$updatedAt`) and an `insightId` backref. The
caller-supplied identifier is now `key`.
Tests:
- E2E sampleCTA factory uses `key` everywhere; testCreate asserts the
freshly-created CTA carries `$id`, `$createdAt`, `insightId`, and
the right shape.
- Dropped the testUpdate*CTA* tests — user Update no longer accepts
CTAs. testDismissViaUpdate now depends on testUpdate directly.
- Unit tests rewritten to validate `key` instead of `id`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eldad's review comment: insights about nested resources need a pointer
to the containing parent (the file-in-bucket pattern). Add three
optional fields:
- parentResourceType (plural noun, e.g. `tables`, `collections`)
- parentResourceId
- parentResourceInternalId
so an insight whose `resourceType=indexes` / `resourceId=_idx_status`
can also carry `parentResourceType=tables` / `parentResourceId=orders`
to identify the table that owns the index. All three are nullable for
top-level resources (e.g. a project-wide audit finding).
Schema, response model, manager Create endpoint, and the listInsights
query validator (parent fields are filterable). New compound index
`_key_project_parent_resource(projectInternalId, parentResourceType,
parentResourceId, $sequence)` to support the parent lookup pattern
the console will use ("show all insights for table X").
E2E factory generates a parent by default (engine-aware:
tables for tablesDB, collections for the others). New
testCreateWithoutParentResource exercises the top-level case;
testList gains a parent-resource filter assertion; testUpdate's
preserved-fields check picks up the new attributes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Insights are produced by internal Appwrite services (edge, executor,
background analyzers) — never by user clients. Move the ingestion
endpoint accordingly.
- Move Http/Insights/Create.php → Http/Manager/Insights/Create.php.
- Path: /v1/insights → /v1/manager/insights. SDK Method marked
`hide: true` and namespaced under `manager` so generated SDKs don't
expose it. Auth narrowed from [ADMIN, KEY] to [KEY] only.
- New scope `insights.manager`. Not granted by any user role
(app/config/roles.php) — Cloud/edge teams configure their internal
key issuance to grant it. `insights.write` description trimmed to
the user-facing surface (update/dismiss/delete) since create is now
manager-only.
- Reports, ListInsights, GetInsight, UpdateInsight, DeleteInsight
remain at /v1/insights/*. Existing scopes unchanged.
- Reports `categories` switched from JSON-encoded string to a native
array<string> column (size 64 per entry, up to 32 entries via the
endpoint validator). MySQL JSON-array indexes are weak and we never
query individual entries — read+rewrite only.
- E2E test API key in tests/e2e/Scopes/ProjectCustom.php gains
insights.read/write/manager + reports.read/write so the manager
endpoint is reachable from the test harness.
- E2E InsightsBase.createInsight() helper now POSTs /manager/insights.
- New testCreateRequiresManagerScope verifies a key with
insights.read/write but no insights.manager is rejected with 401.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- InsightCTA model now exposes `service` (SDK namespace) and `method`
(function name) instead of a single `action` string. Drops the
string-splitting burden on every consumer and lets the console reach
the right SDK method directly.
- Validator requires both `service` and `method` non-empty; same
16-entry max still enforced.
- Endpoint normalization (Create + Update) splits the new shape into
the persisted CTA descriptor.
- Constants split: INSIGHT_CTA_SERVICE_* (databases / tablesDB /
documentsDB / vectorsDB) and INSIGHT_CTA_METHOD_* (createIndex).
- Insight model + InsightCTA model docs updated with the new field
semantics and per-engine examples.
- E2E factory `sampleCTA($id, $engine)` emits the correct service and
engine-appropriate params keys (tableId/columns for tablesDB;
collectionId/attributes everywhere else). Engine matrix asserts
`service` and `method` independently.
- Added e2e + unit coverage for the new failure modes (missing
service, missing method, empty service, empty method).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Restructure InsightsBase trait with explicit helper methods
(createInsight/getInsight/listInsights/updateInsight/deleteInsight,
createReport/getReport/listReports/updateReport/deleteReport, plus
sampleInsight/sampleCTA factories) — same shape ProxyBase uses.
- Add coverage for: report CRUD + duplicate-id rejection, invalid type
rejection, list filtering by all allowed attributes, cursor
pagination + missing-cursor, update preserving untouched fields,
CTA validation edge cases (duplicate ids, empty fields, count > 16),
dismissal round-trip + status filter, report cascade delete,
unauthorized access (no server key), empty-result list.
- Engine-specific insight types (tablesDBIndex, documentsDBIndex,
vectorsDBIndex, plus the legacy databaseIndex) so the CTA's `action`
can map to the matching public API: databases.createIndex,
tablesDB.createIndex, documentsDB.createIndex,
vectorsDB.createIndex. dataProvider drives the engine matrix and
asserts the right action lands in the persisted CTA. Constants for
each action name live in app/init/constants.php.
- InsightCTA model docs spell out which action belongs to which engine
and that the params keys differ between APIs (tableId/columns for
tablesDB vs collectionId/attributes for the legacy / DocumentsDB /
VectorsDB APIs).
- Insight model `type` description now lists every engine variant.
- CTAsTest gains coverage for object-shaped params, empty-action and
empty-label rejection, and the default 16-entry cap.
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>
Moves Validator/CTAParams/DatabasesCreateIndex to
Validator/CTA/Databases/Index/Create so the validator hierarchy mirrors
the action hierarchy (Insights/CTA/Action/Databases/Indexes/Create).
Also tightens the Update and CTA execute endpoint descriptions to call
out the dismissal-via-status flow and what the execution result carries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the body of the public databases create-index endpoint into a new
final `createIndex(...)` helper on the abstract Indexes Action so both
the HTTP-facing CE action and the insights CTA share one source of truth
for index validation, persistence, and queue dispatch.
The CTA's `Appwrite\Insights\CTA\Action` becomes a small interface
(`getName()` + `execute()`); the dispatcher now calls `$action->execute(...)`
directly instead of poking at Utopia Action internals via `getCallback()`
and `getParams()`. The CTA's `Create` extends the CE Indexes `Create`
so it inherits `createIndex()` for free, while keeping a no-op constructor
to skip the HTTP route registration that runs in the parent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CTA execution dispatcher invokes the action callback positionally
with values that have already been validated (insight fetched and
asserted non-empty in the endpoint, project pulled from the request
context). Re-validating them through param declarations adds noise
without catching anything. Drop the declarations and the validator
classes/tests created for them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the no-op `fn () => true` validators (gated by skipValidation)
on the `insight` and `project` params with dedicated InsightDocument and
ProjectDocument validators. They check that the injected value is a
non-empty Document with the expected attributes, so a misconfigured
dispatcher or unbound injection fails fast with a useful message.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the action class from Action/DatabasesCreateIndex.php to
Action/Databases/Indexes/Create.php so the directory mirrors the
underlying resource hierarchy. Action name follows: databases.createIndex
becomes databases.indexes.create, with the constant renamed to
INSIGHT_CTA_ACTION_DATABASES_INDEXES_CREATE.
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>
Project-specific override of the default camelCase-acronyms convention:
namespaces, class names, file paths, and SDK method names use `CTA` in
all caps. Touches all insights surfaces — directories, response models,
validators, container resource keys, and SDK method names like
`createInsightCTAExecution`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Persist the insight CTA registry across requests by attaching it to the
boot-time global $register, mirroring the pattern used for `geodb`,
`passwordsDictionary`, `hooks`, etc. The container resource now looks
the registry up from `$register` instead of rebuilding it on every
request.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract the existing test methods into InsightsBase trait and
provide thin Custom{Server,Console}Test classes that compose the
base with the appropriate scope/side traits. Updates the dismissal
and CTA execution tests to the new sub-resource paths.
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.
Replace the inline CTA shape check with a dedicated Utopia validator
so the constraint surfaces as a normal 400 with a useful message
rather than a generic argument-invalid exception.
Replace the bespoke CtaAction/Registry with Utopia\Platform\Action
and Utopia\Registry\Registry. Implement DatabasesCreateIndex with
the full createDocument('indexes') + queue path used by the existing
indexes endpoint, validated via a dedicated Utopia validator. Drop
the obsolete unit tests (custom-Action contract) in favor of
validator-focused tests.
- Add inline comments listing possible values for enum-bounded attributes
- Use VAR_ID for resourceInternalId
- Drop trailing 0 from composite index lengths and use $sequence over $createdAt
- Drop redundant ORDER_ASC values (default direction)
Unit tests cover the CTA registry register/resolve/has/all behaviour
and the DatabasesCreateIndex action's name, scope, validation surface,
and not-implemented execute path. The e2e suite runs the full CRUD
lifecycle, dismiss, and CTA trigger paths against a real cloud project,
including authentication boundaries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dismiss is a convenience that stamps `dismissedAt` and `dismissedBy` so
analyzers can see an insight has been acknowledged without losing the
record. CTA trigger looks up the action in the runtime registry,
validates the params blob, executes the action, and returns the result
as an `InsightCtaResult`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the create, get, list, update, and delete endpoints under the
`insights` SDK namespace. Mutating endpoints are admin/key-only because
insights are produced by analyzers; reads are open to sessions and JWTs
so console UIs can surface them. Updates use sparse documents so unset
fields keep their existing value.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the platform module so the http service is discoverable, registers
it on the Appwrite platform alongside the other modules, exposes the
`insights` and `insights.[insightId]` realtime channels via the messaging
adapter, and ships the queries validator covering the indexed attributes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Adds the Insight, InsightCta, and InsightCtaResult response models and
registers their model identifiers on the Response class so endpoints can
serialise insights consistently across the SDK surface.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the call-to-action framework: a server-side registry plus an
Action interface that lets analyzers offer one-click remediations alongside
each insight. Ships a stub `databases.createIndex` action whose execute()
defers to a future cloud-side implementation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>