Repository navigation
feat(core): published definitions are integration types like code; code can embed one; workspaces own their own - #696
Conversation
…ike code
A published definition is now installed, listed, named and routed exactly as a
code integration; only the deployment underneath is shared.
- integration-types.js: resolveIntegrationClass / listIntegrationClasses. A
registered class may be a provider (static IntegrationProvider); the shared
runtime class provides one generated class per published definition, named
by its id. Registered classes win over a definition with the same name.
- Integration use cases (create, get, update, delete, instance, options) and
the queue worker resolve config.type through it, so { type: '<id>' }
installs and pins the latest version, and GET /api/integrations lists code
and published types in one list.
- IntegrationBase.getDeploymentName(): a hosted class queues and schedules on
its host's queue while logs, telemetry and job ids carry its own name.
- Webhooks: an installation's URL is its own type's; the host's webhook
function also serves /api/{integrationType}/webhooks[/{id}] (devtools adds
the routes; registered integrations keep theirs). App-level webhooks fan
out to installations of every provided type.
- Upgrade, runs, syncs and decision routes answer for every integration (an
empty answer for code, not a 404). Publishing moves to
/api/admin/integrations/versions; the old paths still answer.
- Fix: an approval step's named approvers can decide without owning the
installation; anyone else gets a 404.
- ADR-063 accepted; route table, shapes and the v0.1 note updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGC9SnFuqdjX2xH2qeu8vw
…aths Never on next or published, so nothing depends on them; publishing lives at /api/admin/integrations/versions only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGC9SnFuqdjX2xH2qeu8vw
… runtime
extendIntegrationClass(CodeClass, { definition, manifests }) gives a
hand-written integration what published ones have: workflows with delay
and approval pauses, syncs, UI surfaces, the generated configure form and
run history, through the same records and routes.
- The class keeps its name, modules, routes, schedules, lifecycle and
handlers; the definition's id must be its name and it is validated at
class creation.
- Code steps may call the class's own methods with ref "#method"; a
published definition cannot (validate's codeMethods option).
- Webhooks, the configure form and schedules stay the code's unless the
definition declares them; code schedules run beside compiled ones.
- IntegrationEventDispatcher.dispatchJob calls runEvent when an
integration has one: the class's own queued events are recorded as runs
(triggerType "code"); compiled workflows record only their own run. The
history never decides the handler's outcome.
- The embedded definition ships with the code: installations run the
deployed version and upgrade reports there is nothing to move to.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGC9SnFuqdjX2xH2qeu8vw
…edit The Integrator's "build your own": a signed-in user publishes an integration into their workspace, where it is listed and installed like any other, and no one else can see it. - IntegrationDefinitionVersion.workspaceId (both schemas; PostgreSQL migration 20261019120000_definition_workspace). Null is the app's own catalogue. - POST /api/integration-definitions publishes (or drafts) into the user's workspace; GET /api/integration-definitions/:id returns every version and the newest document for the editor, to its owner only. - An id has one owner: another workspace's, the catalogue's or a code integration's name is refused with 409 ID_TAKEN (admin publishing too). - resolveIntegrationClass / listIntegrationClasses take the asker's workspace: GET /api/integrations options and CreateIntegration see only the catalogue plus their own; hydrating an existing installation still resolves by id. An own integration's option carries editable: true. - CreateIntegration refuses a type the user cannot install before writing a record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGC9SnFuqdjX2xH2qeu8vw
There was a problem hiding this comment.
If your organization's extra usage balance is empty, an organization admin can add extra usage credits at claude.ai/admin-settings/usage. If its monthly spend limit was reached, an admin can raise it on the same page. If neither applies, contact Anthropic support.
Once extra usage is available, someone with write access to this repository can comment @claude review on this pull request to trigger a review.
| * X-Hook-Secret), otherwise queue the delivery as Frigg always does. The URL names the module (?module=). | ||
| */ | ||
| async onWebhookReceived({ req, res }) { | ||
| if (embedded && !ownsWebhooks(this.def)) return super.onWebhookReceived({ req, res }); |
There was a problem hiding this comment.
Embedded webhook handshake never runs
High Severity
Receipt constructs a dry instance and never calls initialize, so this.def is unset. For an embedded class, ownsWebhooks(this.def) is therefore always false and onWebhookReceived always delegates to super, skipping Slack/Asana-style handshakes even when the embedded definition owns webhooks.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit eb2b29b. Configure here.
|
ADR reconciliation needed after Stack A/B — drafted with Claude Code at Sean's request Severity: must-reconcile.
Merge order: #671 (registry) first; whichever of Stack A (#686) and Stack B lands second rebases over the shared files. |
…ations Move publish, history and deprecate from /api/admin/integrations to /admin/integrations (versions, :id/versions, :id/versions/:v/deprecate), beside /admin/scripts behind the same admin-auth middleware. Collect every route the definitions router serves into DEFINITION_ROUTES (method, path, auth) so moving the user routes onto the /api/v2 registry (#671) is mechanical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGC9SnFuqdjX2xH2qeu8vw
- Revert the status flip: Accepted is the merge of the ADR PR (ADR-043). - Route table: note that the routes move under /api/v2 through the #671 route registry (ADR-053), and that "workspace" is user.getId() for now. - Use a generic example id in prose. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGC9SnFuqdjX2xH2qeu8vw
…en code shadows a definition Publishing an id another workspace owns answered 409 ID_TAKEN, telling one tenant that another's id exists. It now answers 404 NOT_FOUND, identical to reading it (ADR-052 section 3). Catalogue and code-integration ids are public and keep 409 ID_TAKEN; the admin route is not a tenant and keeps it too. A definition ignored because registered code has the same name is logged once per process as frigg.integrations.definition.shadowed_by_code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGC9SnFuqdjX2xH2qeu8vw
…-4, triggers; definitions-as-types moved onto triggers) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGC9SnFuqdjX2xH2qeu8vw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGC9SnFuqdjX2xH2qeu8vw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGC9SnFuqdjX2xH2qeu8vw
…e triggers model carried into stack-5's additions Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGC9SnFuqdjX2xH2qeu8vw
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
There are 3 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 5d9f1be. Configure here.
| if (!latest) return null; | ||
| cls = classFor({ ...latest, workspaceId: (await definitions.ownerOf?.(type)) ?? null }); | ||
| } | ||
| return visible(cls, context) ? cls : null; |
There was a problem hiding this comment.
Deprecated definitions fail to resolve
High Severity
IntegrationProvider.resolve loads a type only through definitions.latest(), which returns a PUBLISHED document. After every version is DEPRECATED, that call is null, so existing installations of { type: '<id>' } no longer hydrate. The same list()/PUBLISHED-only view drives triggerTypes(), so those installations also drop off the host tick. Deprecation is supposed to block new installs while pinned rows keep running.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 5d9f1be. Configure here.
| let out; | ||
| try { out = await invoke(); } catch (e) { await write('failed', e); throw e; } | ||
| await write('completed'); | ||
| return out; |
There was a problem hiding this comment.
Framework events recorded as code runs
Medium Severity
runEvent treats every non-compiled dispatchJob event as a triggerType: "code" run. Production ticks and webhooks arrive as FRIGG_TRIGGER_RUN and ON_WEBHOOK, not as NIGHTLY or WF_*, so those framework names are stored instead of the real handler, and a compiled schedule or webhook workflow is written twice. The tests only dispatchJob the inner event name, which the queue never does.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 5d9f1be. Configure here.
|
Release review follow-up (folded in at Done
Waiting
Generated by Claude Code |






Stack 5 of 9. Stacked on #695.
Summary
One kind of integration. A published definition is installed, listed, named and routed exactly like a code integration. Only the deployment underneath is shared.
integration-types.js:resolveIntegrationClass/listIntegrationClasses. A registered class can be a provider (static IntegrationProvider).config.typethrough it.GET /api/integrationslists code and published types together.IntegrationBase.getDeploymentName(): a hosted class uses its host's queue and schedules but logs under its own name./api/{integrationType}/webhooks[/{id}]. The routes are added in devtools./api/admin/integrations/versions. The unreleased/api/admin/data-definedpaths are removed.Code integrations embed a definition.
extendIntegrationClass(CodeClass, { definition, manifests })gives a code integration workflows, pauses, syncs, UI surfaces, the generated form and run history.#method.triggerType: "code").A workspace's own integrations.
IntegrationDefinitionVersion.workspaceId, with migration20261019120000_definition_workspace.POST /api/integration-definitionspublishes into the user's workspace.GET /api/integration-definitions/:idserves the editor, and only to the owning workspace.409 ID_TAKEN.CreateIntegrationshow the catalogue plus the user's own integrations. Options for the user's own carryeditable: true.ADR-063 is marked accepted, with the route table updated.
Test plan
nextat this commit. Nothing new fails.Generated by Claude Code
Note
High Risk
Changes core integration resolution, multi-tenant definition ownership, webhook routing, and authorization on approval decisions—mistakes could leak ids, block installs, or mis-route webhooks and queue work.
Overview
Published integration definitions behave like first-class integration types:
integration-types.jsresolvesconfig.typeviaIntegrationProvider, lists them beside code onGET /api/integrations, and routes queue/schedules/webhooks through the host deployment while keeping each type’s own name in URLs and telemetry (getDeploymentName).extendIntegrationClasslets hand-written integrations embed a definition (workflows, syncs, UI, form, run history);#methodcode steps call class methods, anddispatchJobrecords native queued events as runs whenrunEventexists.Workspace-scoped authoring adds
workspaceId(DB migration),POST/GET /api/integration-definitions, catalogue filtering, andID_TAKEN/ 404 rules so tenants cannot probe foreign ids. Admin publish paths move to/admin/integrations/versions; shared routes (runs, syncs, upgrade, approval decisions) return empty/safe answers for code integrations too. Approvers named on a run may decide without owning the install; others get 404. Provider hosts gain dynamic webhook routes and multi-type trigger ticks with continuation cursors.Reviewed by Cursor Bugbot for commit 5d9f1be. Bugbot is set up for automated code reviews on this repo. Configure here.
📦 Published PR as canary version:
2.0.0--canary.696.5d9f1be.0✨ Test out this PR locally via:
npm install @friggframework/admin-scripts@2.0.0--canary.696.5d9f1be.0 npm install @friggframework/core@2.0.0--canary.696.5d9f1be.0 npm install @friggframework/devtools@2.0.0--canary.696.5d9f1be.0 npm install @friggframework/eslint-config@2.0.0--canary.696.5d9f1be.0 npm install @friggframework/prettier-config@2.0.0--canary.696.5d9f1be.0 npm install @friggframework/schemas@2.0.0--canary.696.5d9f1be.0 npm install @friggframework/serverless-plugin@2.0.0--canary.696.5d9f1be.0 npm install @friggframework/test@2.0.0--canary.696.5d9f1be.0 npm install @friggframework/ui@2.0.0--canary.696.5d9f1be.0 # or yarn add @friggframework/admin-scripts@2.0.0--canary.696.5d9f1be.0 yarn add @friggframework/core@2.0.0--canary.696.5d9f1be.0 yarn add @friggframework/devtools@2.0.0--canary.696.5d9f1be.0 yarn add @friggframework/eslint-config@2.0.0--canary.696.5d9f1be.0 yarn add @friggframework/prettier-config@2.0.0--canary.696.5d9f1be.0 yarn add @friggframework/schemas@2.0.0--canary.696.5d9f1be.0 yarn add @friggframework/serverless-plugin@2.0.0--canary.696.5d9f1be.0 yarn add @friggframework/test@2.0.0--canary.696.5d9f1be.0 yarn add @friggframework/ui@2.0.0--canary.696.5d9f1be.0