Skip to content

ADR-057: Canonical Schema (optional field/object mapping extension) - #676

Merged
d-klotz merged 3 commits into
nextfrom
adr/canonical-schema
Oct 19, 2026
Merged

d-klotz merged 3 commits into
nextfrom
adr/canonical-schema

Conversation

@seanspeaks

Copy link
Copy Markdown
Contributor

Adds docs/architecture-decisions/057-canonical-schema.md (Proposed).

  • New 4th extension type (ADR-015): Canonical Schema. It is data-only and registered on appDefinition.canonicalSchemas.
  • API modules register mappings (extensions.mapping, ADR-019). Mappings are declarative, conditional and one-to-many.
  • Adopters choose an off-the-shelf domain model or bring their own schema/overlay.
  • Per-field merge order: workspace > adopter overlay > module mapping > schema defaults, with the winning layer recorded as provenance.
  • Schema version ranges (e.g. business@^2) are checked by Test Bench.
  • Optional flows on ADR-020 capabilities. Direction, copy and fit checks are derived from them.
  • Builds on ADR-044/045/046 (docs(adr): 044-047 — sync engine revitalization, capability traits, canonical models, orchestrator/worker #648) and does not replace them. See "Relationship to ADR-046".
  • Open questions are listed at the end of the doc.

@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.

@netlify

netlify Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for friggframework-org canceled.

Name Link
🔨 Latest commit a12b242
🔍 Latest deploy log https://app.netlify.com/projects/friggframework-org/deploys/6acf8705533e9b3448694566

extensions: {
webhooks: require('./extensions/webhooks'),
mapping: {
schemas: { business: require('./mappings/business.yaml') }, // invoice Line[] → InvoiceLine + Product (one-to-many)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The example calls require('./mappings/business.yaml'). Node's require cannot load .yaml files without a custom loader, so it throws a SyntaxError at module load. The ADR defines no loader or build step for this, so a module copying the pattern would fail to import.

Fix by using an explicit loader, for example:

schemas: { business: loadYaml(path.join(__dirname, 'mappings/business.yaml')) }

Alternatively, state in the ADR that the framework resolves mapping file paths.

Suggested change
schemas: { business: require('./mappings/business.yaml') }, // invoice Line[] → InvoiceLine + Product (one-to-many)
schemas: { business: loadYaml(path.join(__dirname, 'mappings/business.yaml')) }, // invoice Line[] → InvoiceLine + Product (one-to-many)

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 3b02d40: the example now loads the mapping with an explicit loadYaml(path.join(__dirname, 'mappings/business.yaml')) instead of require on a .yaml file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Follow-up in a12b242: the example is now a plain JS module. mappings/business.js exports an object and is loaded with a normal require. YAML and the loadYaml helper are gone. Schemas and mappings are now plain CommonJS objects validated against new JSON Schemas in @friggframework/schemas.

Comment on lines +126 to +139
Invoice: { filter: { source: 'hubspot.deal', where: { dealstage: '{{config.invoiceStage}}' } } },
},
},
capabilities: {
'crm.customer.sync.bidir': {
surface: 'sync',
flows: [
{ from: 'hubspot', to: 'quickbooks', object: 'Customer', trigger: 'webhook' },
{ from: 'quickbooks', to: 'hubspot', object: 'Customer', trigger: 'webhook' },
],
},
'billing.invoice.create': {
surface: 'sync',
flows: [{ from: 'hubspot', to: 'quickbooks', object: 'Invoice', source: 'Deal', conflict: 'createOnly' }],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The worked example is internally inconsistent and would fail the Test Bench check this ADR describes.

  • The integration declares Invoice: { filter: { source: 'hubspot.deal', ... } } and a billing.invoice.create flow with object: 'Invoice', source: 'Deal'.
  • The HubSpot mapping (lines 80-93) only maps contact and company. There is no deal → Invoice mapping.
  • The QuickBooks mapping does not show an Invoice mapping either.
  • Line 170 says Test Bench must fail when a rule or flow references an object absent from the resolved mappings. As written, the example would fail.
  • source is spelled hubspot.deal in the rule and Deal in the flow.
  • conflict: 'createOnly' uses an ADR-045 trait name as a conflict strategy. Line 179 says such traits should not be redeclared, and line 124 uses lastWriteWins for conflicts.

Fix: add a deal → Invoice mapping to the HubSpot example, use one consistent source spelling, and express createOnly through traits rather than conflict.

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@sonarqubecloud

Copy link
Copy Markdown

@d-klotz
d-klotz merged commit fafb8eb into next Oct 19, 2026
12 of 13 checks passed
@d-klotz
d-klotz deleted the adr/canonical-schema branch October 19, 2026 15:45
@seanspeaks

Copy link
Copy Markdown
Contributor Author

🚀 PR was released in v2.0.0-next.119 🚀

@seanspeaks seanspeaks added the prerelease This change is available in a prerelease. label Oct 20, 2026
@seanspeaks

Copy link
Copy Markdown
Contributor Author

🚀 PR was released in v2.0.0-next.119 🚀

1 similar comment
@seanspeaks

Copy link
Copy Markdown
Contributor Author

🚀 PR was released in v2.0.0-next.119 🚀

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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants