Skip to content

feat(quickbooks): add core webhook triggers - #6245

Open
BillLeoutsakosvl346 wants to merge 32 commits into
stagingfrom
feat/quickbooks-08-webhook-core
Open

BillLeoutsakosvl346 wants to merge 32 commits into
stagingfrom
feat/quickbooks-08-webhook-core

Conversation

@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor

Summary

  • add authenticated app-level QuickBooks webhook ingress with durable bounded processing
  • route events to deployed workflows for the matching QuickBooks company
  • add seven representative QuickBooks trigger options to the existing block
  • document the fixed callback and verifier-token setup using the established app-level trigger pattern

Validation

  • QuickBooks webhook route/provider/worker/target/trigger tests (15 passing)
  • Sim type-check
  • API, icon, client-boundary, and tool-registry-boundary validation
  • format, lint, documentation generation, and git diff checks
  • independent max-reasoning trigger review: no actionable findings

Live Intuit delivery remains dependent on configuring the public callback and QUICKBOOKS_WEBHOOK_VERIFIER_TOKEN; signed raw-body fixtures cover ingress locally.

Bill Leoutsakos and others added 24 commits July 30, 2026 11:58
…tegration

# Conflicts:
#	apps/sim/tools/generated/tool-ids.ts
#	apps/sim/tools/generated/tool-metadata.ts
#	apps/sim/tools/generated/tool-outputs.ts
…tegration

# Conflicts:
#	apps/sim/tools/index.ts
…tegration

# Conflicts:
#	apps/sim/tools/generated/tool-outputs.ts
* feat(quickbooks): add master data and CRUD tools

* feat(quickbooks): expose compact master data operations

* docs(quickbooks): document master data actions

* fix(quickbooks): harden master data outputs

* fix(redaction): preserve workflow state tokens

* fix(quickbooks): address integration review findings

* fix(quickbooks): align item updates and generated docs

* fix(quickbooks): sanitize customer tax identifiers

* fix(quickbooks): preserve read-write compatibility

* fix(quickbooks): use action-first operation labels

* test(quickbooks): align operation label expectations

* fix(quickbooks): align catalog operation labels

* fix(redaction): cover namespaced secret fields

* fix(quickbooks): expose master data pagination metadata

* fix(quickbooks): omit null optional values

* fix(quickbooks): validate master data inputs

* chore(tools): sync master data metadata

* fix(redaction): anchor workflow token allowlist

* feat(quickbooks): add sales and accounts receivable (#6130)

* feat(quickbooks): add bounded sales transaction reads

* feat(quickbooks): add sales and receivables mutations

* feat(quickbooks): expose sales operations in the block

* fix(quickbooks): address independent sales review

* fix(quickbooks): address final integration review

* fix(quickbooks): clarify master data output metadata

* fix(quickbooks): mark sales pagination outputs optional

* fix(quickbooks): validate calculated sales amounts

* fix(quickbooks): generate sales arrays correctly

* fix(quickbooks): align sales metadata conditions

* chore(tools): sync sales metadata

* feat(quickbooks): add purchasing and payables (#6159)

* feat(quickbooks): add safe purchasing and payables tools

* feat(quickbooks): expose purchasing and payables operations

* docs(quickbooks): document purchasing and payables tools

* fix(quickbooks): require current purchase payment type

* fix(quickbooks): allow rounded purchasing line totals

* fix(quickbooks): generate purchasing arrays correctly

* fix(quickbooks): validate bill payment accounts

* fix(quickbooks): validate bill allocations before account lookup

* chore(tools): sync purchasing metadata

* fix(quickbooks): sanitize bill payment faults

* feat(quickbooks): add general accounting operations (#6185)

* feat(quickbooks): add accounting transaction tools

* feat(quickbooks): expose accounting operations

* docs(quickbooks): generate accounting catalog

* fix(quickbooks): preserve accounting amount precision

* fix(quickbooks): balance journal entries in exact cents

* fix(quickbooks): include account in deposit updates

* chore(quickbooks): sync accounting catalog

* feat(quickbooks): add observable PO-to-bill linking (#6194)

* feat(quickbooks): link bills to purchase order lines

* docs(quickbooks): document observable bill linking

* fix(quickbooks): document purchase order link identifiers

* fix(quickbooks): keep shared line example valid

* chore(quickbooks): sync bill linking catalog

* feat(quickbooks): add accountant-focused financial reports (#6197)

* feat(quickbooks): add verified financial report contracts

* feat(quickbooks): expose reports in block and catalog

* test(quickbooks): cover null report filters

* fix(quickbooks): expose report header time

* chore(quickbooks): sync reports catalog

* feat(quickbooks): add documents and attachments (#6200)

* feat(quickbooks): add document and attachment tools

* feat(quickbooks): add bounded document file routes

* feat(quickbooks): expose document workflows

* fix(quickbooks): enforce attachment upload bounds

* fix(quickbooks): tighten document handling

* fix(quickbooks): align file response limits

* test(quickbooks): cover missing PDF content type

* test(quickbooks): cover attachment MIME fallback

* fix(quickbooks): redact attachment access URLs

* fix(quickbooks): store downloaded documents safely

* fix(quickbooks): stop cancelled attachment downloads

* fix(quickbooks): correct document schemas and upload bytes

* chore(quickbooks): sync document catalog

* feat(quickbooks): add accountant filters (#6208)

* feat(quickbooks): add safe n8n parity tools

* feat(quickbooks): expose accountant parity options

* fix(quickbooks): address parity review findings

* fix(quickbooks): require recipient for payment email

* chore(quickbooks): sync parity catalog

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Mac.localdomain>
Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Mac.localdomain>
Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Mac.localdomain>
Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
Co-authored-by: Bill Leoutsakos <billleoutsakos@Mac.localdomain>

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Mac.localdomain>
Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
Co-authored-by: Bill Leoutsakos <billleoutsakos@Mac.localdomain>

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
Co-authored-by: Bill Leoutsakos <billleoutsakos@Mac.localdomain>
…rink shared-logic surface

Validation of the integration against Intuit's published attribute tables
(static.developer.intuit.com/JSONObjects/*, the payload behind the docs SPA)
surfaced defects that write wrong data to customers' books, plus shared-module
changes broader than this integration needs.

Data integrity:
- update_customer_payment now reads the payment and merges allocations. QuickBooks
  applies payment lines ALL-or-NONE, so sending a subset silently unapplied every
  omitted invoice. Destructive replacement stays reachable via a user-only
  unapplyOmittedInvoices flag. Also validates the merged total, not just the
  caller's allocations.
- create_item required ExpenseAccountRef (required for Service and NonInventory)
  as optional while mandating IncomeAccountRef (not required for NonInventory).
- create_employee required DisplayName, which QuickBooks derives and treats as
  read-only under Payroll, while leaving the real at-least-one GivenName/FamilyName
  rule unchecked. Both tools threw a raw TypeError on their schema-minimal call.
- create_purchase sent PaymentRefNum, absent from the Purchase entity; QuickBooks
  discards unknown fields silently, so check numbers vanished. Now DocNumber.
- A Fault nested in QueryResponse was reported as an empty result set. Detected in
  parseQuickBooksJson so every response path is covered.
- assertQuickBooksSparseUpdate never checked sparse despite its name.
- Bill header-level LinkedTxn omitted the required TxnLineId and duplicated the
  line-level links; DescriptionOnly lines omitted DescriptionLineDetail.
- Line amounts were positive-only, making discounts, returns and credits
  unrepresentable, and bare Number() silently coerced true and [5] onto the wire.
- Phantom report parameters removed; aging method and period split so each report
  only offers the control it accepts.

Safety and boundaries:
- confirmVoid and confirmPosting moved to user-only, so an agent can no longer
  supply its own approval for voids and journal postings.
- Split the runtime-free helpers out of tools/quickbooks/utils.ts. The block was
  the only one in the repo dragging error-extractors, client.ts,
  microsoft_excel/utils and stream-limits into the client bundle.
- Attachment routes: 20 MB cap per Intuit's limit, outbound timeouts, extension
  preserved through filename truncation, Unicode filenames, wider MIME acceptance.
- The two download routes merged into one; extracted storeToolOutputFile.

Shared logic reduced to what this integration needs:
- redaction.ts back to a bypass-set entry for SyncToken; every origin/staging
  assertion retained.
- refreshOAuthToken read the body before the ok check, so a token response over
  64 KiB became a spurious failure for every provider. Provider error text is
  restored to logs, redacted and truncated, and kept out of the caller message.
- maxResponseBytes clamped to the global ceiling.
- generate-docs is deterministic again; its output no longer depends on what is
  already on disk.

The generic redaction engine, the serializer singleton-canonical fix and the
visibility-based docs filter are deferred to their own PRs.

Sparse-update semantics for ten write tools remain unverified against a live
sandbox; apps/sim/scripts/quickbooks-sparse-probe.ts settles it.
@vercel

vercel Bot commented Aug 4, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Oct 3, 2026 3:43am UTC

Request Review

@cursor

cursor Bot commented Aug 4, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches webhook ingress, async job routing, and OAuth credential binding for financial data events; changes are well-tested but misconfiguration of the verifier token or realm routing could drop or misroute deliveries.

Overview
Adds QuickBooks Online webhook triggers so workflows can start from Intuit CloudEvents (29 entity-specific triggers on the QuickBooks block, with multi-select Event Types where applicable).

Ingress: New fixed callback POST /api/webhooks/quickbooks verifies intuit-signature (HMAC over the raw body via QUICKBOOKS_WEBHOOK_VERIFIER_TOKEN), validates batched envelopes (up to 1,000 events), then durably enqueues a quickbooks-webhook-ingress job before responding. The worker routes each event by company realm ID (intuitaccountid) to deployed webhooks and dispatches through the shared processor (provider ingress + queued execution, same pattern as TikTok).

Deploy & lifecycle: Trigger deploy resolves the connected OAuth company to a routing key (realm ID); credential removal deactivates bound QuickBooks webhook rows alongside Slack.

Copilot / VFS: Multi-select dropdown subblocks are validated and serialized so trigger eventTypes_* arrays are accepted in workflow edits.

Docs and integration metadata list trigger configuration outputs; .env.example documents the verifier token.

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

@greptile-apps

greptile-apps Bot commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds authenticated QuickBooks webhook ingestion and routes company-scoped events through durable workflow dispatch.

  • Verifies Intuit signatures and validates bounded webhook envelopes before durable acceptance.
  • Adds QuickBooks trigger definitions, registry entries, deployment support, and generated integration documentation.
  • Processes all events and targets in an accepted delivery before retrying failures.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported batch-termination issue is resolved because target and lookup failures are isolated until all later events have been processed.

Important Files Changed

Filename Overview
apps/sim/app/api/webhooks/quickbooks/route.ts Adds bounded, signature-authenticated app-level webhook ingress that acknowledges deliveries only after durable enqueue.
apps/sim/background/quickbooks-webhook-ingress.ts Routes every accepted event by QuickBooks company, isolates target failures, and retries after processing the complete batch.
apps/sim/lib/webhooks/providers/quickbooks.ts Adds QuickBooks signature verification, event matching, normalization, and event-ID extraction for idempotency.
apps/sim/lib/webhooks/deploy.ts Integrates QuickBooks with the app-level trigger deployment lifecycle.
apps/sim/triggers/quickbooks/index.ts Exports the new QuickBooks event trigger catalog for registry consumption.
apps/sim/blocks/blocks/quickbooks.ts Enables trigger mode and exposes the registered QuickBooks trigger options on the existing block.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  QB[QuickBooks] --> Route[Signed webhook route]
  Route --> Validate[Verify signature and validate bounded envelope]
  Validate --> Ingress[Durable ingress job]
  Ingress --> Lookup[Find deployed targets by company realm]
  Lookup --> Dispatch[Dispatch each event to matching workflows]
  Dispatch --> Idempotency[Webhook execution idempotency]
  Idempotency --> Execute[Execute workflow]
Loading

Reviews (4): Last reviewed commit: "refactor(quickbooks): align webhook trig..." | Re-trigger Greptile

Comment thread apps/sim/background/quickbooks-webhook-ingress.ts Outdated
Comment thread apps/sim/background/quickbooks-webhook-ingress.ts
Comment thread apps/sim/app/api/webhooks/quickbooks/route.ts Outdated
Comment thread apps/sim/blocks/blocks/quickbooks.ts Outdated
@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@greptile

@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@greptile

* feat(quickbooks): complete webhook trigger matrix

* fix(quickbooks): document webhook event selectors

* fix(quickbooks): clarify webhook setup

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Mac.localdomain>
@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

@greptile

@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 afb7009. Configure here.

Comment thread apps/sim/lib/copilot/tools/server/workflow/edit-workflow/validation.ts Outdated
@BillLeoutsakosvl346

Copy link
Copy Markdown
Contributor Author

QuickBooks trigger file classification

The implementation has been kept mostly within Sim’s normal trigger patterns. I’ve classified the files as:

  • Standard: Expected files for an ordinary trigger-enabled integration.
  • Semi-standard: Established patterns used by integrations with app-level webhooks, provider authentication, or deployment routing. Slack and TikTok have comparable pieces.
  • Unique: QuickBooks-specific infrastructure that cannot currently be replaced by an existing shared Sim primitive without weakening delivery reliability.

Standard

These are the normal files expected when adding triggers to an existing integration.

Trigger definitions

  • apps/sim/triggers/quickbooks/account_events.ts — created
  • apps/sim/triggers/quickbooks/bill_events.ts — created
  • apps/sim/triggers/quickbooks/bill_payment_events.ts — created
  • apps/sim/triggers/quickbooks/budget_events.ts — created
  • apps/sim/triggers/quickbooks/class_events.ts — created
  • apps/sim/triggers/quickbooks/credit_memo_events.ts — created
  • apps/sim/triggers/quickbooks/currency_events.ts — created
  • apps/sim/triggers/quickbooks/customer_events.ts — created
  • apps/sim/triggers/quickbooks/department_events.ts — created
  • apps/sim/triggers/quickbooks/deposit_events.ts — created
  • apps/sim/triggers/quickbooks/employee_events.ts — created
  • apps/sim/triggers/quickbooks/estimate_events.ts — created
  • apps/sim/triggers/quickbooks/invoice_events.ts — created
  • apps/sim/triggers/quickbooks/item_events.ts — created
  • apps/sim/triggers/quickbooks/journal_code_events.ts — created
  • apps/sim/triggers/quickbooks/journal_entry_events.ts — created
  • apps/sim/triggers/quickbooks/payment_events.ts — created
  • apps/sim/triggers/quickbooks/payment_method_events.ts — created
  • apps/sim/triggers/quickbooks/preferences_updated.ts — created
  • apps/sim/triggers/quickbooks/purchase_events.ts — created
  • apps/sim/triggers/quickbooks/purchase_order_events.ts — created
  • apps/sim/triggers/quickbooks/refund_receipt_events.ts — created
  • apps/sim/triggers/quickbooks/sales_receipt_events.ts — created
  • apps/sim/triggers/quickbooks/tax_agency_events.ts — created
  • apps/sim/triggers/quickbooks/term_events.ts — created
  • apps/sim/triggers/quickbooks/time_activity_events.ts — created
  • apps/sim/triggers/quickbooks/transfer_events.ts — created
  • apps/sim/triggers/quickbooks/vendor_credit_events.ts — created
  • apps/sim/triggers/quickbooks/vendor_events.ts — created
  • apps/sim/triggers/quickbooks/index.ts — created
  • apps/sim/triggers/quickbooks/utils.ts — created

These contain the conventional trigger definitions, the fixed Intuit entity/action matrix, trigger matching, input formatting, and shared QuickBooks trigger options.

Block and registries

  • apps/sim/blocks/blocks/quickbooks.ts — modified
  • apps/sim/triggers/registry.ts — modified
  • apps/sim/lib/integrations/integrations.json — modified
  • apps/docs/content/docs/en/integrations/quickbooks.mdx — modified

These expose the 29 grouped trigger choices through the existing QuickBooks block, register them, and update the generated integration catalogue and documentation.

Standard trigger tests

  • apps/sim/triggers/quickbooks/quickbooks.test.ts — created

This provides table-driven behavioral coverage for all 29 choices and 101 supported entity/action combinations, including matching, formatting, event-type isolation, and Intuit void → Sim voided normalization.

Semi-standard

These files are needed because QuickBooks uses one app-level webhook endpoint rather than giving every workflow its own webhook URL.

Comparable patterns exist in integrations such as TikTok and Slack: they authenticate a shared provider callback, route an event to deployed workflows using provider-owned identity information, and then use Sim’s shared webhook dispatcher.

App-level webhook callback

  • apps/sim/app/api/webhooks/quickbooks/route.ts — created
  • apps/sim/lib/api/contracts/webhooks.ts — modified
  • scripts/check-api-validation-contracts.ts — modified

The route receives Intuit’s app-level callback, reads a bounded raw body, verifies the intuit-signature, validates the event batch, and admits it for processing. The other changes register and validate that API route.

This belongs in the trigger PR because Intuit cannot deliver events to the QuickBooks triggers without a public authenticated callback.

Test:

  • apps/sim/app/api/webhooks/quickbooks/route.test.ts — created

Webhook provider handling

  • apps/sim/lib/webhooks/providers/quickbooks.ts — created
  • apps/sim/lib/webhooks/providers/registry.ts — modified

The provider handles QuickBooks-specific signature authentication, entity/action matching, output formatting, and event-ID idempotency while still using Sim’s standard dispatcher.

TikTok and Slack have comparable provider handlers because their webhook authentication and payload formats are provider-specific.

This belongs here because these are the minimum adapters required to translate Intuit events into Sim’s standard trigger execution model.

Test:

  • apps/sim/lib/webhooks/providers/quickbooks.test.ts — created

Deployment-time company routing

  • apps/sim/lib/webhooks/deploy.ts — modified
  • apps/sim/lib/credentials/deletion.ts — modified

When a QuickBooks trigger is deployed, Sim stores the connected company’s realmId in the existing standard webhook.routingKey field. Incoming events are then routed only to workflows connected to that company.

Deleting the credential also deactivates its deployed triggers.

TikTok similarly stores an account identity in routingKey, while Slack uses installation/team identity for equivalent app-level routing.

These changes belong in this PR because correct company isolation and credential cleanup are essential parts of deploying the trigger safely.

Tests:

  • apps/sim/lib/webhooks/deploy.test.ts — modified
  • apps/sim/lib/credentials/__tests__/webhook-deactivation.test.ts — created

Provider configuration

  • apps/sim/.env.example — modified
  • apps/sim/lib/core/config/env.ts — modified

These register the server-only Intuit webhook verifier token used to authenticate callback signatures.

This is comparable to the signing secrets or verifier configuration required by other authenticated webhook providers and is directly required for the QuickBooks endpoint to reject forged deliveries.

Mothership multi-select support

  • apps/sim/lib/copilot/tools/server/workflow/edit-workflow/validation.ts — modified
  • apps/sim/lib/copilot/vfs/serializers.ts — modified

QuickBooks trigger actions use Sim’s existing multiSelect dropdown capability. Mothership previously did not serialize that flag or validate array values correctly, so it could not configure actions such as created, updated, deleted, and merged together.

These are small provider-neutral corrections to support an existing block feature—not QuickBooks-specific branches or new abstractions. They belong in this PR because without them most of the trigger matrix was impossible to configure through Mothership.

Tests:

  • apps/sim/lib/copilot/tools/server/workflow/edit-workflow/validation.test.ts — modified
  • apps/sim/lib/copilot/vfs/serializers.test.ts — modified

Unique

There is one unique runtime component: the durable QuickBooks batch-ingress job.

  • apps/sim/background/quickbooks-webhook-ingress.ts — created
  • apps/sim/lib/core/async-jobs/types.ts — modified
  • apps/sim/lib/core/async-jobs/backends/trigger-dev.ts — modified

Test:

  • apps/sim/background/quickbooks-webhook-ingress.test.ts — created

Intuit may send a single webhook request containing multiple events for multiple QuickBooks companies, while requiring the endpoint to acknowledge the delivery within roughly three seconds and recommending asynchronous processing.

The ingress job lets the callback perform only:

authenticate → validate → durably accept → acknowledge

@waleedlatif1
waleedlatif1 force-pushed the feat/quickbooks-integration branch 5 times, most recently from 80b3121 to 2405a19 Compare August 28, 2026 18:12
@waleedlatif1
waleedlatif1 force-pushed the feat/quickbooks-integration branch from 0f23331 to 6249489 Compare September 5, 2026 01:41
Base automatically changed from feat/quickbooks-integration to staging September 5, 2026 01:59
@gitguardian

gitguardian Bot commented Sep 5, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
35640005 Triggered Generic Password 0c3de74 apps/desktop/src/main/browser-import/import-service.test.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Preserve the current credential-scoped QuickBooks implementation already incorporated in staging: app-key ingress, encrypted verifier tokens, app-and-company routing, bounded durable processing, deployed target metadata, and the complete trigger matrix.

Resolve inherited foundation conflicts against their current owners and retain deliberate moves and deletions. Remove superseded global ingress, per-entity trigger duplicates, HTTP tool adapters, and legacy VFS files rather than reviving older architecture. Canonical outputs retain the staging versions because their source inputs are unchanged. Validation was limited to source and Git inspection as requested.

This branch was successfully deployed

1 active deployment
Preview — 1fd77ceb Deployed Oct 3, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants