Ga naar hoofdinhoud

Architecture decisions

Company-wide architecture decision records (ADRs) apply to every Conduction app. This page lists them by number, so a product page can cite "ADR-083" and a reader can look it up here.

The ADR files live in our internal Hydra repository, under openspec/architecture/. That repository is private, so this page is the public index. How ADRs relate to specs is on Spec-driven development.

How to read the status column​

  • Core rule. ADR-001 to ADR-016, except ADR-007 and ADR-013. These are the compact base rules every agent session loads. They carry no status field because they are the standing baseline.
  • Accepted. The decision is in force.
  • Proposed. The decision is written and under review. Many proposed ADRs already have a gate or a test that enforces them; the status is what the file says, not a measure of adoption.

Numbers 027, 028 and 116 to 121 are not in use. A number is never reused.

Data, API and backend​

ADRTitleSummaryStatus
001Data layerAll domain data lives in OpenRegister objects; app config lives in IAppConfig.Core rule
002APIOne URL pattern, standard HTTP methods, _page/_limit pagination, Nextcloud auth only.Core rule
003BackendController, service, mapper in strict layers; constructor injection; no static locators.Core rule
005SecurityNextcloud authentication only, admin checks on the backend, per-object authorization on every mutation.Core rule
006MetricsEvery app exposes a Prometheus metrics endpoint and a public health endpoint.Core rule
011Schema standardsschema.org as the primary vocabulary, relations through OpenRegister, breaking changes migrate.Core rule
012DeduplicationSearch OpenRegister and the shared library before building a new capability.Core rule
014LicensingCode is EUPL-1.2; info.xml says agpl because the App Store does not know EUPL.Core rule
015Common patternsPatterns found in three or more reviews, fixed across all files in one pass.Core rule
016Routesappinfo/routes.php is the only place routes are registered.Core rule
019Integration registry patternThings linked to an OpenRegister object are declared in a two-sided registry.Accepted
022Apps consume OpenRegister abstractionsWhen OpenRegister offers a capability, apps use it instead of building their own.Accepted
023Action-level authorizationOpenRegister decides who may read or write which objects; apps trust its filtering.Accepted
031Schema-declarative business logicBehaviour that a schema extension can express is declared in the schema, not coded.Proposed
041Cross-app commands via eventsOne app asks another to act through typed Nextcloud events, not HTTP calls.Proposed
045OpenRegister owns the MDM surfaceData quality and deduplication are schema-scoped OpenRegister features.Proposed
047OpenRegister owns the AVG/DSAR workflowData subject requests are one OpenRegister capability, not rebuilt per app.Proposed
048Cross-app semantic referencesReferences between apps resolve by semantic type, not by app id or schema slug.Proposed
050Response envelopeEndpoints return flat, purpose-shaped payloads without a wrapper envelope.Accepted
051Semantic object handoffCross-app workflows hand objects over by semantic type.Proposed
052URL canonicalizationEvery URL comparison uses one shared canonicalizer.Accepted
058Bounded object queriesObject queries get a default limit when the caller sets none.Proposed
065One flow engine and one canvasThe flow engine lives in OpenRegister; the flow canvas lives in the shared Vue library.Accepted
066Cross-app leaf registrationSibling apps may add read-only leaf providers through a typed collect event.Accepted
069Background jobs, repair steps and listenersOne directory and one set of conventions for jobs, repair steps and listeners.Proposed
070OpenRegister-backed persistence is the defaultNew apps store data in registers; own database tables need an exception ADR.Proposed
078Object event work placementListener work that cannot change the write runs asynchronously.Proposed
083OpenRegister dependency shapeOpenRegister is constructor-injected and guarded at runtime.Proposed
084OpenRegister published contractOpenRegister publishes interfaces; consumers type-hint the interface, never the class.Proposed
091External API surface belongs to OpenConnectorEndpoints authenticated by anything other than a Nextcloud session live in OpenConnector.Proposed
092Retire the classic OpenConnector dialectOld ingestion entities give way to OpenRegister's flow engine.Proposed
098Fleet workflow convergenceEvery workflow runs on the OpenRegister engine; a node can be a person, group or agent.Proposed
099Acting on behalf of a userActing as a user is a granted, run-scoped identity with one implementation.Accepted
100A well-formed appApp id, directory name and lib/ vocabulary are one consistent fact.Proposed
103Virtual schemas for Nextcloud entitiesNextcloud entities appear as virtual schemas with semantic providers.Proposed
105Controller exception translationControllers translate service exceptions into JSON responses or declare them.Accepted
106Request lifecycle work placementSetup work runs at install, upgrade or cron, never during a request.Proposed
107Money and effort ownershipEach kind of money and effort has one owning app.Proposed

Security and integrations​

ADRTitleSummaryStatus
054Public surface hardeningPublic endpoints get rate limits, CSRF on writes, webhook secrets and signed tokens.Proposed
055Authorization gate extensionsChecks for actor parameters, write symmetry, tenant scope and RBAC bypass flags.Proposed
060Test reality and phantom greenPrivileged actions reachable by non-admins need a test that can actually fail.Proposed
064Credential custodyApps store a credential reference; the secret lives in the credential broker.Proposed
067Shared egress planeOutbound calls to shared third-party APIs go through one adapter and one egress guard.Proposed
082Public endpoint throttlingEvery public endpoint is throttled, and the attribute alone is not enough.Proposed
095Tool grants are a structureAn agent's tool grants are app, subject, action and tool, not loose strings.Accepted
102Config fail-modeEvery security-relevant config read states what happens when the value is empty.Accepted

Frontend and user interface​

ADRTitleSummaryStatus
004FrontendPinia stores, Nextcloud axios, translated strings, shared components.Core rule
007InternationalizationEvery app supports Dutch and English.Accepted
010NL Design SystemDesign tokens instead of hard-coded styling, scoped styles, WCAG AA.Core rule
017Component compositionSelf-contained components are not wrapped in extra layout containers.Accepted
018Features and roadmap menuEvery app links to its features and roadmap from the navigation settings.Proposed
024App manifestEvery app describes its pages and menus in a validated manifest.json.Accepted
025i18n source of truthOne source of truth for translations and API language negotiation.Proposed
026Store migration patternsApps load OpenRegister data through the shared object store.Accepted
033Collaborative editingRules for showing and editing an OpenRegister object on a detail page.Proposed
034AI chat companionOne shared chat companion component family for every app.Proposed
036Universal widget manifestEvery page type uses one widgets[] array on a grid.Proposed
042First-time setup wizardA shared setup wizard, declared in the manifest.Proposed
043Product walkthrough engineA shared walkthrough engine, declared in the manifest.Proposed
044Menu architectureApps build their menu through one shared manifest pipeline.Proposed
049Declarative widget vocabularyBuilt-in widget types come before custom widgets.Proposed
053Semantic color tokensWidget colours are semantic tokens, not colour values.Proposed
057i18n locale parity and key hygieneA check fails when a locale misses a key the English file has.Proposed
059Keyboard operability of custom controlsAnything that responds to a pointer also works with a keyboard.Proposed
061Frontend bundle and boot hygieneThe shared library is tree-shakeable and apps boot lean.Proposed
062Detail page grid disciplineA detail-page widget fills its grid cell and never overflows it.Proposed
071Shared frontend runtimeThe frontend runtime lives in @conduction/nextcloud-vue.Proposed
072Component absorptionA component hand-rolled in two apps moves into the shared library.Proposed
073Vue 3 library targetEvery app targets the Vue 3 line of the shared library.Proposed
077Semantic icon vocabularyOne icon per concept across the fleet.Proposed
079Settings surface placementAdmin settings live in Nextcloud's settings; the gear is per user.Proposed
080Store planeOpenRegister owns discovery; each app owns its own install.Proposed
081Vue 3 migrationThe shared library moves to Vue 3 before any app does.Accepted
085Forms and journeysForms and journeys are OpenRegister data, rendered by one runtime.Proposed
087Office suite abstractionFile conversion goes through Nextcloud's conversion manager.Proposed
089Communal core loads onceThe shared library is made smaller before several apps share it on one page.Proposed
096Dashboard, index and detailAn app opens on a dashboard; collections are index pages leading to detail pages.Accepted
097Navigation budgetFour main menu domains, six at most.Proposed
101Universal in-app editingOne edit shell that appears in every app.Proposed
109Public portal shared runtimeThe public portal runs the same manifest runtime as the apps.Proposed
110App navigation contractLinks to other apps sit in an Integrations section, not the main menu.Proposed
111Demo data and setup walkthroughEvery app ships demo data and offers it first in its setup walkthrough.Proposed
112Reports are one pageAn app's reports are one page of cards.Proposed
113Cross-app widgets declare their appA widget that needs another app says so and shows a set-up state when it is absent.Proposed
114App chrome is seven itemsThe fixed app chrome has seven items, checked by one gate.Proposed

Platform and products​

ADRTitleSummaryStatus
030JourneydocUser documentation is produced from captured screenshots of the running app.Accepted
035MCP per-app coverageEvery app with user actions exposes them as MCP tools.Proposed
037Modular config fragmentsEach change adds its own register or manifest fragment file.Accepted
039Root configuration consolidationRoot config files follow one shared template.Accepted
040AppHostOpenRegister's AppHost takes over observability and boilerplate from apps.Proposed
046Portal contribution contractApps contribute to the portal through one declared class.Accepted
063MCP as a platform abstractionSchemas declare MCP tools; OpenRegister derives them.Proposed
075Document generation has one channelOne app owns document and PDF generation for the fleet.Proposed
076AppHost settings planeThe app scaffold and settings are AppHost services.Proposed
086Portaliq headless CMSPortaliq is the fleet's content system; its API is the product.Proposed
094Automation targets the OpenRegister flow engineNew automation uses OpenRegister's flow engine, not n8n.Accepted
104Spectr market intelligence appMarket intelligence is rebuilt as a configured app.Proposed
108Public surface placementCitizen-facing object access belongs to Portaliq.Proposed

Engineering process​

ADRTitleSummaryStatus
008TestingEvery service, component, endpoint and spec scenario gets a test.Core rule
009DocumentationEvery user-facing feature is documented with screenshots.Core rule
013Unified container poolAll pipeline containers share one slot pool.Accepted
020Gates scoped to the PR diffMechanical gates judge the lines a pull request changed.Accepted
021Bounded fix scope by shapeA reviewer may fix only changes of a defined shape.Accepted
029Route reachability gateEvery controller method that returns a response has a route, and every route a method.Accepted
032Spec sizing and chainingA proposal declares its kind, which sets its route and budget.Proposed
038Canonical requirement headingOne heading form for requirements, with an optional id suffix.Proposed
056Spec canonical home and status truthA spec exists when it is on the default branch; status claims need an anchor.Proposed
068Skill maturity and self-learningOne seven-level maturity model for skills.Proposed
074E2E surface coverage baselineEvery UI surface has a Playwright test that can fail.Proposed
088Agent-authored artefacts are markedAnything an agent writes carries a durable mark.Proposed
090Dependency integrity gatesA build does not start from a tree that disagrees with its lockfile.Proposed
093Composer dependency cooldownNew composer releases wait a cooldown period before Dependabot proposes them.Proposed
115A green instrument is not a present featureA check that judged nothing says so, and nobody counts it as a pass.Proposed

Adding or changing an ADR​

Read Writing ADRs for the format and for when a decision belongs in an app's own openspec/architecture/ instead. When a new company-wide ADR lands, add its row here in the same pull request.