Type to search pages and rules.

Browse pages

Dependencies

Dependencies are the packages a codebase takes on, and each one adds updates, advisories, and migration work. This page states which dependencies each layer, from Domain to the API host, may take, where each package version is pinned, and what a new package needs before it lands. It applies to anyone who adds a package, references another layer, or connects two frontends.

Markdown11 provisions

Stack: any Layer: workspace

Concepts#

Layers and Their Packages#

The backend holds four architecture layers: Domain, Application, Infrastructure, and WebApi. Each layer is a role a type plays, whatever folder or project holds it. Each layer takes only the packages its provisions allow:

  • Domain takes none.
  • Application takes messaging and read abstractions, plus the one declared read boundary.
  • Infrastructure takes providers and adapters.
  • WebApi, the layer that hosts the API, takes host, authentication, and documentation packages.
  • Test projects take test-only packages.

Pins and the Manifest#

Every version resolves in one owned place: litespec.manifest.json pins the baseline stack the standards name, the consumer's own central file or lockfile pins the product's own packages, and no version sits inline in a module file. A central entry still does not authorize every layer to reference the package: the ownership table in the conventions below decides who takes what.

Approving a New Package#

A package outside the manifest lands only with a technical decision under .litespec/implementation/decisions/: the use case it serves, the alternatives weighed, and the condition that removes it, with an admits entry per package naming its owning layer, license, processing location, account owner, failure behavior, and operating cost. Writing the removal condition while the alternatives are still fresh keeps each package traceable to the requirement that admitted it.

A project that selects the Delivery extension generates a bill of materials in CI, an SPDX or CycloneDX JSON document paths.sbom names, and checks it against the manifest pins and the admissions under that extension. A direct package is one the document root depends on in one step: an SPDX DEPENDS_ON relationship from the root package, or a CycloneDX dependencies entry of metadata.component. Its ecosystem comes from its package URL: pkg:nuget/ maps to the manifest's NuGet pins and to nuget: admission ids, and pkg:npm/ to its npm pins and npm: ids.

Standards#

Follow the Declared Dependency Graph#

ruleruleguidance
litespec/workspace/dependencies#follow-the-declared-dependency-graph
Statement

A dependency between layers or modules MUST follow the declared graph and add no other edge.

Why

The declared graph is the list of crossings review checks. An undeclared edge lets a layer reach past its boundary, and review finds no graph that forbids it. Each new edge then needs an explicit decision instead of arriving silently with an import.

Checka reviewer answers

Reviewed forEach layer and module dependency follows the declared graph; read the references between layer folders under apps/api/src and the imports in apps/.

Marked guidance: a reviewer answers this question.

Keep Domain Dependency-Free#

ruleruleguidance
litespec/workspace/dependencies#keep-domain-dependency-free
Statement

The Domain layer MUST depend on no external package or framework.

Why

Domain holds business rules, and business rules outlive frameworks. Depending on no external package or framework keeps the rules testable and portable on their own. Persistence, messaging, web, injection, serialization, and logging libraries all stay outside.

Checka reviewer answers

Reviewed forNo Domain layer folder needs an external package or framework; read the Domain layer folders and the manifests that serve them.

Marked guidance: a reviewer answers this question.

Keep Application Free of Provider Packages#

ruleruleguidance
litespec/workspace/dependencies#keep-application-free-of-provider-packages
Statement

Application MUST depend only on messaging abstractions, required read abstractions, and the one declared read boundary.

Why

Application coordinates use cases through abstractions; Infrastructure hides the technology behind them. A provider package in Application makes the coordination layer depend on the technology Infrastructure was meant to hide, so a provider change reaches two layers. Only messaging abstractions, required read abstractions, and the one declared read boundary stay.

Checka reviewer answers

Reviewed forThe Application layer folders reference only messaging abstractions, read abstractions, and the declared read boundary; read those folders and the manifests that serve them.

Marked guidance: a reviewer answers this question.

Keep Providers in Infrastructure#

ruleruleguidance
litespec/workspace/dependencies#keep-providers-in-infrastructure
Statement

A database provider, external SDK, resilience, discovery, or serialization adapter MUST live in Infrastructure.

Why

A database provider, an external SDK, and resilience, discovery, and serialization adapters all change with their technology. Keeping them in Infrastructure keeps each provider change inside one layer, apart from the read boundary the ownership table assigns to Application.

Checka reviewer answers

Reviewed forDatabase providers, external SDKs, and resilience, discovery, and serialization adapters appear only in the Infrastructure layer folders; read those folders and the manifests that serve them.

Marked guidance: a reviewer answers this question.

Pin Every Dependency Version Centrally#

ruleruleguidance
litespec/workspace/dependencies#pin-every-dependency-version-centrally
Statement

Every dependency version MUST resolve through central management or the committed lockfile, at the pin litespec.manifest.json states where it states one.

Why

An inline version in one module file is invisible to the manifest that is supposed to own it, so two modules drift to two versions of the same package. Central management or the committed lockfile resolves every version in one owned place: the manifest pins the baseline stack the standards name, and the consumer's own central file or lockfile pins the product's own packages. The manifest belongs to the LiteSpec release rather than to the consumer, so a consumer's pins live beside its code, admitted by litespec/workspace/dependencies#approve-new-packages-explicitly.

Example

A consumer generating invoices pins its PDF library in its central version file with a decision record. The manifest names only the baseline stack, and the pin is still central.

Checka reviewer answers

Reviewed forEvery version resolves through central management or the committed lockfile at the manifest pin where one exists; no version sits inline in a module file.

Marked guidance: a reviewer answers this question.

Approve New Packages Explicitly#

rulerulecheckable
litespec/workspace/dependencies#approve-new-packages-explicitly
Statement

A package absent from the manifest MUST be admitted by a technical decision whose admits entry names the package with its owning layer, license, processing location, account owner, failure behavior, and operating cost.

Why

A package arrives with a cost nobody revisits: updates, advisories, license review, and migration work, and a hosted one adds a place personal data may travel and an account someone must own. The decision answers those while the alternatives are still fresh: its options and choice give the use case and the alternatives, its reopenedWhen gives the removal condition, and its admits entry gives the facts a dependency gate asks per package. A baseline package lands in the manifest in the same change as its decision. A consumer-owned package lands in the consumer's central file instead, because the manifest belongs to the LiteSpec release rather than to the consumer.

Example

Claimwick's decision/0005-render-receipts-as-pdf chooses a PDF library over an HTML-to-PDF browser and admits nuget:PdfSharp with layer Infrastructure, license MIT, location in-process, account owner none, failure behavior "the receipt download fails and the expense stays readable", and operating cost none.

Checkchecked by a tool

Checked bylitespec model check reports dependency-undeclared (level S) for a direct package in the bill of materials that the manifest does not pin and no decision admits, and admission-invalid (level G) for an admits entry missing any of its six facts.

Marked checkable: the tool named here checks it.

Keep Frontend Applications Isolated#

ruleruleguidance
litespec/workspace/dependencies#keep-frontend-applications-isolated
Statement

A frontend MUST NOT import another application's source or another module's internal feature files.

Why

When one frontend imports another's source, neither can change its internals without checking the other, so the two release as one. A frontend also stays out of another module's internal feature files for the same reason. Sharing goes through a shared package with a documented public entry point that depends on no application.

Checka reviewer answers

Reviewed forNo import in one frontend names another application's source or another module's internal feature files.

Marked guidance: a reviewer answers this question.

Keep the Approved Web UI Dependency Boundary#

ruleruleguidance
litespec/workspace/dependencies#keep-the-approved-web-ui-dependency-boundary
Statement

A React web frontend MUST take its UI packages from the uiBaseline in litespec.manifest.json, and a Svelte web frontend from uiBaselineSvelte.

Why

A fixed UI baseline keeps every screen drawing from the same design system, styling, base components, icons, and utilities. Taking UI packages only from uiBaseline for React and uiBaselineSvelte for Svelte keeps that set reviewed and versioned in one place. Each baseline pins the design system, styling, base components, icons, and the utility packages the installed source imports directly.

Checka reviewer answers

Reviewed forEach React frontend manifest takes UI packages only from uiBaseline, and each Svelte one only from uiBaselineSvelte, in litespec.manifest.json.

Marked guidance: a reviewer answers this question.

Conventions#

Keep Generated Packages Dependency-Light#

conventiondefaultguidance
litespec/workspace/dependencies#keep-generated-packages-dependency-light
Default

Keep generated API types free of dependencies and limit the shared client to the pinned generator's runtime, its query plugin, and those types.

ReplacementA project whose client needs more records its own client dependency set in the client package manifest.

Why

Generated types mirror the API contract, and the shared client only carries them to queries. Keeping the types free of dependencies and the client limited to the generator's runtime, its query plugin, and those types keeps regeneration a pure function of the contract. A generated package that owns state, UI behavior, or authentication policy stops being regenerable.

Checka reviewer answers

Reviewed forThe generated API types package carries no dependencies, and the shared client carries only the generator runtime, its query plugin, and those types; where replaced, the client package manifest names the project's own client dependency set.

Marked guidance: a reviewer answers this question.

Keep Test Dependencies in Test Projects#

conventiondefaultguidance
litespec/workspace/dependencies#keep-test-dependencies-in-test-projects
Default

Keep assertion, substitution, host, container, and architecture-test packages inside test projects.

ReplacementA project that ships a test helper records the helper and its reason in its project documentation.

Why

Assertion, substitution, host, container, and architecture-test packages exist to verify code, not to run it. Referenced by a production project, a test package ships to production with its own dependencies and advisories. Keeping them inside test projects keeps the shipped graph free of verification weight.

Checka reviewer answers

Reviewed forNo production project manifest references an assertion, substitution, host, container, or architecture-test package; where replaced, the project documentation names the shipped helper and its reason.

Marked guidance: a reviewer answers this question.

Assign Each Baseline Package to Its Owning Layer#

conventiondefaultguidance
litespec/workspace/dependencies#assign-each-baseline-package-to-its-owning-layer
Default

Assign each baseline package to the owning layer named in the table in this section.

ReplacementA project that moves a baseline package records its own owning layer in the decision that admits the move.

Example

LayerDirect package groups
DomainNone
ApplicationRequired abstractions only, plus the declared read boundary
InfrastructureProviders and adapters required by implemented ports
WebApiHost, authentication, and documentation packages
Test codeTest-only packages

A new package joins the narrowest layer that needs it. A central version entry does not authorize every layer to reference that package: the table decides who takes what. A consumer references conditional packages only after their extension activates.

Checka reviewer answers

Reviewed forEach baseline package sits in the owning layer the table in this section assigns; where replaced, the admitting decision names the project's own owning layer.

Marked guidance: a reviewer answers this question.