Skip to content

feat(plugins): in-repo country plugin system#147

Draft
carvalab wants to merge 1 commit into
FreeOpenSourcePOS:mainfrom
carvalab:feature-plugin-system
Draft

feat(plugins): in-repo country plugin system#147
carvalab wants to merge 1 commit into
FreeOpenSourcePOS:mainfrom
carvalab:feature-plugin-system

Conversation

@carvalab

@carvalab carvalab commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Stage 1 of the plugin proposal: a country plugin system that lives in the repo, with a Settings → Integraciones UI to install, activate, and configure each capability.

What's in this PR:

  • Manifests + runtimes for AR (IVA, ARCA, Mercado Pago, PedidosYa, Rappi), IN (GST, GST invoice, UPI, Swiggy, Zomato), TH (fixed 7% VAT), and a global.default package for countries without one. Each country package owns its own Zod config schema and the in-process tax math; payment/delivery/fiscal stay declared-only (hosted) for the Stage 3 broker. Some of that are examples, I will delete before the final PR.
  • Shared contract in shared/plugin-api.ts that both sides import. Browser-safe Zod schemas cover the manifest, runtime bundle, request/result envelopes, the catalog response, installation, feature, connector, and payment-method wire shapes, plus a typed PluginRegistry and definePluginRuntimeBundle(). The shared installationFeatureSchema now allows installationId: null so unactivated features don't carry an empty string.
  • Activation-aware tax dispatch. Country packages are auto-installed and activated at first-run setup and at every startup if the store has an owner + country. The dispatcher activates in-process tax only when the package is installed and the tax feature is active. AR/IN win over global.default; the global default wins for everything else.
  • Per-capability activation gate. Payment/delivery/fiscal features require an authorized + broker-verified connector. Tax doesn't. Hosted capabilities get connector_verification_hosted instead of a local "verified" stamp.
  • Connector handler dispatch. main/plugins/connector-handlers.ts is the only file that knows provider names. The route dispatches to validate/summarize by capability.provider. Unknown providers get 400, never a silent generic store.
  • Manifest-driven UI. Capability display labels/descriptions, permission grid, runtime, scope, and the per-capability configuration form (text / select / number / boolean fields) all come from the manifest. Adding a new package is a manifest + registry edit, not a frontend change.
  • SQLite migration v34 adds the plugin state tables. Migrations are non-destructive (CREATE IF NOT EXISTS) and the prior plugin_packages / plugin_outbox speculative tables were dropped because nothing reads them.
  • Tests. npm test is green; test:plugin-contracts, test:plugin-routes, and test:plugin-default are wired into the test chain. Semver range parser now covers || and prerelease forms.
  • Docs. docs/plugin-architecture.md, docs/plugin-authoring.md, docs/plugin-proposal.md, docs/plugin-references.md.

Visual

Settings → Integraciones on a fresh AR store (/settings/?tab=plugins):

  • Catalog grid: 2 packages (country.ar, global.default), 5 AR capabilities, manifest permissions
  • Detail dialog: capability list with localized labels + descriptions, per-capability status, per-capability configuration form
  • After install: Desactivar/Desinstalar actions, persisted to plugin_installations + plugin_features

Self-review notes

  • Hardcoded capability IDs, labels, and provider config state are gone from the frontend. Everything surfaces from the manifest.
  • Frontend Zod-validates the catalog/install/connector/payment-method responses. No unchecked as casts.
  • Reinstall after uninstalled works (deletes child features/connector rows + resets status).
  • The connector config is hard-capped at 32 KiB; secret-like keys (camelCase normalized) are rejected at the route boundary.
  • Fiscal hosted capabilities share the same activation gate as payment/delivery now.

Test plan (asking for help)

This PR is draft for proposal. I want to merge after two things:

  1. Manual smoke test on each in-repo country package (AR, IN, TH, global.default):
    • First-run setup activates the right tax pack
    • Order with the country's tax math returns the expected line items
    • Settings → Integraciones shows the package; install/activate round-trips
    • Hosted payment/delivery/fiscal per-feature gate rejects unauthorized activation
  2. The new test:plugin-default is now wired into test:cors so it runs in CI; please check the test pipeline on your end

@itsbkm @khaira777 — could you run through the country packages on a fresh DB please?

then open Settings → Integraciones on the AR/IN/TH seeded stores. And also no broke taxes.

Out of scope (Stage 2 / Stage 3)

  • Signed external catalog API (docs/plugin-proposal.md)
  • Hosted broker / provider API calls from the POS
  • utility_process ABI execution
  • Per-capability permission grants (currently install-wide)

Refs: docs/plugin-proposal.md, docs/plugin-authoring.md, docs/plugin-architecture.md

@carvalab
carvalab requested review from itsbkm and khaira777 July 24, 2026 01:55
@carvalab
carvalab force-pushed the feature-plugin-system branch 2 times, most recently from 8c395eb to 65a0898 Compare July 24, 2026 02:31
Stage 1 of the proposal at docs/plugin-proposal.md: manifests + runtimes
for AR, IN, TH, and a global default tax engine, with Settings →
Integraciones managing install/activation/connector config through the
new /api/plugins/* routes.

Shared contract (shared/plugin-api.ts) owns both browser-safe types and
Zod schemas, including the catalog/install/connector/payment-method wire
shapes. Backend adds the SQLite state (migration v34) and activation-
aware tax resolution; frontend renders capability display text and
configuration field metadata straight from each manifest, so adding a
country or provider no longer needs frontend changes.

Backend: activation-aware tax dispatcher, runtime registry with
hosted/in-process capability enforcement, connector handler dispatch,
provisionBuiltinTaxPackage for first-run setup and idle restarts,
reinstall from 'uninstalled' status, 32 KiB connector config cap,
32-byte secret-field detection with camelCase normalization, 256 KiB
envelope cap, host-provider must list 'hosted' providers in
connectorIds, and a shared Zod envelope bundle validator.

Frontend: Zod-validated API responses, generic capability-configuration
form driven by capability.configuration.fields, manifest-derived
localized labels/descriptions, plugin-driven payment method list with
QrCode icon, shared InstallationStatus / InstallationFeature / PaymentMethod
types in @flo-plugin-api, and Settings → Integraciones tab on AR store
showing 2 packages, 5 AR capabilities, manifest permissions, and the
per-capability activation gate.

Tests: npm test green (lint, tsc, full suite including
plugin-contracts, plugin-routes, plugin-default; npm test wired the
plugin-default suite into the cors chain).

Docs: docs/plugin-architecture.md, docs/plugin-authoring.md,
docs/plugin-proposal.md, docs/plugin-references.md.

Note: migration v34 ships without plugin_packages / plugin_outbox tables
(speculative schema surface removed); the activation gate now treats
hosted fiscal the same as hosted payment/delivery (requires
authorized + broker-verified connector); review comments from
review-risk / review-reliability / oracle agents addressed.

Refs: docs/plugin-proposal.md, docs/plugin-authoring.md
@carvalab
carvalab force-pushed the feature-plugin-system branch from 65a0898 to 3c377d1 Compare July 24, 2026 02:51
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.

1 participant