Status: implemented, 24 September 2026. Each workstream landed as its own pull request off
master: D0 #77, D1 #78 with its CI fix #81, D2 #79, D3 #80, D4 this document's follow-up. The
first edge image is published, attested and verified from a consumer's machine. The commercial
boundary is recorded as a commitment in OpenCore.md. Still with the maintainer:
the Docker Hub secrets and variable for the mirror, and the next release tag, which publishes the
versioned image for real.
Steward already ships as a source tree, a signed release tarball, and a public demo. What it does
not ship as is the thing most teams reach for first: a container they can pull. The sibling
repository, AgentSafe, solved this well and the plan copies it rather than reinventing it: one
multi-architecture image built by the release workflow, pushed to GHCR with BuildKit's SBOM and
provenance attached, its digest attested with the workflow's keyless identity, and the same
manifest copied by digest to Docker Hub so one digest names a release on both registries. On top
of that sit two things AgentSafe also has and NGINX does not: a machine-readable llms.txt so an
agent evaluating Steward finds the right facts without scraping, and a written open-core boundary.
The NGINX model is the frame for the commercial question: the open-source server is complete and
free, the paid product is a subscription you connect it to, and the switch is a credential, never a
crippled build. For Steward that switch is live mode against a Decionis tenant, and the moment
commerce enters is the first review that is meant to execute rather than only be recorded.
| Concern | AgentSafe | Reused here |
|---|---|---|
| Build | docker buildx build --platform linux/amd64,linux/arm64 --sbom=true --provenance=mode=max --push, in the release job, once |
Yes, verbatim shape |
| Registry of record | ghcr.io/decionis/agentsafe, pushed with the workflow token |
ghcr.io/decionis/steward |
| Tags | <version> immutable; <major>.<minor>, <major>, latest for a stable release |
Same, plus edge from master |
| Attestation | actions/attest on the manifest digest, push-to-registry: true; gh attestation verify oci://… documented |
Same |
| Docker Hub | Separate dockerhub.yml: copies the attested GHCR manifest by digest, refuses a digest mismatch, attests the Hub name, publishes overview |
Same, gated on DOCKERHUB_PUBLISH_ENABLED |
| Base images | Pinned by digest, pulled from public.ecr.aws to dodge Hub rate limits; distroless non-root runtime under Node's permission model |
Digest pins now; distroless is a later step |
| Build stages | --platform=$BUILDPLATFORM for install and build, per-platform only for the runtime stage |
Yes |
| Discovery | llms.txt and llms-full.txt at the repo root, scripts/CheckDiscovery.mjs, weekly discovery.yml |
Yes, and served by the app at /llms.txt |
| Open core | OPEN-CORE.md: what is open, what is operated, the seam, five commitments |
OpenCore.md, same structure |
| Not reused | Homebrew, .deb/.rpm, single executable, Helm chart, hosted listener |
Steward is a web tier; see "Later" |
Docker Hub already carries four Decionis repositories (agentsafe at 1.7K pulls), so
docker.io/decionis/steward sits in an established namespace.
NGINX is the reference for "complete open-source product, commercial layer you connect to".
| NGINX | Steward |
|---|---|
| Open-source nginx is the whole server, BSD-licensed, no feature flags | Steward is the whole operator tier, Apache-2.0, no feature flags, demo mode complete forever |
| Distributed as source tarballs, signed distro packages from nginx.org, and a Docker image | Source, a signed release tarball with SBOM and provenance, and now a signed image on GHCR and Docker Hub |
stable and mainline channels; versioned image tags plus moving aliases |
<version> immutable, <major>.<minor>, <major>, latest for releases; edge for master |
| NGINX Plus: same core plus operated value (active health checks, API, dashboard, support) | The Decionis platform: policy evaluation, execution grants, Decision Dossiers, the ledger, Presence, support |
| Plus is a subscription: a certificate and key for a private repository and a JWT license file | A Decionis tenant: sign-in handoff, an organisation scope, a plan. No file in the image changes |
| Plus reports usage to F5 and has a grace period when it cannot | Steward reports nothing; entitlement and metering live in the platform, which sees every review it records |
| A customer builds the Plus image from the private repository | There is no "Plus image". The same image serves free and paid tenants; what differs is what the platform returns |
| Advisories, CVE process, signed releases | SECURITY.md, private reporting, attested releases; unchanged |
The one place the mapping is deliberately not followed: NGINX Plus is a different binary with more features. Steward will not have one. A separate paid build of the operator tier would put policy or entitlement logic into the repository the trust boundary exists to keep it out of. Paid value is operated, and the image is the same bytes for everyone.
| Channel | Name | What it is | Verify with |
|---|---|---|---|
| Container, primary | ghcr.io/decionis/steward:<version> |
Multi-arch image, BuildKit SBOM and provenance, attested | gh attestation verify oci://ghcr.io/decionis/steward:<v> --repo decionis/steward |
| Container, mirror | docker.io/decionis/steward:<version> |
The same manifest, copied by digest after each release | Same command against the Hub name; the two inspect digests match |
| Release tarball | GitHub release v<version> |
Standalone server, CycloneDX SBOM, Sigstore bundle | gh attestation verify decionis-steward-<v>.tar.gz --repo decionis/steward |
| Source | git clone |
pnpm install && pnpm dev, demo mode, no credentials |
pnpm verify |
| Hosted demo | https://decionis-steward.vercel.app | Demo mode, deterministic fixtures, not indexed | /api/health, /api/steward/portfolio reports DEMO |
| Machine discovery | /llms.txt on every deployment; llms.txt in the repo |
What Steward is, what it is not, where the facts are | discovery.yml, weekly |
- Base:
node:22-alpine, pinned by digest, pulled from the AWS public mirror of the Docker library so an unauthenticated CI runner is not rate-limited by Docker Hub. Distroless is the next step once the health check is rewritten to run undernodewithout a shell (see "Later"). - Runs as the unprivileged
nodeuser. No shell is needed at run time;wgetstays only for the health check until distroless. - Defaults to demo mode.
STEWARD_DATA_MODE=demo, no credential, nothing persisted. Live mode is-e STEWARD_DATA_MODE=live -e DECIONIS_API_BASE_URL=…; the process refuses to start in live mode without a base URL rather than degrading. - Ships
public/, which the current Dockerfile does not copy;/llms.txtlives there. - OCI labels:
source,title,description,licenses,version,revision,created, so registries and scanners describe the image correctly. - Health:
HEALTHCHECKagainst/api/health, which the session middleware exempts. - Platforms:
linux/amd64andlinux/arm64. Install and build stages run on the builder's platform; only the runtime stage is per platform. - Tags:
<version>(immutable, the one to run in production),<major>.<minor>,<major>,latest(a stable release), andedge(every push tomaster, for people who want tomorrow's build today and accept that it moves). A prerelease version gets only its exact tag. - Provenance: BuildKit attaches an SBOM and SLSA provenance to the manifest; the workflow then
attests the manifest digest with
actions/attestand pushes the attestation to the registry. Verification is oneghcommand, the same as for the tarball. - Smoke test before publish: the built image is started,
/api/healthmust answer,/api/steward/portfoliomust reportdataStatus: DEMO,/llms.txtmust serve with the right first line, and every security header must be present. A release whose image cannot start is not a release.
llms.txt is the concise map and llms-full.txt its superset that embeds it verbatim; both follow
the llms.txt shape: an H1, a blockquote summary, H2 link lists with a note
per link, and an Optional section for what an agent can skip. Steward differs from AgentSafe in
one way: Steward is a running web application, so the files are served by it at /llms.txt and
/llms-full.txt in every data mode, from the container, the tarball and the Vercel demo alike.
The canonical copies live in public/; byte-identical copies sit at the repository root so GitHub
readers and raw URLs find them, and the discovery gate fails if the pairs drift.
Two exemptions make that work in live mode. The session middleware lets the two paths through
without a Decionis session, as it does /api/health and /sign-in, so a prospective adopter's
agent can read them before anyone signs in. And the X-Robots-Tag: noindex the app sets on every
response, correct for an operator console, is overridden to all on those two paths only: the
console stays out of search engines, the description of it does not.
The discovery rules carried over from AgentSafe: verify before claiming, absolute public links, no figure without a checked-in measurement, no surface (OpenAPI, MCP, security.txt) unless it exists, and a script that checks the pairs and every link, weekly, because links rot without anyone touching the repository.
Stated in full in OpenCore.md. The short version:
| Free, forever, in this repository | Paid, operated by Decionis, not in this repository |
|---|---|
| The whole operator tier: dashboard, queue, account detail, the review flow, every screen | Policy evaluation and the customer_ops policy pack, with versions |
| Demo mode: deterministic fixtures, no account, no credential, nothing persisted | Execution grants: the review that actually changes a processing limit |
| Live mode against a Decionis workspace in shadow: real evidence, real recommendations, reviews recorded, dossiers read | Decision Dossier retention, the audit ledger, export |
The image, the tarball, the source, the contracts in domain/, the tests that prove the boundary |
Connectors at scale, identity resolution, SSO and SCIM, Presence-verified approvals |
| Machine discovery, documentation, the threat model, the evidence pack | Support, SLAs, hosted Steward for tenants who do not want to run the container |
The activation point. Commerce enters the flow at exactly one place, and it is not in this repository: the first review that is meant to execute. Everything up to that line is free, because everything up to it is Steward doing what Steward does, which is render evidence and forward a review. The caption on every review control already says it: "Records a review only; no downstream limit is changed." That sentence is the free tier. The paid tier is the platform making the change.
The flow an adopter walks, and where each step sits:
docker run ghcr.io/decionis/steward— free, no account, demo evidence. Five minutes.- "Connect your platform" in the shell — a link to the Decionis sign-in handoff. Still free: it
provisions a workspace that decides in shadow, the same way AgentSafe's
login --provisiondoes. - Live mode in shadow — real accounts, real evidence, reviews recorded, dossiers readable. Free. This is where an adopter learns whether the recommendations are worth acting on.
- Enforcement — the tenant moves to a plan; approved reviews execute under grants. Paid. Steward
renders the same screens; the platform returns
COMPLETEDdecisions with outcomes.
What Steward commits never to do, so the boundary stays honest: no license key, no plan check, no entitlement read, no telemetry, no phone-home, no feature that only lights up for a paying tenant because of code in this repository. If a screen shows something only paying tenants have, it is because the platform returned it.
Each is one pull request off master, passes pnpm verify, is signed off, and adds a changelog
line.
docs/Distribution.md, OpenCore.md, a README pointer. No code.
Dockerfile: digest-pinned base from the AWS public mirror,--platform=$BUILDPLATFORMon the install and build stages,COPY public/, OCI labels via build args, unchanged runtime user and health check..github/workflows/image.yml: on av*tag, and by dispatch withdry_run, verify the tree, build multi-arch with SBOM and provenance, smoke-test thelinux/amd64image locally, push to GHCR with the tag set above, attest the digest, then calldockerhub.ymlwhenDOCKERHUB_PUBLISH_ENABLEDistrue. On every push tomaster, the same build pushes onlyedge..github/workflows/dockerhub.yml: AgentSafe's copy-by-digest workflow, adapted to this repository's names, publishingpackaging/dockerhub/README.mdas the Hub overview.packaging/dockerhub/README.mdanddocs/Docker.md: how to run it, demo and live, how to verify it, what the tags mean, why to mirror before a cluster pulls..github/dependabot.yml: thedockerecosystem, monthly, so the base image digest moves on purpose..dockerignore: keep tests and docs images out of the context.
Gate: a dry-run dispatch builds, smoke-tests and attests without publishing. Then the next release tag publishes for real.
public/llms.txt,public/llms-full.txt; root copies;scripts/CheckDiscovery.mjs(pairs identical, structure valid, local links exist, public links resolve on an allowlist of hosts);pnpm discoveryinverify;.github/workflows/discovery.ymlweekly and on pull requests.middleware.ts: the two paths join the unauthenticated set;middleware.test.tsproves it.next.config.ts: the two paths getX-Robots-Tag: all; a test asserts the override.docs/Discovery.md: the rules, in this repository's words.
Depends on D1's COPY public/ for the container to serve the files; the tarball and Vercel
already ship public/.
- The app shell's demo badge gains "Connect your platform", a link to the sign-in handoff.
- The sign-in page says what is free and what is paid, in two sentences, with a link to
OpenCore.md, so the line is stated where an adopter meets it. - No entitlement logic, no plan check. Presentation only.
- README: an "Install" section in AgentSafe's shape (Docker, tarball, source, hosted demo), the image verification command, and the free/paid line.
EvidencePack.md: rows for image provenance, base-image pinning, and the no-telemetry boundary restated for the container.ThreatModel.md: the container as a deployment surface; what the image does and does not hold.
| Stage | Work | Gate |
|---|---|---|
| 0. Record | D0 | Merged; the boundary is written before any channel that exposes it |
| 1. Ship the image | D1 | Dry-run dispatch green: built, smoke-tested, attested. Then v0.2.1 or the next tag publishes |
| 2. Be found | D2, D3 | /llms.txt serves from the container and the demo; the shell links to the handoff |
| 3. Say so | D4 | README and evidence pack describe what exists, verified against the registries |
| 4. Mirror | Docker Hub | Secrets set, DOCKERHUB_PUBLISH_ENABLED=true, the Hub digest equals the GHCR digest |
D1 and D2 can run in parallel as branches; D2's container path only works once D1 has merged.
| Input | Needed for |
|---|---|
Repository secrets DOCKERHUB_USERNAME, DOCKERHUB_TOKEN |
The Docker Hub mirror. The same account that owns decionis/agentsafe on the Hub |
Repository variable DOCKERHUB_PUBLISH_ENABLED=true |
Turning the mirror on; absent or false, GHCR alone publishes |
| GHCR package visibility | The first push creates the package; confirm it is public and linked to this repository |
| A decision on distroless | Hardening; needs the health check rewritten to run under node without a shell |
A decision on edge |
Whether a moving tag from master is wanted at all; the plan says yes, latest stays releases |
- Distroless runtime under Node's permission model, as AgentSafe runs. Worth doing; needs the health check and a read-only root filesystem proven first.
- Helm chart on
oci://ghcr.io/decionis/charts/steward. A web tier with one Deployment and one Service is small; do it when a cluster asks. - Hosted Steward for tenants who do not want to run the container: the same image behind a Decionis-operated listener. That is a platform product, not a repository change.
- A self-hosted control plane with an NGINX Plus-style license file and usage reporting. If Decionis ever ships one, Steward does not change; the platform does.
- Homebrew,
.deb,.rpm, a single executable: right for a gateway, wrong for a web tier.