Merconiq is currently one deployable ASP.NET Core application backed by PostgreSQL. Business capabilities are grouped in the existing projects; they are not separate deployable services or independently versioned packages. This page describes boundaries demonstrated by the current source tree, not a promise that every listed interface is a supported third-party API.
| Capability | Current code ownership | Current boundary |
|---|---|---|
| Platform, tenancy, authorization and audit | Contracts and entities in src/Merconiq.Core; HTTP authentication, authorization, tenant request context and host composition in src/Merconiq.Web; persistence and company membership implementation in src/Merconiq.Infrastructure |
The Web host authenticates and authorizes requests. Tenant-scoped application services and persistence enforce data scope. Audit records are produced by the shared persistence pipeline. A service interface alone is not an authorization boundary. |
| Inventory | Domain entities, service contracts and stock operations in src/Merconiq.Core; PostgreSQL mapping and stock-count persistence in src/Merconiq.Infrastructure; HTTP/UI entry points in src/Merconiq.Web |
Covers item/location stock, movement history, reservations, quarantine, valuation, stock counts and transfer-in-transit records. Inventory posting remains behind existing application services and their transaction/unit-of-work behavior. |
| Procurement | Supplier and purchase-order contracts, entities and services in src/Merconiq.Core; EF mapping/repositories in src/Merconiq.Infrastructure; endpoint/UI and DI composition in src/Merconiq.Web |
An approved purchase order is a procurement document; it is not itself a stock receipt or a posted financial journal. Do not infer a goods-receipt-to-ledger contract that the current source does not implement. |
| Sales / customer master | Customer entity in src/Merconiq.Core; tenant/company ownership, migration and audit in src/Merconiq.Infrastructure; company-capability API in src/Merconiq.Web |
Implements independent company-owned customer records and configuration-only payment-term days. Quotations, orders, invoices, receivables, credit controls, shared customer masters and UI/import workflows remain future work. |
| Integrations and decision support | Contracts and forecasting services in src/Merconiq.Core; webhook delivery, configuration, host services and persistence registrations in src/Merconiq.Infrastructure and src/Merconiq.Web |
The HTTP and webhook behavior documented in docs/API.md is part of the declared public v1 contract. Issue #355 tracks additional implementation and evidence; future, undocumented integration behavior is not thereby promised as a stable contract. |
| General ledger / finance | Fiscal-calendar, journal and posting-policy contracts and models in src/Merconiq.Core; FiscalCalendarService, JournalPostingService and AccountPostingPolicyService in src/Merconiq.Infrastructure; company-capability API in src/Merconiq.Web |
The chart-of-account catalog remains company-supplied configuration. The posting kernel commits immutable, balanced JournalEntry/JournalLine rows exactly once per company/source action inside one transaction, gated by open fiscal periods and company base currency; corrections are source-linked reversals. Goods receipts, supplier invoices, payments, deliveries and sales invoices now post through this kernel with exact stock/GRNI/AP/AR/COGS effects; sales-order and purchase-order approval never post. Stock valuation records remain distinct from journal rows and reconcile through the posting services. See docs/FINANCE_POSTING.md. |
These are capability ownership notes, not a claim that code is already split into module assemblies. New behavior should stay with the capability that owns its invariants. Shared platform concerns belong in existing platform code; they should not become an unowned cross-module utility layer.
Merconiq.Core <- Merconiq.Infrastructure <- Merconiq.Web
^ ^ ^
+--------------- Merconiq.Tests --------+
Merconiq.Core has no production project references. It owns domain types,
service contracts and persistence-agnostic application/domain logic.Merconiq.Infrastructure references Core only. It owns EF Core/PostgreSQL
persistence, repositories and infrastructure implementations.Merconiq.Web references Core and Infrastructure. It owns the deployable
host, HTTP/UI, authentication/authorization pipeline and composition root.Merconiq.Tests may reference all three production projects. Production
projects must not reference the test project.The executable check in scripts/validate-architecture.sh enforces this
project-reference graph. A new project or dependency direction is an
architecture change and requires updating that check and its evidence; do not
create a service-per-capability topology to add an extension.
The current persistence owner is Merconiq.Infrastructure. Its
InventoryDbContext contains the implemented capability tables and the EF
migrations in src/Merconiq.Infrastructure/Migrations/. There is one context
and one migration stream. Do not create another context, migration assembly,
or schema owner until a concrete ownership boundary and an upgrade test prove
that separation is safe. The Web host currently applies this migration stream
automatically only in Development.
For existing inventory postings, the persisted records have distinct roles:
| Record | Meaning and rule |
|---|---|
StockTransactions |
Append-only stock-operation evidence. Physical movement types record quantity movement; Quarantine and QuarantineRelease record hold changes without changing on-hand quantity. Interpret TransactionType before reconciling quantities; do not count every row as physical flow. The DbContext enforces append-only behavior. |
StockInHand |
Current on-hand quantity by item, location and lot. ReservedQuantity and QuarantinedQuantity are holds within that position, not additional physical movements. Read it as the current operational position; do not independently write it or treat it as the movement history. |
StockValuationEntries |
Append-only moving-average valuation postings linked to valued stock movements. These are inventory valuation evidence, not general-ledger journals. |
StockValuationBuckets |
Current weighted-average quantity/value position by item and location. Keep it consistent with the valuation entries through the existing posting service; do not use it as a second, independently writable ledger. |
TransferOrders, TransferTransitEntries and TransferTransitSettlements |
Transfer intent and the recorded in-transit/settlement history. A transfer extension must preserve source, transit and destination movement lineage through the existing transfer workflows. |
Posting code must use the existing application service/command and unit-of-work
boundary so validation, tenant/company authorization, audit, concurrency,
idempotency (where supported), movement/valuation consistency and transaction
rollback remain in force. Reports and adapters may read authorized application
results; they must not write another module’s ledger or bypass the posting
service with direct DbContext writes. Never infer a general-ledger posting
from an inventory valuation entry. The general-ledger journal model exists
(JournalEntry, JournalLine) behind IJournalPostingService. Its only
writers are the finance posting adapters listed in docs/FINANCE_POSTING.md
(procurement, sales, payments, POS, fixed assets, expense claims and till
approval), which reach the ledger through that kernel. Only those services may
write journal rows, stock rows, receipts, invoices or payments; direct
DbContext writes to posted records are blocked by append-only guards.
Reconciliation, CRM, card settlement and statement services read ledger or
pipeline state and never write journals.
src/Merconiq.Web/Configuration/ApplicationServiceExtensions.cs explicitly
registers existing Core service contracts such as IStockService,
IPurchaseOrderService, ITransferOrderService, IDemandForecastService,
IAnomalyDetectionService and IWebhookDispatcher with in-process
implementations. ForecastingOptions.Implementation accepts only the managed
moving-average and SSA algorithm identifiers currently supported by
ForecastingImplementations.
These registrations demonstrate compile-time dependency injection inside the host. They do not establish a stable external extension API: registrations are host-owned, no service interface is identified here as a supported third-party extension contract, there is no published report/adapter contract, and no example third-party implementation has been tested through an application/database upgrade. Adding an alternate implementation today requires compiling it into and registering it from the host composition root. Any such implementation must be called only through an authorized application entry point and preserve tenant scope, audit and posting invariants. Replacing a registration does not itself provide those controls.
tests/Merconiq.Tests/Integration/ExtensionUpgradeFixturePostgreSqlTests.cs
defines one bounded, test-only example around the existing MediatR
CreateItemCommand pipeline. It starts at the explicitly named
20260918183139_EnforceAuditLogAppendOnly migration, applies the current
forward migrations, then sends an authenticated item-creation request through
the Web host with the adapter registered by dependency injection. Its assertions
cover the host authorization boundary, the existing EF audit record, and the
absence of writes to stock, valuation, procurement, and transfer ledgers. This
is an executable example of an in-process observer around one existing
operation; it does not publish a third-party report, rules, or adapter API and
does not establish that arbitrary business behavior can be replaced safely.
The interfaces remain internal implementation seams until a stable external contract is explicitly designed, reviewed, versioned, and qualified. Do not introduce runtime assembly discovery, arbitrary or untrusted plugin loading, a generic plugin framework, direct cross-module ledger writes, or a second migration owner to make an example appear extensible.
CHANGELOG.md records a 1.0.0 release and states that the project follows
Semantic Versioning. docs/API.md explicitly identifies /api/v1 as the
public HTTP API contract. Accordingly, treat the documented /api/v1
request, response, authentication and behavior contract as stable: compatible
additions may remain in v1, but a breaking HTTP contract change requires a
new API version and a documented consumer transition. Do not silently change
documented v1 behavior.docs/API.md are part of the declared public v1 contract and follow the same
compatibility rule, including documented authentication, response, event
identity, signature, retry and payload guarantees. Issue #355 tracks
additional implementation and acceptance evidence; its open status is not an
exception to those existing v1 guarantees. Undocumented future behavior is
not promised as part of the stable contract.Merconiq.Infrastructure /
InventoryDbContext.The web application enforces strict Roslyn analyzer baselines in both local Release builds and automated CI pipelines:
When a warning appears in these categories, developers must fix the component or contract definition rather than suppressing the diagnostic. Unrelated documentation warnings (such as CS1591) remain advisory and do not fail the build.
Merconiq integrates OpenTelemetry for end-to-end distributed tracing and metrics:
OpenTelemetry:Enabled=true (or OpenTelemetry__Enabled=true) and specify an OTLP collector via OpenTelemetry:OtlpEndpoint.Merconiq.Inventory): Emits business counters for concurrency retries, forecast fallbacks, low-stock threshold triggers, and webhook delivery failures. Keep collector endpoints and credentials in deployment environment secrets rather than source control.Production deployments persist Data Protection encryption keys to durable external storage:
DataProtection:KeyStoragePath (default /app/data/keys in Docker containers, mapped to the dataprotection persistent volume).