The open-source customer support decisioning platform: see what is happening, triage one queue, act before the customer asks.
Try the live demo → — the whole operator workflow on deterministic fixtures. No account, no credentials, nothing to install.
Steward is the open-source (Apache-2.0) customer support decisioning platform. Customer operations, risk and revenue teams see what is happening across their accounts as it happens: the usage, support, settlement, CRM and KYC signals, each with its source, its record, when it was observed and whether the source is healthy, weighed at the moment a decision is made rather than read from a stale profile. They triage one queue of processing-limit reviews, expansion outreach, friction interventions and KYC/KYB escalations, and act before the customer asks. An approver accepts, holds or rejects; the review is recorded and forwarded to Decionis, the authority behind Steward, which executes the change and files the Decision Dossier. Nothing changes until it does.
The problem it solves: in regulated fintech, the people closest to the customer can see that an account is ready for a higher processing limit or is about to churn, but they cannot act on it without an auditable, policy-bound path. Steward is that path. Every action an operator takes here becomes a reviewed, attributable event upstream — never an ad-hoc change made in a spreadsheet.
This boundary is the most important thing to understand before contributing.
Steward does not own policy evaluation, identity resolution, execution grants, or Decision Dossiers. Those remain authoritative in the Decionis platform. Steward collects the signals that become context, presents what the platform decided, and forwards what the operator does:
| Steward owns | Decionis platform owns |
|---|---|
| The operator UI and review workflow | Policy evaluation and the customer_ops policy pack |
| Server-side orchestration (the BFF) | Identity resolution and the weighing of signals |
| Signal connectors and the credentials they run with, server-side | Execution grants, Decision Dossiers, the audit ledger |
| Typed, runtime-validated API contracts | The authoritative record of every review |
| Formatting and presentation policy |
An operator can accept a review in Steward. That acceptance cannot, by itself, change a processing limit or a policy. Steward forwards the review; Decionis decides, executes, and returns the resulting state and a dossier reference.
Steward also collects. Connectors under infra/connectors/ reach the operator's own systems (CRM,
support desk, ERP, MCP servers, uploaded documents, file servers), produce signals in one small shape,
and forward them to the platform over the Decionis Protocol, where they are resolved to accounts and
weighed. Steward stores none of it. docs/SignalConnectors.md records the
decision and the workstreams; the first, the connector framework and the Signal sources page, is in
the tree.
Steward also keeps its own records. Its users and sessions, the Decionis workspace connection,
the signal sources it is configured with, the decisions it showed, the reviews its operators
recorded and their activities live in a database the operator chooses: the embedded one under
STEWARD_DATA_DIR with nothing configured, or PostgreSQL, MySQL, SQL Server or Oracle Database
named by STEWARD_DATABASE_URL. A signal's content is never written to it. The schema changes
only through portable migrations, applied at start. docs/Persistence.md records the
decision.
OpenCore.md states the same boundary as a business model: what is free forever, what Decionis operates, and the one point where commerce enters the flow. docs/Distribution.md is the plan for shipping Steward as a container and being found by the agents that evaluate it.
Browser
-> Steward Next.js server / BFF <- this repository
-> signal connectors: CRM, ERP, MCP servers, documents, file servers
-> Decionis /v1/cdi APIs
-> signal ingestion, identity resolution, weighting
-> customer_ops policy pack
-> execution grants, dossiers, ledger
One application, four ways to run it. The image, the tarball and the source are built from the same commit, and nothing about the trust boundary differs between them.
| Where | How | Page |
|---|---|---|
| Docker | docker run -p 3000:3000 ghcr.io/decionis/steward:<version>; the same digest as docker.io/decionis/steward; two architectures, non-root, attested |
docs/Docker.md |
| Release tarball | decionis-steward-<version>.tar.gz from the releases, with its SBOM and provenance |
Deployment |
| From source | pnpm install && pnpm dev, demo mode, no credentials |
Quickstart |
| Hosted demo | https://decionis-steward.vercel.app, demo mode, nothing to install | What you get |
Every path starts in demo mode and ends at the same place: connecting a Decionis workspace, which
OpenCore.md explains costs nothing until a review is meant to execute. An agent
evaluating a deployment reads /llms.txt on any of them.
Requires Node >= 20 and pnpm 9. No Decionis credentials are needed — the app boots against deterministic demo fixtures.
cp .env.example .env.local
pnpm install
pnpm devOpen http://localhost:3000.
STEWARD_DATA_MODE=demo (the default outside production) serves every screen from
infra/demo/DemoStewardData.ts via DemoStewardRepository. You are signed in as a fixture operator with
ADMIN and APPROVER roles in the demo-fintech organization, so the full review flow is
exercisable end to end:
- Portfolio dashboard (
/) — account health, evidence coverage, the summary counters that drive triage, the decisions that reached an outcome under "Recently resolved", and "Plan support": the portfolio by region and by segment, friction first, so effort goes where it is needed. - Opportunity queue — friction interventions, KYC/KYB escalations, processing-limit reviews, and expansion outreach, each with its rationale, confidence, linked evidence, and the platform's "Why this disposition". Deliberate inaction is grouped beneath the queue rather than hidden.
- Account detail (
/accounts/[id]) — evidence signals with their context class and a coverage strip, connection health, applicable policy, the state of the context at the moment of review, and the decision timeline. - Signal sources (
/signals) — the systems this deployment collects from, each with its kind, the categories it yields, health, when it was last collected and how many signals; "Collect now" pulls a batch and forwards it, and reports how many were accepted upstream.
Every review control is captioned "Records a review only; no downstream limit is changed." That is the trust boundary stated in the interface, not only in the documentation. It is also the free tier: the top bar's "Connect your platform" link leads to the Decionis sign-in handoff, and OpenCore.md says what is paid from there.
Reviews submitted in demo mode return a deterministic result. Nothing is persisted and no downstream action is executed.
In demo mode the sources are fixtures and collecting them forwards nowhere. In live mode a batch
goes to the Decionis Protocol's published signal ingress, authenticated by the connector's webhook
secret from your deployment bundle, and collection answers 503 until those two values are set,
rather than pretending a batch was accepted.
Screenshots are generated from a running instance with pnpm screenshots, so they can be refreshed
rather than left to drift out of date.
All configuration is parsed and validated once, at startup, by StewardRuntimeConfig.fromEnvironment().
Invalid or missing required values fail fast rather than degrading at request time.
| Variable | Required | Default | Purpose |
|---|---|---|---|
STEWARD_DATA_MODE |
no | live in production, else demo |
Selects DemoStewardRepository or DecionisStewardRepository. |
DECIONIS_API_BASE_URL |
in live mode | — | Decionis API origin. Startup throws in live mode if unset. |
DECIONIS_STEWARD_SERVICE_TOKEN |
no | — | Server-to-server fallback credential. Prefer a user session. |
STEWARD_ACCESS_TOKEN_COOKIE |
no | decionis_access_token |
Cookie carrying the Decionis access token. |
STEWARD_ORG_ID_COOKIE |
no | decionis_org_id |
Cookie carrying the organization scope. |
NEXT_PUBLIC_DECIONIS_SIGN_IN_URL |
no | https://decionis.com/sign-in |
External identity handoff target used by /sign-in. |
DECIONIS_CONNECTOR_ID |
to forward | — | The signal connector issued by the Decionis deployment bundle. |
DECIONIS_WEBHOOK_SECRET |
to forward | — | Its webhook secret; sent in a header, never in a URL or a log. |
DECIONIS_WEBHOOK_URL |
no | <API origin>/v1/signals/webhooks/<id> |
The ingress URL from the bundle, if it differs. |
STEWARD_DATABASE_URL |
no | the embedded database | postgres://, mysql://, mssql://, oracle:// or sqlite: selects where Steward's records live. |
STEWARD_DATA_DIR |
no | ./data |
Where the embedded database file lives when no URL is set; a volume in the container. |
STEWARD_DATABASE_MIGRATE |
no | on-start |
off refuses to serve a schema that is behind instead of migrating it. |
Two further cookies are read opportunistically in live mode and are not required:
decionis_display_name (URL-encoded, for the app shell) and decionis_roles (a comma-separated
subset of VIEWER,OPERATOR,APPROVER,ADMIN; anything unrecognized is dropped, and an empty result
falls back to VIEWER).
The connector id and the webhook secret are issued together by a signal-mapping session in your
Decionis workspace (decionis.com/docs/signal-mapping);
setting one without the other fails at startup. Without them, the Signal sources page still lists
and checks sources, and "Collect now" answers 503.
No API credential is ever exposed to browser code. The upstream client is server-only, and its
request timeout is currently fixed at 8s in StewardRuntimeConfig.
STEWARD_DATA_MODE=live
DECIONIS_API_BASE_URL=https://api.decionis.comIn live mode middleware.ts requires a Decionis session on every path except /api/health,
/sign-in, and the discovery files /llms.txt and /llms-full.txt. Page requests without one are redirected to /sign-in?returnTo=…; API requests receive
401 {"error":"UNAUTHORIZED"}. BFF callers may instead present an Authorization: Bearer token with
an X-Decionis-Org-Id header.
A live API failure surfaces as an error. It never falls back to demo fixtures — silently showing fabricated data to an operator making a regulated decision is treated as a defect, not a resilience feature.
| Route | Method | Notes |
|---|---|---|
/api/steward/portfolio |
GET | Portfolio snapshot for the session's org. |
/api/steward/accounts/[id] |
GET | 404 when the account is unknown. |
/api/steward/opportunities |
GET | Opportunity queue. |
/api/steward/opportunities/[id]/review |
POST | Requires APPROVER or ADMIN, else 403. |
/api/steward/signals/sources |
GET | Configured signal sources, with health. |
/api/steward/signals/sources/[id]/collect |
POST | Requires OPERATOR or above, else 403; 503 until forwarding is configured. |
/api/health |
GET | Unauthenticated liveness probe; reports the database opened at start. |
/llms.txt, /llms-full.txt |
GET | Machine discovery; no session, indexable. |
GET /v1/cdi/portfolioGET /v1/cdi/accounts/:accountIdGET /v1/cdi/opportunitiesPOST /v1/cdi/opportunities/:opportunityId/reviewsPOST /v1/signals/webhooks/:connectorId— the Protocol's published signal ingress (decionis.com/docs/webhooks), authenticated by the connector's webhook secret in thex-webhook-secretheader; where a collected batch goes
Every upstream response is parsed through a Zod contract in domain/ before it is allowed into the
application layer, so schema drift upstream fails loudly at the boundary instead of rendering as a
subtly wrong number on a dashboard.
The published image is the quickest path. It is built for linux/amd64 and linux/arm64, runs
unprivileged, defaults to demo mode, and carries a signed provenance attestation:
docker run -p 3000:3000 ghcr.io/decionis/steward:<version> # demo mode, no credentials
gh attestation verify oci://ghcr.io/decionis/steward:<version> --repo decionis/stewardThe same digest is on Docker Hub as decionis/steward. docs/Docker.md covers
tags, live mode, verification and what the image does and does not hold.
next.config.ts sets output: "standalone", so the build emits a self-contained server. The
included Dockerfile packages it:
docker build -t decionis-steward .
docker run -p 3000:3000 decionis-steward # demo mode, no credentialsdocker run -p 3000:3000 \
-e STEWARD_DATA_MODE=live \
-e DECIONIS_API_BASE_URL=https://api.decionis.com \
decionis-steward # live modeThe image runs as a non-root user, disables Next telemetry, and declares a HEALTHCHECK against
/api/health — which is exempt from the session middleware precisely so probes work without a
Decionis session.
If you deploy without Docker, note that .next/standalone is not self-sufficient: next build
emits static assets separately, and .next/static must be copied alongside the server. The Dockerfile
and release workflow both do this.
Tagged releases ship a deployable tarball, a CycloneDX SBOM, and a signed SLSA provenance attestation. Verify an artifact came from this repository before deploying it:
gh attestation verify decionis-steward-<version>.tar.gz --repo decionis/stewardFour layers, one direction of dependency: app → application → domain, with infra supplying
implementations through a composition root and presentation holding formatting policy only.
| Layer | Responsibility |
|---|---|
app/ |
Next.js routes, the BFF, and framework entrypoints. |
application/ |
Use-case services and permission checks (OpportunityService, …). |
domain/ |
Typed, runtime-validated Steward contracts. No I/O. |
infra/ |
Gateways, repositories, config, errors, demo data, composition. |
presentation/ |
Formatting and presentation policy. |
Swapping demo for live is a single decision in StewardRepositoryFactory behind the StewardRepository
interface — the application and UI layers cannot tell the difference. See
Architecture.md for the full boundary and directory map.
docs/ContextEngineering.md maps the design onto the context-engineering framework (sense, interpret, arbitrate, act) and records the plan for making each of the platform's decisions more legible to the operator reviewing it.
pnpm dev # Next.js dev server
pnpm test # Vitest
pnpm test:watch # Vitest in watch mode
pnpm lint # ESLint
pnpm typecheck # tsc --noEmit
pnpm format:fix # Prettier write
pnpm verify # format + lint + typecheck + test + build
pnpm licenses:check # Fail on a dependency outside the approved license policy
pnpm licenses:list # Production dependency licenses
pnpm screenshots # Regenerate the README screenshots from a running instancepnpm verify is the gate — run it before opening a pull request.
CI runs it on Node 20 and 22 (.github/workflows/verify.yml), and separately runs the supply-chain
gate (.github/workflows/audit.yml) — a production-tree audit at any severity, a whole-tree audit at
high and critical, and the license policy check — on every pull request and again weekly, so an
advisory published against an unchanged tree still surfaces.
Production dependencies must carry a license in the approved set enforced by CheckLicensePolicy.mjs. Adding a dependency under any other license requires an explicit, package-scoped exception in that file and a recorded rationale in ThirdPartyLicenses.md.
package.json carries a pnpm.overrides block pinning postcss, nanoid, and sharp above known
vulnerable ranges. These are forward pins to patched releases, not version freezes — remove each once
the upstream next range resolves past it on its own.
- Feature and domain files use PascalCase (
AccountService.ts,CustomerOpportunity.ts). Framework-required files keep Next.js naming (page.tsx,layout.tsx,route.ts,middleware.ts), as do directory segments. - Classes and interfaces over loose utility functions. Each module gets one reason to change.
- camelCase for variables, properties, and methods.
- Full rules in coding.rule.md.
- Evidence may adapt; policy authority stays deterministic.
- Demo mode is explicit. A live API failure never falls back to fixtures.
- Review actions are role-gated in
application/and forwarded to Decionis — the UI is not the enforcement point. - No credential, token, or connector secret reaches client-side code.
- A connector reaches only the host its configured source names, and nothing it collects is stored
here. Collection is gated in
application/like reviews; the platform resolves and weighs what it receives. - Steward's database holds Steward's own records and never a signal's content, and its schema
changes only through a portable migration under
infra/persistence/migrations/, reviewed like code and applied at start.
This repository holds no secrets and no policy logic, which is what makes it safe to develop against in the open.
The trust boundary is enforced in six files, and each is covered by tests you can run:
| Enforcement | Code | Tests |
|---|---|---|
| Session gating, 401-vs-redirect, health-probe exemption | middleware.ts | middleware.test.ts |
Role parsing and the VIEWER privilege floor |
StewardSessionResolver.ts | StewardSessionResolver.test.ts |
| Credential handling and boundary schema validation | JsonHttpClient.ts | JsonHttpClient.test.ts |
| Role-gated review forwarding | OpportunityService.ts | OpportunityService.test.ts |
| Role-gated signal collection and forwarding | SignalService.ts | SignalService.test.ts |
| Migrations at start, failing closed when behind | Migrator.ts | Persistence.test.ts |
Four properties the tests assert directly: the access token never appears in a request URL, only in
the Authorization header; no client component ever receives the session, so the token is never
serialized into a page payload; an unrecognized or wrong-case role claim resolves to VIEWER rather
than to an empty role set; and an unhandled error maps to a generic 500 that leaks no internal detail.
Evaluating Steward as a vendor? EvidencePack.md maps the usual security-review questions to the artifact that answers each one, and states the gaps as plainly as the strengths.
ThreatModel.md sets out the assets, trust boundaries, eight named threats with the code and test backing each mitigation, the security headers this app sets — and, deliberately, the gaps we have accepted rather than fixed.
Reporting a vulnerability: do not open a public issue. See SECURITY.md for the private disclosure process, scope, and response targets.
Contributions are welcome. Start with CONTRIBUTING.md for setup, the review gate, conventions, and DCO sign-off.
Run pnpm verify before opening a pull request — a green local run means a green CI run.
Read the trust boundary above before proposing anything that moves decision authority into this repository. Evaluating policy locally, persisting customer data in this tier, or falling back to fixtures when a live call fails are the changes this project will not accept, and CONTRIBUTING.md says so up front so you find out from a document rather than from a closed pull request.
Participation is governed by our Code of Conduct. Contributions are accepted under Apache-2.0 per section 5 of the LICENSE; there is no CLA.
Licensed under the Apache License, Version 2.0 — see LICENSE and NOTICE. Copyright 2026 Decionis, Inc.
Apache-2.0 is the default license across Decionis projects: permissive, with an express patent grant.
Third-party components and their licenses are inventoried in ThirdPartyLicenses.md. No dependency imposes a reciprocal obligation on this codebase.
package.json is marked "private": true. That prevents accidental publication to npm — this is a
deployable application, not a library — and does not restrict use of the source under Apache-2.0.
Formerly "Decionis CDI". Renamed to Steward in August 2026, before external adoption. The GitHub URL redirects, but releases
v0.1.0–v0.1.2keepdecionis-cdi-*artifact names — those names are bound into signed provenance attestations and are left as the historical record.
Public and Apache-2.0 licensed. Latest release v0.3.0; pre-1.0 and under active development, so
contracts in domain/ may change without a deprecation period before 1.0.0 — pin exactly if you
integrate against those types. CHANGELOG.md records what has shipped.
Evaluating Steward as a vendor? EvidencePack.md maps the usual security-review questions to the artifact that answers each, and states the gaps as plainly as the strengths. OpenSource.md is the record of how this repository was prepared for public release, including what was found wrong along the way.




