NNO Stack Registry
Documentation for NNO Stack Registry
Date: 2026-03-30
Status: Detailed Design
Parent: System Architecture — Section 3, Layer 0 (NNO Core)
Service: services/stack-registry (renamed from services/marketplace)
Overview
The NNO Stack Registry is the authoritative source of versioned Stack template definitions for the NNO platform. NNO operators publish stack templates here; platform admins and the provisioning service consume them.
What the Stack Registry does:
- Stores NNO-authored stack template definitions as versioned JSON
- Provides a browsable catalogue for platform admins to discover available stacks
- Serves specific stack versions to the provisioning service during stack activation
What the Stack Registry does NOT do:
- No submission pipeline — only NNO operators can publish templates (no external submissions)
- No bundle storage — all feature packages are NNO-built and already in the monorepo; stack definitions are JSON only
- No review process — NNO operators publish directly without a review queue
Who uses it:
- NNO operators — publish and manage stack templates via API
- Platform admins — browse available templates from Zero UI
- Provisioning service — fetches
StackDefinitionJSON at stack activation time
1. D1 Schema
The Stack Registry has its own D1 database, separate from the NNO Registry. The databases retain their legacy marketplace-db names: nno-k3m9p2xw7q-marketplace-db (prod) and nno-k3m9p2xw7q-marketplace-db-stg (stg) — see §6 for details.
-- One row per NNO-authored stack template
CREATE TABLE stack_templates (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE, -- kebab-case slug, e.g. 'saas-starter'
display_name TEXT NOT NULL, -- e.g. 'SaaS Starter Stack'
description TEXT NOT NULL,
icon TEXT, -- Lucide icon name
domain TEXT, -- Business domain grouping, e.g. 'saas', 'analytics'
latest_version TEXT, -- Semver of latest active version
status TEXT NOT NULL DEFAULT 'active', -- 'active' | 'deprecated'
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
-- One row per published version of a stack template
CREATE TABLE stack_versions (
id TEXT PRIMARY KEY,
template_id TEXT NOT NULL REFERENCES stack_templates(id),
version TEXT NOT NULL, -- semver
stack_definition TEXT NOT NULL, -- JSON StackDefinition
nno_sdk_version TEXT NOT NULL, -- minimum @neutrino-io/sdk version required
status TEXT NOT NULL DEFAULT 'active', -- 'active' | 'deprecated'
published_at INTEGER NOT NULL,
created_at INTEGER NOT NULL,
UNIQUE(template_id, version)
);
CREATE INDEX idx_stack_templates_status ON stack_templates(status);
CREATE INDEX idx_stack_versions_template ON stack_versions(template_id);
CREATE INDEX idx_stack_versions_status ON stack_versions(template_id, status);
-- Note: idx_stack_templates_name does NOT exist — name has a UNIQUE constraint which
-- implicitly covers point-lookup queries. No migration has created this index.2. API Surface
All endpoints are served by the Stack Registry Worker via the NNO Gateway. Base path: /api/v1/stacks.
Authentication: Authorization: Bearer \{nno-session-token\} on all requests. NNO operator token required for write endpoints.
Operator auth (Phase 1): Write endpoints check the
x-nno-rolerequest header for"operator"or"owner". This header is expected to be injected by the gateway or IAM session layer. Phase 2 will move to the standardAUTH_API_KEYbearer token model consistent with other services.
Browse (Platform Admins)
GET /api/v1/stacks List active stack templates
GET /api/v1/stacks/:name Template detail + latest version StackDefinition
GET /api/v1/stacks/:name/versions All published versions for a template
GET /api/v1/stacks/:name/:version Specific version StackDefinition (used by provisioning)GET /api/v1/stacks response:
{
"data": [
{
"id": "tpl_saas_starter",
"name": "saas-starter",
"displayName": "SaaS Starter Stack",
"description": "Complete SaaS starter with billing, settings, and analytics.",
"icon": "Layers",
"domain": "saas",
"latestVersion": "1.0.0",
"status": "active",
"createdAt": "2026-02-28T10:00:00Z"
}
],
"total": 1
}GET /api/v1/stacks/:name/:version response (consumed by provisioning service):
{
"id": "tpl_saas_starter",
"name": "saas-starter",
"version": "1.0.0",
"stackDefinition": {
"id": "saas-starter",
"version": "1.0.0",
"displayName": "SaaS Starter Stack",
"description": "Complete SaaS starter with billing, settings, and analytics.",
"icon": "Layers",
"features": [
{ "featureId": "billing", "required": true },
{ "featureId": "settings", "required": true },
{ "featureId": "analytics", "required": false }
],
"resources": {
"sharedD1": true,
"sharedKV": true,
"minimumPlan": "growth"
},
"permissions": ["saas-starter:access"]
},
"nnoSdkVersion": "1.0.0",
"publishedAt": "2026-02-28T10:00:00Z"
}Manage (NNO Operators Only)
POST /api/v1/stacks Create a new template
POST /api/v1/stacks/:name/versions Publish a new version
PATCH /api/v1/stacks/:name Update display_name, description, icon, status
DELETE /api/v1/stacks/:name/:version Deprecate a specific versionPOST /api/v1/stacks request:
{
"name": "saas-starter",
"displayName": "SaaS Starter Stack",
"description": "Complete SaaS starter with billing, settings, and analytics.",
"icon": "Layers",
"domain": "saas"
}POST /api/v1/stacks/:name/versions request:
{
"version": "1.0.0",
"stackDefinition": { ... }, // Full StackDefinition object
"nnoSdkVersion": "1.0.0"
}3. Versioning Policy
Semver Rules
| Bump | Allowed changes |
|---|---|
| Patch (1.0.x) | Bug fixes, description/icon updates, optional feature list changes |
| Minor (1.x.0) | New optional features added, new optional resource requirements |
| Major (x.0.0) | Required features added/removed, resource requirements changed, id renamed |
Major version bumps indicate breaking changes that require platform admins to explicitly choose to upgrade.
Deprecation
Deprecated templates remain activatable — platforms that already have the template active are unaffected. Deprecated versions show a warning badge in Zero UI to encourage migration.
NNO operators mark a version or entire template as deprecated via PATCH /api/v1/stacks/:name (set status: 'deprecated').
latest_version Tracking
When a new version is published, the Stack Registry automatically updates stack_templates.latest_version to the highest semver among active versions. GET /api/v1/stacks/:name always returns the latest_version StackDefinition.
4. Integration with Provisioning Service
When a PROVISION_STACK job runs (see provisioning.md §2.3), step 1 fetches the StackDefinition:
PROVISION_STACK step 1: resolve_stack_definition
→ For registry template:
GET /api/v1/stacks/{name}/{version} from stack-registry
Validates all featureIds exist in the NNO feature catalogue
→ For local stack (is_local: true):
StackDefinition is inline in the job payload
Stack Registry is not calledThe provisioning service uses the StackDefinition.features[] array to determine which features to activate and StackDefinition.resources to determine what CF primitives to create.
5. Built-in Stack Templates
NNO ships with the following first-party stack templates:
| Name | Features | Resources | Min Plan |
|---|---|---|---|
saas-starter | billing, settings, analytics (optional) | sharedD1, sharedKV | growth |
analytics-pro | analytics, data-export, dashboards (optional) | sharedD1, sharedR2, sharedKV | growth |
operator-suite | zero | sharedD1 | starter |
These are published to the Stack Registry by NNO operators and are available to all platforms on the appropriate tier.
6. Wrangler Configuration
# services/stack-registry/wrangler.toml
# Default (no --env flag) = production
name = "nno-k3m9p2xw7q-stack-registry"
main = "src/index.ts"
compatibility_date = "2024-09-13"
compatibility_flags = ["nodejs_compat"]
# PRODUCTION (default)
[[d1_databases]]
binding = "DB"
database_name = "nno-k3m9p2xw7q-marketplace-db"
database_id = "33a6d03d-c191-4c18-a53d-5888be08ac73"
migrations_dir = "migrations"
[vars]
SERVICE_NAME = "stack-registry"
ENVIRONMENT = "prod"
[[routes]]
pattern = "stack-registry.svc.nno.app"
custom_domain = true
# STG ENVIRONMENT
[env.stg]
name = "nno-k3m9p2xw7q-stack-registry-stg"
[[env.stg.d1_databases]]
binding = "DB"
database_name = "nno-k3m9p2xw7q-marketplace-db-stg"
database_id = "543fa5be-bf3e-4917-90b9-4c586d24147c"
migrations_dir = "migrations"
[env.stg.vars]
SERVICE_NAME = "stack-registry"
ENVIRONMENT = "stg"
[[env.stg.routes]]
pattern = "stack-registry.svc.stg.nno.app"
custom_domain = trueLegacy D1 database names: The D1 databases retain their original
marketplace-dbnames from when this service wasservices/marketplace. The worker and worker name have been updated tostack-registry, but the underlying D1 database resources have not been recreated — renaming a D1 database requires migrating data and updating thedatabase_id. The legacy names are the source of truth; do not usestack-registry-dbnames.
No
local/devenvironments: Unlike some other NNO services, stack-registry only defines the default (prod) environment andenv.stg. There is no wrangler-managedlocalordevenvironment.
No
NNO_METRICSbinding: stack-registry does not have a metrics instrumentation binding (noNNO_METRICSKV or Analytics Engine binding in wrangler.toml). Unlike gateway or IAM, metrics are not wired in this service.
Note: R2 binding has been removed. Stack definitions are JSON stored in D1 — no bundle storage required.
Secrets
| Secret | Description |
|---|---|
AUTH_API_KEY | Shared secret for service-to-service authentication |
CORS_ORIGINS | Comma-separated allowed origins |
NNO_REGISTRY_URL | Registry Worker URL |
NNO_INTERNAL_API_KEY | Service-to-service auth with Registry |
Status: Detailed design — Stack Registry replaces Marketplace 2026-02-28
Implementation target: services/stack-registry/ (renamed from services/marketplace/)
Related: System Architecture · Stacks · Provisioning · Registry