Skip to content

feat(core): app-level entity registry (ADR-059) - #679

Open
seanspeaks wants to merge 2 commits into
feat/management-api-v2from
claude/internal-mcp-03-entity-registry
Open

seanspeaks wants to merge 2 commits into
feat/management-api-v2from
claude/internal-mcp-03-entity-registry

Conversation

@seanspeaks

@seanspeaks seanspeaks commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This implements ADR-059 (see #677). An app can now declare entity types in an optional appDefinition.entities, keyed by entity type name. That makes a type listable and authorizable without first declaring an integration that uses it. Declared types are merged with the types inferred from integration Definitions, and $infer: false turns inference off.

Based on #671 (feat/management-api-v2). It does not depend on #678.

What it does

  • modules/entity-type-registry.js. getEntityTypeRegistry(appDefinition) is pure and returns a frozen Map of name -> { definition, sources: [{kind, name}], settings }. It throws EntityRegistryError, which extends BaseError with code ENTITY_REGISTRY_INVALID.
    • Key rule. A key must equal moduleName and, when one is defined, getName(). Matching is case-sensitive.
    • Conflicts. The rule is strict once entities is present, even when it is {}. Without an entities key, two integrations that each bind a different object with the same moduleName keep the first one and log frigg.modules.entity_registry.duplicate_definition, so existing apps behave exactly as they do today.
    • $infer: false. Any bound module that is not registered fails at load, and the error names the integrations that bind it.
  • Built once. loadAppDefinition builds the registry and fails at load if it is invalid. The v1 integration router (/api/authorize), health, the webhook loader, the queue-worker entry and the v2 dependencies all read that one registry instead of re-inferring from integration classes.
  • v2 entity-types routes. List and get return types that are only in the registry, with a usedBy field. Get looks up by registry key. This fixes a type that list returned but get answered with 404, and a sort crash for definitions that have only getName().
  • Queue workers and createFriggCommands. They take injected moduleDefinitions. When none is passed, they fall back explicitly to the integration's own modules.
  • Schemas and docs. entities is added to app-definition.schema.json, and usedBy to the entity-type API schema and the OpenAPI component. types/modules/index.d.ts is new, and core-concepts.md and api-reference.md are updated.

Breaking (pre-release line)

  • getModulesDefinitionFromIntegrationClasses is removed from @friggframework/core and integrations/index.js. No caller in this repo uses it. An external adopter importing it should switch to getEntityTypeRegistry.

Not in this PR

  • Module credentialFields encryption. The loader for it has a broken require path, so those fields have never been encrypted. This PR deliberately leaves that alone and adds a comment pointing to the follow-up.
  • The fix is in the stacked PR, claude/internal-mcp-03b-module-credential-encryption, because turning encryption on can break reads of existing plaintext rows.
  • Registry display/scope settings are stored, but nothing reads them yet (ADR-056 work).

Test plan

  • Full packages/core run with the no-op global setup, because Mongo and Postgres binaries can't run here: 65 tests fail, the same failing set by name as base. 2894 pass.
  • packages/schemas: 166 pass.
  • ESLint: 0 errors.
  • Two independent reviews (spec/test and clean code). After the review round, 29 of 29 mutations were killed; they covered the key rule, the lenient-vs-strict conflict rule, lookup by key, $infer, and each of the 8 call sites.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6


Generated by Claude Code


Note

Medium Risk
Touches authorization, module factory wiring, and app load behavior; misconfigured entities now stops the app at startup, but apps without entities keep prior inference behavior.

Overview
Adds an app-level entity registry (ADR-059) via optional appDefinition.entities, so entity types can be offered and authorized even when no integration binds them. loadAppDefinition() builds a frozen entityTypeRegistry and moduleDefinitions once at startup and fails fast on invalid registries (EntityRegistryError); strict rules apply when entities is present, with $infer: false requiring every integration-bound module to be registered.

Wiring: v1 /api/authorize, v2 authorize/entity-types, health, webhooks, queue workers, and createFriggCommands / createIntegrationCommands now consume the loaded registry (with an explicit fallback to the integration’s own modules when moduleDefinitions is omitted). getModulesDefinitionFromIntegrationClasses remains as a deprecated registry wrapper.

API: GET /api/v2/entities/types and per-type GET include usedBy (integration names; empty for registry-only types). OpenAPI, JSON schemas, docs, and new TypeScript types for the registry are updated.

Reviewed by Cursor Bugbot for commit a2bcc2f. Bugbot is set up for automated code reviews on this repo. Configure here.

📦 Published PR as canary version: 2.0.0--canary.679.a2bcc2f.0

✨ Test out this PR locally via:

npm install @friggframework/admin-scripts@2.0.0--canary.679.a2bcc2f.0
npm install @friggframework/core@2.0.0--canary.679.a2bcc2f.0
npm install @friggframework/devtools@2.0.0--canary.679.a2bcc2f.0
npm install @friggframework/eslint-config@2.0.0--canary.679.a2bcc2f.0
npm install @friggframework/prettier-config@2.0.0--canary.679.a2bcc2f.0
npm install @friggframework/schemas@2.0.0--canary.679.a2bcc2f.0
npm install @friggframework/serverless-plugin@2.0.0--canary.679.a2bcc2f.0
npm install @friggframework/test@2.0.0--canary.679.a2bcc2f.0
npm install @friggframework/ui@2.0.0--canary.679.a2bcc2f.0
# or 
yarn add @friggframework/admin-scripts@2.0.0--canary.679.a2bcc2f.0
yarn add @friggframework/core@2.0.0--canary.679.a2bcc2f.0
yarn add @friggframework/devtools@2.0.0--canary.679.a2bcc2f.0
yarn add @friggframework/eslint-config@2.0.0--canary.679.a2bcc2f.0
yarn add @friggframework/prettier-config@2.0.0--canary.679.a2bcc2f.0
yarn add @friggframework/schemas@2.0.0--canary.679.a2bcc2f.0
yarn add @friggframework/serverless-plugin@2.0.0--canary.679.a2bcc2f.0
yarn add @friggframework/test@2.0.0--canary.679.a2bcc2f.0
yarn add @friggframework/ui@2.0.0--canary.679.a2bcc2f.0

Add getEntityTypeRegistry(appDefinition), a pure function that merges the
optional appDefinition.entities map with the modules bound by integrations.
Inference stays on unless entities.$infer is false.

With no entities key nothing new can fail: two different definitions under
one moduleName keep the first and log a warning. With entities present
(even {}) the rules are strict: a key must equal moduleName and getName(),
one name is one definition (same object, or same package@version), and
$infer: false requires every bound module to be registered.

loadAppDefinition builds the registry once, at load, so errors fail fast,
and returns entityTypeRegistry and moduleDefinitions. The v1 router, v2
dependencies, health, the webhook loader and the worker entry read those;
createIntegrationCommands and createQueueWorker take moduleDefinitions and
fall back to the integration's own modules when none is given.
getModulesDefinitionFromIntegrationClasses is removed.

GET /api/v2/entities/types[/:type] lists registry-only types, looks them up
by registry key, and each description gains usedBy. app-definition.schema.json
gains entities; types/modules/index.d.ts adds the registry types.

This commit does not change which fields are encrypted:
loadModuleEncryptionSchemas keeps its existing (broken) require path, with a
comment pointing to claude/internal-mcp-03b-module-credential-encryption.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Code review skipped — your organization has no extra usage available to pay for this review.

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.

@seanspeaks seanspeaks added release Create a release when this pr is merged prerelease This change is available in a prerelease. labels Oct 15, 2026 — with Claude
…cated registry wrapper

#679 removed the export. ADR-059 §2 keeps it as a deprecated wrapper so
adopters that import it keep working. It now returns the entity type
registry's definitions (optionally including app.entities), logs a
deprecation warning once per process, and is exported again from
@friggframework/core and integrations/. Typed in types/modules.

The "registered": false list flag of ADR-059 §4 is not built; the
reference doc says so (the ADR is amended on the ADR branch).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
seanspeaks pushed a commit that referenced this pull request Oct 16, 2026
…wrapper takes entities

The first implementation lists, reads and deletes an entity of an
unregistered type and refuses new ones, but does not emit
"registered": false yet. Says so, and how such an entity answers today.
getModulesDefinitionFromIntegrationClasses is kept as a deprecated wrapper
(#679 now restores it) with an optional entities argument.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
@sonarqubecloud

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a2bcc2f. Configure here.

return definition;
}

/** Lists the entity types (API modules) this app can authorize. */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Authorize ignores registry type keys

Medium Severity

ListEntityTypes and GetEntityType now identify a type by its registry key, which is getName() when moduleName is missing. findDefinition, GetAuthorizationRequirements, and ProcessAuthorizationCallback still match only definition.moduleName. A type that list and get return is then 404 ENTITY_TYPE_NOT_FOUND on requirements and authorize, so a listed type cannot be connected.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a2bcc2f. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

prerelease This change is available in a prerelease. release Create a release when this pr is merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants