diff --git a/docs/site/_components/breadcrumb.vto b/docs/site/_components/breadcrumb.vto index 8aad516ac..1d0b2de21 100644 --- a/docs/site/_components/breadcrumb.vto +++ b/docs/site/_components/breadcrumb.vto @@ -1,38 +1,52 @@ -{{# breadcrumb.vto — derives a Home / Section / Page trail from the current page - url matched against navSections. Both pageUrl and the candidate hrefs are - base-prefixed via the `url` filter so they compare on equal footing (same - rule as the sidebar active-highlight). On the home page the trail collapses - to just "Home" (rendered as plain text, no link). All colors key off the - existing fresh-ui --ns-* tokens. #}} +{{# breadcrumb.vto — lane-prefixed trail derived from nav.breadcrumb(url) + (root→page order), prefixed with the owning navLanes lane label so + /web-layer/query/ reads Home / Build / Web Layer / Data loading & cache. + Node/page urls are SOURCE urls (source==source); emitted hrefs are + base-prefixed via the `url` filter. Pages outside the nav tree (nav_hide, + redirect shims, the how-to catalog) fall back to Home / . #}} {{ set homeUrl = "/" |> url }} -{{ set pageUrl = url |> url }} -{{# Build the trail: always start with Home. Then, unless we ARE home, find the - nav section + item this page belongs to and append section label + page. #}} -{{ set crumbSection = null }} -{{ set crumbItem = null }} -{{ for section of navSections }} - {{ for item of section.items }} - {{ set itemUrl = item.href |> url }} - {{ if itemUrl !== homeUrl && pageUrl.startsWith(itemUrl) }} - {{ set crumbSection = section }} - {{ set crumbItem = item }} - {{ /if }} - {{ /for }} +{{ set laneLabel = null }} +{{ for lane of navLanes }} + {{ if lane.kind === "flat" }} + {{ for item of lane.items }} + {{ if url === item.href }}{{ set laneLabel = lane.label }}{{ /if }} + {{ /for }} + {{ else }} + {{ for root of lane.roots }} + {{ if url.startsWith(root) }}{{ set laneLabel = lane.label }}{{ /if }} + {{ /for }} + {{ /if }} {{ /for }} +{{ set crumbs = nav.breadcrumb(url, navQuery, navSort) }} + <nav class="ns-breadcrumb" aria-label="Breadcrumb"> <ol class="ns-breadcrumb__list"> - {{ if pageUrl === homeUrl }} + {{ if url === "/" }} <li class="ns-breadcrumb__item" aria-current="page">Home</li> {{ else }} <li class="ns-breadcrumb__item"><a href="{{ homeUrl }}">Home</a></li> - {{ if crumbSection }} + {{ if laneLabel }} + <li class="ns-breadcrumb__sep" aria-hidden="true">/</li> + <li class="ns-breadcrumb__item">{{ laneLabel }}</li> + {{ /if }} + {{ set rendered = 0 }} + {{ for crumb of crumbs }} + {{ if crumb.data && crumb.data.url !== "/" }} + <li class="ns-breadcrumb__sep" aria-hidden="true">/</li> + {{ if crumb.data.url === url }} + <li class="ns-breadcrumb__item" aria-current="page">{{ crumb.data.nav_title || crumb.data.title }}</li> + {{ else }} + <li class="ns-breadcrumb__item"><a href="{{ crumb.data.url |> url }}">{{ crumb.data.nav_title || crumb.data.title }}</a></li> + {{ /if }} + {{ set rendered = rendered + 1 }} + {{ /if }} + {{ /for }} + {{ if rendered === 0 }} <li class="ns-breadcrumb__sep" aria-hidden="true">/</li> - <li class="ns-breadcrumb__item">{{ crumbSection.label }}</li> + <li class="ns-breadcrumb__item" aria-current="page">{{ title }}</li> {{ /if }} - <li class="ns-breadcrumb__sep" aria-hidden="true">/</li> - <li class="ns-breadcrumb__item" aria-current="page">{{ if crumbItem }}{{ crumbItem.label }}{{ else }}{{ title }}{{ /if }}</li> {{ /if }} </ol> </nav> diff --git a/docs/site/_components/nextPrev.vto b/docs/site/_components/nextPrev.vto index 1befe8aad..483888b98 100644 --- a/docs/site/_components/nextPrev.vto +++ b/docs/site/_components/nextPrev.vto @@ -1,20 +1,38 @@ -{{# nextPrev.vto — prev/next pager rendered at the foot of the article. Pages - opt in by setting `prev` and/or `next` front-matter vars, each shaped - { label, href }. If neither is present nothing renders. Hrefs are authored - root-relative (e.g. /quickstart/) and base-prefixed here via the `url` - filter. Colors key off the existing fresh-ui --ns-* tokens. #}} -{{ if prev || next }} +{{# nextPrev.vto — prev/next pager rendered at the foot of the article. + Two sources, front matter first: + 1. Pages may set `prev`/`next` front-matter vars ({ label, href }) — the + tutorial tracks keep per-chapter editorial control this way. + 2. Reference pages (32 generated units — unmaintainable by hand) auto-derive + neighbours from the nav tree via nav.nextPage/previousPage(url, query, + sort) — the verified v2.5.4 signature has NO basePath argument; the + `url^=/reference/` query term scopes the walk to the reference lane. + Hrefs are source urls, base-prefixed via the `url` filter. #}} +{{ set prevLink = null }} +{{ set nextLink = null }} +{{ if prev }}{{ set prevLink = { label: prev.label, href: prev.href } }}{{ /if }} +{{ if next }}{{ set nextLink = { label: next.label, href: next.href } }}{{ /if }} +{{ if url && url.startsWith("/reference/") }} + {{ if !prevLink }} + {{ set autoPrev = nav.previousPage(url, "url^=/reference/ " + navQuery, navSort) }} + {{ if autoPrev }}{{ set prevLink = { label: autoPrev.nav_title || autoPrev.title, href: autoPrev.url } }}{{ /if }} + {{ /if }} + {{ if !nextLink }} + {{ set autoNext = nav.nextPage(url, "url^=/reference/ " + navQuery, navSort) }} + {{ if autoNext }}{{ set nextLink = { label: autoNext.nav_title || autoNext.title, href: autoNext.url } }}{{ /if }} + {{ /if }} +{{ /if }} +{{ if prevLink || nextLink }} <nav class="ns-nextprev" aria-label="Pagination"> - {{ if prev }} - <a class="ns-nextprev__link ns-nextprev__link--prev" href="{{ prev.href |> url }}" rel="prev"> + {{ if prevLink }} + <a class="ns-nextprev__link ns-nextprev__link--prev" href="{{ prevLink.href |> url }}" rel="prev"> <span class="ns-nextprev__dir" aria-hidden="true">← Previous</span> - <span class="ns-nextprev__label">{{ prev.label }}</span> + <span class="ns-nextprev__label">{{ prevLink.label }}</span> </a> {{ /if }} - {{ if next }} - <a class="ns-nextprev__link ns-nextprev__link--next" href="{{ next.href |> url }}" rel="next"> + {{ if nextLink }} + <a class="ns-nextprev__link ns-nextprev__link--next" href="{{ nextLink.href |> url }}" rel="next"> <span class="ns-nextprev__dir" aria-hidden="true">Next →</span> - <span class="ns-nextprev__label">{{ next.label }}</span> + <span class="ns-nextprev__label">{{ nextLink.label }}</span> </a> {{ /if }} </nav> diff --git a/docs/site/_config.ts b/docs/site/_config.ts index 6330ad473..828746aba 100644 --- a/docs/site/_config.ts +++ b/docs/site/_config.ts @@ -1,5 +1,7 @@ import lume from "lume/mod.ts"; import basePath from "lume/plugins/base_path.ts"; +import nav from "lume/plugins/nav.ts"; +import redirects from "lume/plugins/redirects.ts"; import pagefind from "lume/plugins/pagefind.ts"; import codeHighlight from "lume/plugins/code_highlight.ts"; import anchor from "npm:markdown-it-anchor@9.2.0"; @@ -89,6 +91,15 @@ site.filter( // resolves without an in-template import). Throws on unknown key inside the build. site.data("resolveXref", resolveXref); +// Multilevel sidebar (docs-v5 IA): nav.menu()/nav.breadcrumb() derive the tree +// from page URLs; the sidebar lanes are curated in `_data.ts` `navLanes`. +site.use(nav()); + +// Moved-URL shims: pages declare `oldUrl` front matter and the plugin emits a +// meta-refresh page at the old URL (GitHub Pages cannot 301). Registered before +// base_path so the generated pages get base-prefixed hrefs like everything else. +site.use(redirects()); + // Syntax highlighting for fenced code blocks (TD-6). site.use(codeHighlight({ languages: { diff --git a/docs/site/_data.ts b/docs/site/_data.ts index 40237b26c..0ae52ac29 100644 --- a/docs/site/_data.ts +++ b/docs/site/_data.ts @@ -1,253 +1,115 @@ /** * Site-wide data shared by every page (Lume merges `_data.*` into page data). * - * `navSections` drives the SidebarShell navigation rendered in - * `_includes/layouts/base.vto`. Docs-v4 uses the locked Capability-Hub IA: - * shallow START entries, eight product-area pillars with uniform - * Overview/Quickstart/How-To/Reference leaves, then Tutorials, Explanation, - * and a thin global Reference index. + * `navLanes` is the docs-v5 sidebar spine (see `_plan/10-nav-ia-redesign.md`): + * five curated lanes — Start · Learn · Build · Reference · Concepts — rendered + * in `_includes/layouts/base.vto`. Lane order, the Start links, and the + * nine-pillar Build sequence are the ONLY hand-maintained nav data; everything + * below a lane is folder-derived via the Lume nav plugin (`nav.menu()`), sorted + * by `order` front matter with `basename` as the tiebreak and filtered by + * `nav_hide!=true isRedirect!=true`. * - * Reference URLs stay stable. The four `*-core` internal packages remain folded - * inside reference prose unless they already have generated reference units. + * Reference URLs stay stable and are folder-derived from `reference/` — there + * is no hand-maintained unit list anymore (the xref `ref:` keys live in + * `_data/xref.ts`). */ import cliPackageJson from "../../packages/cli/deno.json" with { type: "json" }; +/** + * Default nav sort weight, cascaded to every page (front matter overrides). + * A defined numeric `order` everywhere keeps the `"order url"` nav sort + * comparator consistent — with undefined values the multi-field sort is + * unstable and lanes render in load order (observed on /reference/). + * Pages without an explicit order (reference units, tutorial chapters) sort + * by their url tiebreak alone (index pages share basename "index", so url is the reliable per-segment tiebreak). + */ +export const order = 0; + /** Current aligned NetScript release train version. */ export const releaseVersion: string = cliPackageJson.version; /** Exact JSR suffix for current NetScript release train examples. */ export const releaseSpecifier: string = `@${releaseVersion}`; -export interface NavItem { +export interface NavLaneLink { href: string; label: string; - icon: string; } -export interface NavSection { +export interface NavLane { label: string; - items: NavItem[]; + /** One-line intent gloss rendered under the lane header. */ + subtitle: string; + kind: "flat" | "menu"; + /** kind "flat": curated links rendered as-is (Start lane). */ + items?: NavLaneLink[]; + /** kind "menu": nav.menu() roots rendered as subtrees, in curated order. */ + roots?: string[]; + /** + * kind "menu" only: render the root as a plain link followed by its children + * at lane level (Learn/Reference/Concepts) instead of one collapsible branch + * per root (Build, where each pillar root IS the branch). + */ + expandRoot?: boolean; } -/** - * Reference units (33). The href is the section-root URL; `url` Lume filter - * applies the /netscript/ base path at render time. - */ -const referenceUnits: NavItem[] = [ - { href: "/reference/ai/", label: "ai", icon: "A" }, - { href: "/reference/ai/skills/", label: "ai skills", icon: "A" }, - { href: "/reference/auth/", label: "auth", icon: "A" }, - { href: "/reference/auth-better-auth/", label: "auth-better-auth", icon: "A" }, - { href: "/reference/auth-kv-oauth/", label: "auth-kv-oauth", icon: "A" }, - { href: "/reference/auth-workos/", label: "auth-workos", icon: "A" }, - { href: "/reference/aspire/", label: "aspire", icon: "A" }, - { href: "/reference/cli/", label: "cli", icon: "C" }, - { href: "/reference/cli/commands/", label: "cli commands", icon: "C" }, - { href: "/reference/config/", label: "config", icon: "C" }, - { href: "/reference/contracts/", label: "contracts", icon: "C" }, - { href: "/reference/cron/", label: "cron", icon: "C" }, - { href: "/reference/database/", label: "database", icon: "D" }, - { href: "/reference/fresh/", label: "fresh", icon: "F" }, - { href: "/reference/fresh-ui/", label: "fresh-ui", icon: "F" }, - { href: "/reference/kv/", label: "kv", icon: "K" }, - { href: "/reference/logger/", label: "logger", icon: "L" }, - { href: "/reference/plugin/", label: "plugin", icon: "P" }, - { href: "/reference/plugin-ai/", label: "plugin-ai", icon: "P" }, - { href: "/reference/plugin-ai-core/", label: "plugin-ai-core", icon: "P" }, - { href: "/reference/plugin-auth/", label: "plugin-auth", icon: "P" }, - { href: "/reference/plugin-auth-core/", label: "plugin-auth-core", icon: "P" }, - { href: "/reference/prisma-adapter-mysql/", label: "prisma-adapter-mysql", icon: "P" }, - { href: "/reference/queue/", label: "queue", icon: "Q" }, - { href: "/reference/runtime-config/", label: "runtime-config", icon: "R" }, - { href: "/reference/sagas/", label: "sagas", icon: "S" }, - { href: "/reference/sdk/", label: "sdk", icon: "S" }, - { href: "/reference/service/", label: "service", icon: "S" }, - { href: "/reference/streams/", label: "streams", icon: "S" }, - { href: "/reference/telemetry/", label: "telemetry", icon: "T" }, - { href: "/reference/triggers/", label: "triggers", icon: "T" }, - { href: "/reference/watchers/", label: "watchers", icon: "W" }, - { href: "/reference/workers/", label: "workers", icon: "W" }, -]; -export const navSections: NavSection[] = [ +export const navLanes: NavLane[] = [ { label: "Start", + subtitle: "Get running in minutes", + kind: "flat", items: [ - { href: "/why/", label: "Why NetScript", icon: "?" }, - { href: "/quickstart/", label: "Quickstart", icon: ">" }, - { href: "/concepts/", label: "Architecture overview", icon: "A" }, - { href: "/glossary/", label: "Glossary", icon: "G" }, + { href: "/why/", label: "Why NetScript" }, + { href: "/quickstart/", label: "Quickstart" }, + { href: "/quickstart/aspire/", label: "Aspire quickstart" }, + { href: "/concepts/", label: "Core concepts" }, + { href: "/cli-reference/", label: "CLI reference" }, + { href: "/glossary/", label: "Glossary" }, + { href: "/how-to/", label: "All recipes" }, ], }, { - label: "Web Layer", - items: [ - { href: "/web-layer/", label: "Overview & Concepts", icon: "O" }, - { href: "/tutorials/live-dashboard/", label: "Quickstart: live dashboard", icon: "Q" }, - { href: "/how-to/customize-fresh-ui/", label: "How-To: customize Fresh UI", icon: "H" }, - { href: "/how-to/build-a-desktop-frontend/", label: "How-To: desktop frontend", icon: "H" }, - { href: "/how-to/build-a-server-validated-form/", label: "How-To: server-validated form", icon: "H" }, - { href: "/web-layer/server/", label: "API: server & islands", icon: "R" }, - { href: "/web-layer/builders/", label: "API: pages & builders", icon: "R" }, - { href: "/web-layer/route/", label: "API: route contracts", icon: "R" }, - { href: "/web-layer/query/", label: "API: data loading & cache", icon: "R" }, - { href: "/web-layer/form/", label: "API: forms & validation", icon: "R" }, - { href: "/web-layer/defer-streaming-ui/", label: "API: defer & streaming UI", icon: "R" }, - { href: "/web-layer/interactive/", label: "API: interactive islands", icon: "R" }, - { href: "/web-layer/vite/", label: "API: build & Vite", icon: "R" }, - { href: "/web-layer/error/", label: "API: errors & diagnostics", icon: "R" }, - { href: "/web-layer/testing/", label: "API: testing pages & islands", icon: "R" }, - { href: "/web-layer/examples/", label: "Examples / sandbox", icon: "E" }, - { href: "/reference/fresh/", label: "Reference: fresh", icon: "R" }, - { href: "/reference/fresh-ui/", label: "Reference: fresh-ui", icon: "R" }, - ], - }, - { - label: "Services & SDK", - items: [ - { href: "/services-sdk/", label: "Overview & Concepts", icon: "O" }, - { href: "/tutorials/storefront/02-catalog-service/", label: "Quickstart: define a service", icon: "Q" }, - { href: "/how-to/add-a-service/", label: "How-To: add a service", icon: "H" }, - { href: "/how-to/discover-services/", label: "How-To: discover services", icon: "H" }, - { href: "/how-to/expose-openapi-scalar/", label: "How-To: OpenAPI & Scalar", icon: "H" }, - { href: "/reference/service/", label: "Reference: service", icon: "R" }, - { href: "/reference/sdk/", label: "Reference: sdk", icon: "R" }, - { href: "/reference/contracts/", label: "Reference: contracts", icon: "R" }, - ], + label: "Learn", + subtitle: "Build one thing end to end", + kind: "menu", + roots: ["/tutorials/"], + expandRoot: true, }, { - label: "Background Processing", - items: [ - { href: "/background-processing/", label: "Overview & Concepts", icon: "O" }, - { href: "/tutorials/erp-sync/03-polyglot-transform/", label: "Quickstart: polyglot task", icon: "Q" }, - { href: "/how-to/queue-kv-cron/", label: "How-To: queue / KV / cron", icon: "H" }, - { href: "/how-to/choose-a-queue-provider/", label: "How-To: choose a queue provider", icon: "H" }, - { href: "/how-to/tune-worker-runtime/", label: "How-To: tune worker runtime", icon: "H" }, - { href: "/how-to/run-a-polyglot-task/", label: "How-To: run a polyglot task", icon: "H" }, - { href: "/how-to/add-a-task-runtime-adapter/", label: "How-To: add a task adapter", icon: "H" }, - { href: "/how-to/restrict-worker-task-permissions/", label: "How-To: restrict task permissions", icon: "H" }, - { href: "/reference/workers/", label: "Reference: workers", icon: "R" }, - { href: "/reference/queue/", label: "Reference: queue", icon: "R" }, - { href: "/reference/cron/", label: "Reference: cron", icon: "R" }, - { href: "/reference/watchers/", label: "Reference: watchers", icon: "R" }, - ], - }, - { - label: "Durable Workflows", - items: [ - { href: "/durable-workflows/", label: "Overview & Concepts", icon: "O" }, - { href: "/tutorials/storefront/04-checkout-saga/", label: "Quickstart: checkout saga", icon: "Q" }, - { href: "/how-to/build-a-validated-ingestion-queue/", label: "How-To: validated ingestion queue", icon: "H" }, - { href: "/how-to/publish-a-durable-stream/", label: "How-To: publish a durable stream", icon: "H" }, - { href: "/reference/sagas/", label: "Reference: sagas", icon: "R" }, - { href: "/reference/triggers/", label: "Reference: triggers", icon: "R" }, - { href: "/reference/streams/", label: "Reference: streams", icon: "R" }, - ], - }, - { - label: "AI & Agents", - items: [ - { href: "/ai/", label: "Overview & Concepts", icon: "O" }, - { href: "/ai/mcp/", label: "Guide: MCP", icon: "G" }, - { href: "/tutorials/chat/", label: "Quickstart: AI chat", icon: "Q" }, - { href: "/how-to/build-a-durable-chat/", label: "How-To: build a durable chat", icon: "H" }, - { href: "/ai/durable-chat/", label: "Guide: durable chat", icon: "G" }, - { href: "/ai/chat-ui/", label: "Guide: chat UI", icon: "G" }, - { href: "/ai/engine/", label: "Reference: AI engine", icon: "R" }, - { href: "/reference/ai/", label: "Reference: ai", icon: "R" }, - { href: "/reference/ai/skills/", label: "Reference: AI skills", icon: "R" }, - { href: "/reference/plugin-ai/", label: "Reference: plugin-ai", icon: "R" }, - { href: "/reference/plugin-ai-core/", label: "Reference: plugin-ai-core", icon: "R" }, - ], - }, - { - label: "Data & Persistence", - items: [ - { href: "/data-persistence/", label: "Overview & Concepts", icon: "O" }, - { href: "/tutorials/storefront/03-cart-contracts/", label: "Quickstart: data contracts", icon: "Q" }, - { href: "/how-to/database-migration/", label: "How-To: database & migration", icon: "H" }, - { href: "/how-to/use-a-second-database/", label: "How-To: second database", icon: "H" }, - { href: "/reference/database/", label: "Reference: database", icon: "R" }, - { href: "/reference/kv/", label: "Reference: kv", icon: "R" }, - { href: "/reference/prisma-adapter-mysql/", label: "Reference: prisma-adapter-mysql", icon: "R" }, + label: "Build", + subtitle: "Add a capability to your app", + kind: "menu", + roots: [ + "/web-layer/", + "/services-sdk/", + "/background-processing/", + "/durable-workflows/", + "/ai/", + "/data-persistence/", + "/identity-access/", + "/orchestration-runtime/", + "/observability/", ], }, { - label: "Identity & Access", - items: [ - { href: "/identity-access/", label: "Overview & Concepts", icon: "O" }, - { href: "/tutorials/workspace/02-auth/", label: "Quickstart: workspace auth", icon: "Q" }, - { href: "/how-to/add-authentication/", label: "How-To: add authentication", icon: "H" }, - { href: "/identity-access/better-auth-plugins/", label: "How-To: better-auth plugins", icon: "H" }, - { href: "/reference/auth/", label: "Reference: auth", icon: "R" }, - { href: "/reference/auth-better-auth/", label: "Reference: auth-better-auth", icon: "R" }, - { href: "/reference/auth-kv-oauth/", label: "Reference: auth-kv-oauth", icon: "R" }, - { href: "/reference/auth-workos/", label: "Reference: auth-workos", icon: "R" }, - { href: "/reference/plugin-auth/", label: "Reference: plugin-auth", icon: "R" }, - { href: "/reference/plugin-auth-core/", label: "Reference: plugin-auth-core", icon: "R" }, - ], - }, - { - label: "Orchestration & Runtime", - items: [ - { href: "/orchestration-runtime/", label: "Overview & Concepts", icon: "O" }, - { href: "/orchestration-runtime/cli-scaffold/", label: "Guide: CLI & scaffold", icon: "G" }, - { href: "/quickstart/", label: "Quickstart: run the workspace", icon: "Q" }, - { href: "/how-to/deno-lsp-code-intelligence/", label: "How-To: Deno LSP intelligence", icon: "H" }, - { href: "/how-to/deploy-local-aspire/", label: "How-To: deploy locally with Aspire", icon: "H" }, - { href: "/how-to/deploy/", label: "How-To: deploy", icon: "H" }, - { href: "/how-to/deploy-deno-deploy/", label: "How-To: deploy to Deno Deploy", icon: "H" }, - { href: "/how-to/roll-out-runtime-overrides/", label: "How-To: runtime overrides", icon: "H" }, - { href: "/how-to/graceful-shutdown/", label: "How-To: graceful shutdown", icon: "H" }, - { href: "/how-to/add-a-plugin/", label: "How-To: add a plugin", icon: "H" }, - { href: "/how-to/author-a-plugin/", label: "How-To: author a plugin", icon: "H" }, - { href: "/reference/aspire/", label: "Reference: aspire", icon: "R" }, - { href: "/reference/config/", label: "Reference: config", icon: "R" }, - { href: "/reference/runtime-config/", label: "Reference: runtime-config", icon: "R" }, - { href: "/reference/plugin/", label: "Reference: plugin", icon: "R" }, - { href: "/reference/cli/", label: "Reference: cli", icon: "R" }, - { href: "/reference/cli/commands/", label: "Reference: CLI commands", icon: "R" }, - ], - }, - { - label: "Observability", - items: [ - { href: "/observability/", label: "Overview & Concepts", icon: "O" }, - { href: "/concepts/", label: "Quickstart: trace the model", icon: "Q" }, - { href: "/how-to/add-opentelemetry/", label: "How-To: add OpenTelemetry", icon: "H" }, - { href: "/reference/telemetry/", label: "Reference: telemetry", icon: "R" }, - { href: "/reference/logger/", label: "Reference: logger", icon: "R" }, - ], - }, - { - label: "Tutorials", - items: [ - { href: "/tutorials/", label: "Tutorials index", icon: "T" }, - { href: "/tutorials/live-dashboard/", label: "Live dashboard", icon: "T" }, - { href: "/tutorials/chat/", label: "AI Chat", icon: "T" }, - { href: "/tutorials/workspace/", label: "Workspace", icon: "T" }, - { href: "/tutorials/storefront/", label: "Storefront", icon: "T" }, - { href: "/tutorials/erp-sync/", label: "ERP sync", icon: "T" }, - ], - }, - { - label: "Explanation", - items: [ - { href: "/explanation/", label: "Explanation index", icon: "E" }, - { href: "/explanation/architecture/", label: "Architecture", icon: "E" }, - { href: "/explanation/contracts/", label: "Contracts & type flow", icon: "E" }, - { href: "/explanation/plugin-system/", label: "The plugin system", icon: "E" }, - { href: "/explanation/auth-model/", label: "Auth model", icon: "E" }, - { href: "/explanation/durability-model/", label: "Durability model", icon: "E" }, - { href: "/explanation/observability/", label: "Observability", icon: "E" }, - { href: "/explanation/aspire/", label: "Orchestration with Aspire", icon: "E" }, - ], + label: "Reference", + subtitle: "Every symbol, generated", + kind: "menu", + roots: ["/reference/"], + expandRoot: true, }, { - label: "Reference", - items: [ - { href: "/reference/", label: "Reference index", icon: "R" }, - ...referenceUnits, - ], + label: "Concepts", + subtitle: "How and why it works", + kind: "menu", + roots: ["/explanation/"], + expandRoot: true, }, ]; + +/** nav.menu()/nav.breadcrumb() query — one definition so templates agree. */ +export const navQuery = "nav_hide!=true isRedirect!=true"; + +/** nav.menu() sort — `order` front matter first, url as tiebreak. */ +export const navSort = "order url"; diff --git a/docs/site/_data/xref.ts b/docs/site/_data/xref.ts index 708f33a72..2a805c052 100644 --- a/docs/site/_data/xref.ts +++ b/docs/site/_data/xref.ts @@ -32,7 +32,7 @@ export interface XrefTarget { label: string; } -/** The 31 generated reference units (mirrors `referenceUnits` in `_data.ts`). */ +/** The 32 generated reference units (mirrors `referenceUnits` in `_data.ts`). */ const REFERENCE_UNITS = [ "ai", "auth", @@ -49,6 +49,7 @@ const REFERENCE_UNITS = [ "fresh-ui", "kv", "logger", + "mcp", "plugin", "plugin-ai", "plugin-ai-core", @@ -93,35 +94,38 @@ export const xref: Record<string, XrefTarget> = { "cap:sdk": { href: "/services-sdk/sdk/", label: "Typed SDK & client" }, "cap:polyglot-tasks": { href: "/background-processing/polyglot-tasks/", label: "Polyglot tasks" }, "cap:runtime-config": { href: "/orchestration-runtime/runtime-config/", label: "Runtime configuration" }, + "cap:agent-tooling": { href: "/ai/agent-tooling/", label: "Agent tooling" }, // ─── How-to recipes (howto:) ─────────────────────────────────────────────── "howto:index": { href: "/how-to/", label: "How-to guides" }, - "howto:add-a-plugin": { href: "/how-to/add-a-plugin/", label: "Add a plugin" }, - "howto:add-a-service": { href: "/how-to/add-a-service/", label: "Add a service" }, - "howto:add-authentication": { href: "/how-to/add-authentication/", label: "Add authentication" }, - "howto:database-migration": { href: "/how-to/database-migration/", label: "Database & migration" }, - "howto:queue-kv-cron": { href: "/how-to/queue-kv-cron/", label: "Queue / KV / cron" }, - "howto:add-opentelemetry": { href: "/how-to/add-opentelemetry/", label: "Add OpenTelemetry" }, - "howto:customize-fresh-ui": { href: "/how-to/customize-fresh-ui/", label: "Customize Fresh UI" }, - "howto:build-a-desktop-frontend": { href: "/how-to/build-a-desktop-frontend/", label: "Build a desktop frontend" }, - "howto:deploy": { href: "/how-to/deploy/", label: "Deploy" }, - "howto:author-a-plugin": { href: "/how-to/author-a-plugin/", label: "Author a plugin" }, - "howto:deno-lsp-code-intelligence": { href: "/how-to/deno-lsp-code-intelligence/", label: "Deno LSP code intelligence" }, + "howto:add-a-plugin": { href: "/orchestration-runtime/how-to/add-a-plugin/", label: "Add a plugin" }, + "howto:add-a-service": { href: "/services-sdk/how-to/add-a-service/", label: "Add a service" }, + "howto:add-authentication": { href: "/identity-access/how-to/add-authentication/", label: "Add authentication" }, + "howto:database-migration": { href: "/data-persistence/how-to/database-migration/", label: "Database & migration" }, + "howto:queue-kv-cron": { href: "/data-persistence/how-to/queue-kv-cron/", label: "Queue / KV / cron" }, + "howto:add-opentelemetry": { href: "/observability/how-to/add-opentelemetry/", label: "Add OpenTelemetry" }, + "howto:customize-fresh-ui": { href: "/web-layer/how-to/customize-fresh-ui/", label: "Customize Fresh UI" }, + "howto:build-a-desktop-frontend": { href: "/web-layer/how-to/build-a-desktop-frontend/", label: "Build a desktop frontend" }, + "howto:deploy": { href: "/orchestration-runtime/how-to/deploy/", label: "Deploy" }, + "howto:author-a-plugin": { href: "/orchestration-runtime/how-to/author-a-plugin/", label: "Author a plugin" }, + "howto:deno-lsp-code-intelligence": { href: "/orchestration-runtime/how-to/deno-lsp-code-intelligence/", label: "Deno LSP code intelligence" }, // v3 NEW how-to recipes - "howto:run-a-polyglot-task": { href: "/how-to/run-a-polyglot-task/", label: "Run a polyglot task" }, - "howto:choose-a-queue-provider": { href: "/how-to/choose-a-queue-provider/", label: "Choose a queue provider" }, - "howto:use-a-second-database": { href: "/how-to/use-a-second-database/", label: "Use a second database" }, - "howto:discover-services": { href: "/how-to/discover-services/", label: "Discover services" }, - "howto:expose-openapi-scalar": { href: "/how-to/expose-openapi-scalar/", label: "Expose OpenAPI & Scalar" }, - "howto:graceful-shutdown": { href: "/how-to/graceful-shutdown/", label: "Graceful shutdown" }, - "howto:tune-worker-runtime": { href: "/how-to/tune-worker-runtime/", label: "Tune the worker runtime" }, - "howto:deploy-local-aspire": { href: "/how-to/deploy-local-aspire/", label: "Deploy locally with Aspire" }, - "howto:roll-out-runtime-overrides": { href: "/how-to/roll-out-runtime-overrides/", label: "Roll out runtime overrides" }, - "howto:add-a-task-runtime-adapter": { href: "/how-to/add-a-task-runtime-adapter/", label: "Add a task runtime adapter" }, - "howto:build-a-server-validated-form": { href: "/how-to/build-a-server-validated-form/", label: "Build a server-validated form" }, - "howto:build-a-validated-ingestion-queue": { href: "/how-to/build-a-validated-ingestion-queue/", label: "Build a validated ingestion queue" }, - "howto:publish-a-durable-stream": { href: "/how-to/publish-a-durable-stream/", label: "Publish a durable stream" }, - "howto:restrict-worker-task-permissions": { href: "/how-to/restrict-worker-task-permissions/", label: "Restrict worker task permissions" }, + "howto:run-a-polyglot-task": { href: "/background-processing/how-to/run-a-polyglot-task/", label: "Run a polyglot task" }, + "howto:choose-a-queue-provider": { href: "/data-persistence/how-to/choose-a-queue-provider/", label: "Choose a queue provider" }, + "howto:use-a-second-database": { href: "/data-persistence/how-to/use-a-second-database/", label: "Use a second database" }, + "howto:discover-services": { href: "/services-sdk/how-to/discover-services/", label: "Discover services" }, + "howto:expose-openapi-scalar": { href: "/services-sdk/how-to/expose-openapi-scalar/", label: "Expose OpenAPI & Scalar" }, + "howto:graceful-shutdown": { href: "/orchestration-runtime/how-to/graceful-shutdown/", label: "Graceful shutdown" }, + "howto:tune-worker-runtime": { href: "/background-processing/how-to/tune-worker-runtime/", label: "Tune the worker runtime" }, + "howto:deploy-local-aspire": { href: "/orchestration-runtime/how-to/deploy-local-aspire/", label: "Deploy locally with Aspire" }, + "howto:roll-out-runtime-overrides": { href: "/orchestration-runtime/how-to/roll-out-runtime-overrides/", label: "Roll out runtime overrides" }, + "howto:add-a-task-runtime-adapter": { href: "/background-processing/how-to/add-a-task-runtime-adapter/", label: "Add a task runtime adapter" }, + "howto:build-a-server-validated-form": { href: "/web-layer/how-to/build-a-server-validated-form/", label: "Build a server-validated form" }, + "howto:build-a-durable-chat": { href: "/ai/how-to/build-a-durable-chat/", label: "Build a durable chat" }, + "howto:deploy-deno-deploy": { href: "/orchestration-runtime/how-to/deploy-deno-deploy/", label: "Deploy to Deno Deploy" }, + "howto:build-a-validated-ingestion-queue": { href: "/durable-workflows/how-to/build-a-validated-ingestion-queue/", label: "Build a validated ingestion queue" }, + "howto:publish-a-durable-stream": { href: "/durable-workflows/how-to/publish-a-durable-stream/", label: "Publish a durable stream" }, + "howto:restrict-worker-task-permissions": { href: "/background-processing/how-to/restrict-worker-task-permissions/", label: "Restrict worker task permissions" }, // ─── Tutorials (tut:) ────────────────────────────────────────────────────── "tut:index": { href: "/tutorials/", label: "Tutorials" }, diff --git a/docs/site/_includes/layouts/base.vto b/docs/site/_includes/layouts/base.vto index f7cc6c039..f4c6c0996 100644 --- a/docs/site/_includes/layouts/base.vto +++ b/docs/site/_includes/layouts/base.vto @@ -39,34 +39,54 @@ <span class="ns-badge ns-badge--muted">docs</span> </div> - {{# Base-prefix the current page url so it matches itemUrl (also base-prefixed - via the `url` filter); the bare `url` page var is the non-prefixed source - url, so comparing it directly never matches (Gate 4 active-highlight). #}} - {{ set homeUrl = "/" |> url }} - {{ set pageUrl = url |> url }} + {{# docs-v5 sidebar (see _plan/10-nav-ia-redesign.md): curated lanes from + `navLanes` over folder-derived nav.menu() subtrees. Node/page urls are + SOURCE urls on both sides (source==source comparisons); emitted hrefs + get the /netscript/ base via the `url` filter + base_path. #}} <nav class="ns-dashboard__sidebar-body" aria-label="Documentation navigation"> - {{ for sectionIndex, section of navSections }} - {{ if sectionIndex > 0 }} + {{ for laneIndex, lane of navLanes }} + {{ if laneIndex > 0 }} <div class="ns-dashboard__nav-divider" role="separator"></div> {{ /if }} <div class="ns-dashboard__nav-group" role="list"> - <span class="ns-dashboard__nav-group-label">{{ section.label }}</span> - {{ for item of section.items }} - {{ set itemUrl = item.href |> url }} - {{ set isActive = pageUrl === itemUrl }} - {{ if itemUrl !== homeUrl && pageUrl.startsWith(itemUrl) }} - {{ set isActive = true }} - {{ /if }} - <a - href="{{ itemUrl }}" - role="listitem" - class="ns-dashboard__nav-item{{ if isActive }} is-active{{ /if }}" - {{ if isActive }}aria-current="page"{{ /if }} - > - <span class="ns-dashboard__nav-icon" aria-hidden="true">{{ item.icon }}</span> - <span class="ns-dashboard__nav-label">{{ item.label }}</span> - </a> - {{ /for }} + <h3 class="ns-dashboard__nav-group-label ns-nav-lane"> + {{ lane.label }} + <span class="ns-nav-lane__sub">{{ lane.subtitle }}</span> + </h3> + {{ if lane.kind === "flat" }} + {{ for item of lane.items }} + {{ set isActive = url === item.href }} + <a + href="{{ item.href |> url }}" + role="listitem" + class="ns-dashboard__nav-item{{ if isActive }} is-active{{ /if }}" + {{ if isActive }}aria-current="page"{{ /if }} + > + <span class="ns-dashboard__nav-label">{{ item.label }}</span> + </a> + {{ /for }} + {{ else }} + {{ for root of lane.roots }} + {{ set tree = nav.menu(root, navQuery, navSort) }} + {{ if tree && tree.data }} + {{ if lane.expandRoot }} + <a + href="{{ tree.data.url |> url }}" + role="listitem" + class="ns-dashboard__nav-item{{ if url === tree.data.url }} is-active{{ /if }}" + {{ if url === tree.data.url }}aria-current="page"{{ /if }} + > + <span class="ns-dashboard__nav-label">{{ tree.data.nav_title || tree.data.title }}</span> + </a> + {{ for child of tree.children }} + {{ include "nav/menu-item.vto" { item: child, currentUrl: url } }} + {{ /for }} + {{ else }} + {{ include "nav/menu-item.vto" { item: tree, currentUrl: url } }} + {{ /if }} + {{ /if }} + {{ /for }} + {{ /if }} </div> {{ /for }} </nav> @@ -128,7 +148,7 @@ </header> <main class="ns-dashboard__content" id="main-content" tabindex="-1"> - {{ comp.breadcrumb({ url, navSections, title }) }} + {{ comp.breadcrumb({ url, title }) }} {{# AI-affordance (PR-A): plain link to this page's Markdown twin (built by _plugins/ai-tooling.ts). CSP-safe, no client JS. #}} <div class="ns-page-actions"> @@ -155,7 +175,7 @@ <ol class="ns-toc__list" data-toc-list></ol> </nav> </div> - {{ comp.nextPrev({ prev, next }) }} + {{ comp.nextPrev({ prev, next, url }) }} {{ set editSourcePath = page.sourcePath }} {{ if editSourcePath && editSourcePath !== "(generated)" && !editSourcePath.startsWith("/reference/") }} {{ set editHref = "https://github.com/rickylabs/netscript/edit/main/docs/site" + editSourcePath }} diff --git a/docs/site/_includes/nav/menu-item.vto b/docs/site/_includes/nav/menu-item.vto new file mode 100644 index 000000000..bd850c2b9 --- /dev/null +++ b/docs/site/_includes/nav/menu-item.vto @@ -0,0 +1,46 @@ +{{# nav/menu-item.vto — one node of the folder-derived sidebar tree, included + recursively for children (the Lume nav plugin's documented pattern). Props: + `item` (nav.menu node: { slug, data?, children? }) and `currentUrl` (the + page's SOURCE url; node urls are source urls too, so comparisons are + source==source — base_path prefixes emitted hrefs at build time). + A node with children renders as <details>, auto-opened on the active path; + clicking the label navigates, the chevron toggles. Every content folder has + an index.md so `item.data` is always present; the guard keeps a page-less + node from crashing the build. #}} +{{ if item.data }} + {{ set nodeUrl = item.data.url }} + {{ set isCurrent = nodeUrl === currentUrl }} + {{ if item.children && item.children.length }} + <details class="ns-nav-branch"{{ if currentUrl.startsWith(nodeUrl) }} open{{ /if }}> + <summary class="ns-nav-branch__summary"> + <a + href="{{ nodeUrl |> url }}" + class="ns-dashboard__nav-item{{ if isCurrent }} is-active{{ /if }}" + {{ if isCurrent }}aria-current="page"{{ /if }} + > + <span class="ns-dashboard__nav-label">{{ item.data.nav_title || item.data.title }}</span> + </a> + <span class="ns-nav-branch__chevron" aria-hidden="true"> + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" + stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"> + <polyline points="9 18 15 12 9 6" /> + </svg> + </span> + </summary> + <div class="ns-nav-branch__children" role="list"> + {{ for child of item.children }} + {{ include "nav/menu-item.vto" { item: child, currentUrl: currentUrl } }} + {{ /for }} + </div> + </details> + {{ else }} + <a + href="{{ nodeUrl |> url }}" + role="listitem" + class="ns-dashboard__nav-item{{ if isCurrent }} is-active{{ /if }}" + {{ if isCurrent }}aria-current="page"{{ /if }} + > + <span class="ns-dashboard__nav-label">{{ item.data.nav_title || item.data.title }}</span> + </a> + {{ /if }} +{{ /if }} diff --git a/docs/site/_plan/10-nav-ia-redesign.md b/docs/site/_plan/10-nav-ia-redesign.md new file mode 100644 index 000000000..45b765aa1 --- /dev/null +++ b/docs/site/_plan/10-nav-ia-redesign.md @@ -0,0 +1,475 @@ +# 10 — Multilevel Nav & IA Redesign (Lume nav plugin) — Proposal + +**Status: PROPOSAL — awaiting maintainer approval. No content moved or rewritten yet.** + +Provenance: produced 2026-07-18 by a supervised multi-agent design run (4 repo/market audits → +3 independent full IA proposals → 3 adversarial judges → synthesis), with every count re-verified +against the live repo. Judges: reader-experience → P2, migration/link-integrity → P2, +state-of-the-art/technical-fit → P3. Winning spine = P2 (journey lanes) with two structural grafts +from P1 (capability-native) and P3 (Diátaxis-separation). + +The problem being solved: today's sidebar is a hand-rolled flat `navSections` array in `_data.ts` — +13 sections, ~120 always-visible links, 37 duplicate listings (the same page in 2–3 sections), +14 pillar guide pages unlisted in nav (counting agent-tooling), and no hierarchy/collapse. The Lume nav plugin derives the +tree from page URLs, so the folder layout must become the IA. + +## 1. Decision summary + +**Shape chosen: lifecycle-lane spine, with the pillars kept whole and the recipes moved home.** +Five curated top-level lanes — **Start · Learn · Build · Reference · Concepts** — render as visual +dividers over folder-derived `nav.menu()` subtrees. This lands dead-center of the 6–9-group SOTA +band (today: 13 flat sections, ~120 always-visible links), keeps Learn (tutorials) as the second +lane so a newcomer's "build one thing" path is prominent, and uses plain-English/intent labels that +are *not* the Diátaxis quadrant names (satisfying the locked "Diátaxis demoted from front door" +decision in `08-decisions-locked.md`). + +**Per-capabilities: KEPT and strengthened.** All nine pillars stay top-level and **contiguous +inside one Build lane**, fixing the journey-first proposal's own worst defect — a Build/Operate +split that fractured the capability set ("where is Observability?"). There is no Operate lane; +deploy/observe concerns are expressed as recipes inside the Orchestration and Observability pillars +plus cross-refs, not by relocating whole pillars. The locked "docs-v4 Capability-Hub IA" (pillars as +the mental model) is preserved, so this design carries **no top-level-IA ratification risk**. + +**The one graft that changes URLs: recipes move into their pillar.** The 27 flat `/how-to/` recipes +were the sidebar's least navigable asset — a decontextualized global list. Two of three judges ruled +a flat recipe list unacceptable. We adopt the namespaced **`/<pillar>/how-to/<recipe>/`** subfolder +(plugin-native real node, collision-proof, depth-3 because pillars stay top-level). Each recipe now +sits next to the guide it serves, in one expand. `queue-kv-cron` and `choose-a-queue-provider` +re-home from Background Processing to Data & Persistence (better semantic fit next to the +`kv-queues-cron` guide). + +**Cost of that graft, stated honestly:** 28 URL moves (27 recipes + `capabilities/agent-tooling`). +This is a deliberate reader-value-over-migration-minimalism decision. The cost is bounded and +build-gated: `check-internal-links.ts` (deno.json task) scans every href in built HTML, so no broken +link can merge; the Lume `redirects` plugin reduces 28 shims to 28 `oldUrl` front-matter lines +(generated pages are flagged `isRedirect: true` and sit outside every nav.menu root). Reference +(crown jewels), tutorials, explanation, and all nine pillar-guide URLs stay byte-stable. + +**Reference stays a single flat locked lane** (universal SOTA pattern; 32 units, `mcp` now +registered) — never scattered into pillars. **Cross-listing dies entirely** (37 duplicate nav +listings removed); discovery re-lands in pillar-hub card-grids via the existing `xref` system. + +## 2. Full target arborescence + +Legend: **[KEEP]** URL+home unchanged · **[MOVE from X]** file moves, ships redirect · **[NEW]** new +file · **[RETIRE→catalog]** kept as nav-hidden catalog · **[HIDE]** on disk, `nav_hide`, +search/link-reachable only · **[LOCKED]** reference URL, byte-stable · **[REGISTER]** exists on +disk, add to data/xref. Lane dividers (`━━`) are non-interactive headers (depth L0). Collapsible +depth below a lane: L1 pillar/track/unit → L2 leaf/how-to-node → L3 recipe/chapter/subpage. +**Max collapsible depth = 3.** + +``` +/ Home (not a nav item) [KEEP] + +━━ START ━━ (curated flat list — locked plain-English front door) + /why/ Why NetScript [KEEP] + /quickstart/ Quickstart (Aspire hero path) [KEEP] (de-cross-listed: drop 2nd listing under Orchestration) + /quickstart/aspire/ Aspire quickstart [NEW] (hero CTA landing; TS AppHost, not .NET; --no-aspire opt-out lives in /explanation/aspire/) + /concepts/ Core concepts [KEEP] (de-cross-listed: drop 2nd listing under Observability; reprose — §6) + /cli-reference/ CLI reference (task cheat-sheet) [KEEP] (NOT moved; reprose to defer symbols to /reference/cli/commands/) + /glossary/ Glossary [KEEP] + /how-to/ All how-to recipes (catalog) [RETIRE→catalog] children moved out; nav_hide; linked from Start + pillar hubs + +━━ LEARN ━━ nav.menu("/tutorials/", "nav_hide!=true", "order url") + /tutorials/ Tutorials (index) [KEEP] L1 + /tutorials/live-dashboard/ 01–06 Live dashboard [KEEP] L2 → chapters L3 (drop Web "Quickstart→index" dup) + /tutorials/chat/ 01–06 Durable chat [KEEP] L2 → L3 (drop AI dup) + /tutorials/workspace/ 01–06 Workspace [KEEP] L2 → L3 (drop Identity "02-auth" mid-chapter anchor) + /tutorials/storefront/ 01–07 Storefront [KEEP] L2 → L3 (drop Services/Data/Durable mid-chapter anchors) + /tutorials/erp-sync/ 01–05 ERP sync [KEEP] L2 → L3 (drop BG "03-polyglot-transform" anchor) + /tutorials/eis-chat/** (5 shims) → /tutorials/chat/* [HIDE] nav_hide (URL preservation only) + +━━ BUILD ━━ 9 contiguous pillar nav.menu() roots, curated order (the locked capability spine) + within each pillar: guide leaves (L2), then a how-to/ node (L2) whose recipes are L3 + + /web-layer/ Web Layer [KEEP] L1 (rewrite → card-grid hub, §6) + /web-layer/server/ builders/ route/ query/ form/ defer-streaming-ui/ + interactive/ vite/ error/ testing/ examples/ [KEEP] L2 (12 guide leaves) + /web-layer/fresh-ui/ Fresh UI & design [KEEP] L2 (was nav-orphan → gains home) + /web-layer/how-to/ (recipes) [NEW] L2 index + …/customize-fresh-ui/ [MOVE from /how-to/customize-fresh-ui/] L3 + …/build-a-desktop-frontend/ [MOVE from /how-to/build-a-desktop-frontend/] L3 + …/build-a-server-validated-form/ [MOVE from /how-to/build-a-server-validated-form/] L3 + + /services-sdk/ Services & SDK [KEEP] L1 (rewrite hub) + /services-sdk/services/ Services & contracts [KEEP] L2 (was orphan → home) + /services-sdk/sdk/ Typed SDK & client [KEEP] L2 (was orphan → home) + /services-sdk/how-to/ [NEW] L2 + …/add-a-service/ …/discover-services/ …/expose-openapi-scalar/ [MOVE from /how-to/*] L3 (3) + + /background-processing/ Background jobs [KEEP] L1 (rewrite hub; "workers" taught inside) + /background-processing/workers/ Background jobs (guide) [KEEP] L2 (was orphan → home) + /background-processing/polyglot-tasks/ Polyglot tasks [KEEP] L2 (was orphan → home) + /background-processing/how-to/ [NEW] L2 + …/tune-worker-runtime/ …/run-a-polyglot-task/ + …/add-a-task-runtime-adapter/ …/restrict-worker-task-permissions/ [MOVE from /how-to/*] L3 (4) + + /durable-workflows/ Durable workflows [KEEP] L1 (rewrite hub; "sagas" taught inside) + /durable-workflows/sagas/ streams/ triggers/ [KEEP] L2 (all 3 were orphans → homes) + /durable-workflows/how-to/ [NEW] L2 + …/build-a-validated-ingestion-queue/ …/publish-a-durable-stream/ [MOVE from /how-to/*] L3 (2) + + /ai/ AI & Agents [KEEP] L1 (rewrite hub) + /ai/mcp/ durable-chat/ chat-ui/ [KEEP] L2 + /ai/engine/ AI engine (guide) [KEEP] L2 (reprose: demote from "Reference:", §6) + /ai/agent-tooling/ Agent tooling [MOVE from /capabilities/agent-tooling/] L2 (was orphan in all-shim dir) + /ai/how-to/ [NEW] L2 + …/build-a-durable-chat/ [MOVE from /how-to/build-a-durable-chat/] L3 (UNKEYED — see §5) + + /data-persistence/ Data & Persistence [KEEP] L1 (rewrite hub) + /data-persistence/database/ kv-queues-cron/ [KEEP] L2 (both were orphans → homes) + /data-persistence/how-to/ [NEW] L2 + …/database-migration/ …/use-a-second-database/ [MOVE from /how-to/*] L3 + …/queue-kv-cron/ …/choose-a-queue-provider/ [MOVE from /how-to/*, RE-HOMED from Background Processing] L3 (4 total) + + /identity-access/ Identity & Access [KEEP] L1 (rewrite hub) + /identity-access/auth/ Authentication [KEEP] L2 (was orphan → home) + /identity-access/better-auth-plugins/ [KEEP] L2 + /identity-access/how-to/ [NEW] L2 + …/add-authentication/ [MOVE from /how-to/add-authentication/] L3 + + /orchestration-runtime/ Orchestration & Runtime (Aspire home) [KEEP] L1 (rewrite hub) + /orchestration-runtime/cli-scaffold/ [KEEP] L2 + /orchestration-runtime/runtime-config/ [KEEP] L2 (was orphan → home) + /orchestration-runtime/how-to/ [NEW] L2 + …/deploy/ …/deploy-local-aspire/ …/deploy-deno-deploy/ + …/roll-out-runtime-overrides/ …/graceful-shutdown/ + …/add-a-plugin/ …/author-a-plugin/ …/deno-lsp-code-intelligence/ [MOVE from /how-to/*] L3 (8) + (deploy-deno-deploy is UNKEYED — see §5) + + /observability/ Observability [KEEP] L1 (rewrite hub) + /observability/telemetry/ Telemetry & logging [KEEP] L2 (was orphan → home) + /observability/how-to/ [NEW] L2 + …/add-opentelemetry/ [MOVE from /how-to/add-opentelemetry/] L3 + +━━ REFERENCE ━━ nav.menu("/reference/", "", "order url") — 32 units, URLs LOCKED byte-stable + /reference/ Reference (index) [KEEP][LOCKED] L1 + …31 registered units (ai, aspire, auth, auth-better-auth, auth-kv-oauth, auth-workos, + cli, config, contracts, cron, database, fresh, fresh-ui, kv, logger, plugin, + plugin-ai, plugin-ai-core, plugin-auth, plugin-auth-core, prisma-adapter-mysql, + queue, runtime-config, sagas, sdk, service, streams, telemetry, triggers, + watchers, workers)… [KEEP][LOCKED] L2 (single listing; drop the 2nd pillar-section copy) + /reference/mcp/ @netscript/mcp [REGISTER][LOCKED] L2 (exists on disk; ADD to referenceUnits + REFERENCE_UNITS + ref:mcp) + /reference/cli/commands/ CLI commands [KEEP][LOCKED] L3 + /reference/ai/skills/ AI skills [KEEP][LOCKED] L3 + /reference/telemetry/convention/ Telemetry convention [KEEP][LOCKED] L3 (was orphan → auto-surfaces via nav.menu) + +━━ CONCEPTS ━━ nav.menu("/explanation/", "nav_hide!=true", "order url") — plain-English label (Diátaxis demoted) + /explanation/ How NetScript works (index) [KEEP] L1 + /explanation/architecture/ contracts/ plugin-system/ auth-model/ + durability-model/ observability/ aspire/ [KEEP] L2 (7 essays) + +━━ (not in any nav lane) ━━ + /capabilities/*.md (16 non-index shims + index.md → pillar dirs) [KEEP as shims] nav_hide + + 28 NEW redirect entries at every [MOVE] source (27 recipes + agent-tooling) [NEW] auto-unlisted via redirects plugin +``` + +**Verified totals:** 28 pages MOVE · 2 NEW pages (`/quickstart/aspire/`, optional — §8) · +9 NEW `how-to/index.md` · 16 pages surface in nav for the first time (14 pillar guides currently +unlisted — counting `agent-tooling`, which re-homes to `/ai/` — + `telemetry/convention` + `mcp`) · +**37 duplicate listings removed** (today's sidebar: 147 entries, 110 unique hrefs) · **0 reference +URLs changed**. + +## 3. Sidebar & nav plugin implementation + +**Plugin registration (`_config.ts`).** Add `import nav from "lume/plugins/nav.ts"; site.use(nav());` +and `import redirects from "lume/plugins/redirects.ts"; site.use(redirects());`. Registration order +relative to `base_path` is irrelevant for `nav` — it is a template-data provider emitting *source* +(pre-base) URLs, and `base_path()` still runs last and rewrites emitted `href`s. `redirects` must +run before `base_path` (it emits pages). `code_highlight`, `pagefind`, `aiTooling` untouched. + +**What replaces `navSections`.** Delete the ~120-entry `navSections` array, the +`NavItem`/`NavSection` types, **and** the `referenceUnits` array (all were nav-only inputs; +reference is now folder-derived). Replace with a compact `navLanes` spine — the *only* +hand-maintained nav data (~35 lines vs ~120): + +```ts +export interface NavLane { + label: string; subtitle: string; icon?: string; + kind: "flat" | "menu"; + items?: string[]; // flat: curated hrefs (Start) + roots?: string[]; // menu: nav.menu roots, in curated order (Build owns the 9-pillar sequence) +} +export const navLanes: NavLane[] = [ + { label: "Start", subtitle: "Get running in minutes", kind: "flat", + items: ["/why/","/quickstart/","/quickstart/aspire/","/concepts/","/cli-reference/","/glossary/","/how-to/"] }, + { label: "Learn", subtitle: "Build one thing end to end", kind: "menu", roots: ["/tutorials/"] }, + { label: "Build", subtitle: "Add a capability to your app", kind: "menu", + roots: ["/web-layer/","/services-sdk/","/background-processing/","/durable-workflows/", + "/ai/","/data-persistence/","/identity-access/","/orchestration-runtime/","/observability/"] }, + { label: "Reference", subtitle: "Every symbol, generated", kind: "menu", roots: ["/reference/"] }, + { label: "Concepts", subtitle: "How and why it works", kind: "menu", roots: ["/explanation/"] }, +]; +``` + +The nine-pillar curated order lives in `Build.roots` — this is what neutralizes decision D-E1's +concern (a *global* `nav.menu("/")` inverting designed order): top-level lane order and pillar +sequence stay curated data, and `nav.menu` only builds single-home subtrees it can order correctly +with `order` front matter. + +**Front-matter conventions (the real content sweep):** + +- `order: <n>` on every index + leaf a `nav.menu` root touches (~113 non-shim pages; none exist + today). Sort string everywhere: `"order url"` (verified valid multi-field grammar). + Pillar/track/how-to **index pages get `order: 0`** so the hub sorts first. Tutorial chapters need + no `order` — their `NN-` prefix sorts under the `url` tiebreak (index pages share basename "index", which is why the tiebreak is url, not basename). Recipes get an + `order: 100+` band so they always cluster after guides inside a pillar. +- `nav_hide: true` filtered out via the `"nav_hide!=true isRedirect!=true"` query (verified: + negation valid, undefined passes). The Lume `redirects` plugin flags its generated pages + `isRedirect: true` (redirects.ts:126 — it does **not** set `unlisted`), and every moved-from URL + lives under `/how-to/` or `/capabilities/`, outside all nav.menu roots; the `isRedirect!=true` + term is belt-and-braces. Only the 5 `tutorials/eis-chat/*` shims (which live under a rendered + root) and the retired `/how-to/` catalog need explicit `nav_hide`. +- `nav_title: <str>` only where the sidebar label must differ from the page H1 (today's curated + labels like "The Fresh page model" diverge from H1s). Template reads + `node.data.nav_title || node.data.title`. +- Every **nav-rendered content root and its subfolders** (tutorials/*, the nine pillar dirs, + reference/*, explanation/) already has `index.md` (verified; scope excludes non-content dirs like + `_components`/`assets`/`styles`) → every section node carries `data`, so every `<summary>` is + clickable and `order`-sort is hazard-free (no page-less intermediate nodes). + +**`base.vto` (replace lines 47–72).** Loop `navLanes`: + +- Render each lane divider: + `<h3 class="nav-lane">{{ lane.label }}<span class="nav-lane-sub">{{ lane.subtitle }}</span></h3>` + (subtitles are a kept SOTA affordance — Stripe/Lume pattern). +- `kind:"flat"` → curated `<a>`s; active check `page.data.url == href` (drop today's double + `|> url` dance — source==source, since base_path rewrites the emitted href). +- `kind:"menu"` → for each root, feed `nav.menu(root, "nav_hide!=true", "order url").children` + to a recursive **`_includes/menu_item.vto`** partial (the plugin's documented recursion — required + because Build reaches depth 3 at pillar→how-to→recipe and Reference/Learn reach + unit/track→subpage). Each folder node is `<details {{ if url.startsWith(node.data.url) }}open{{ /if }}>`; + leaves are `<a … {{ if node.data.url == url }}aria-current="page"{{ /if }}>`. Per-lane icons come + from `navLanes`; per-page icons are dropped (nav data carries none). + +**Collapse:** default-collapsed siblings, auto-expand the active path via `url.startsWith`. Native +`<details>/<summary>`, zero JS. A reader sees ~5 lane headers + 9 pillar summaries + one open +subtree — strictly fewer on-screen elements than today's 120. + +**Breadcrumb (`breadcrumb.vto`).** Replace the `navSections` scan with `nav.breadcrumb(url)` +(root→page array), prefixed by the owning lane label via a small `root→lane` lookup (so +`/web-layer/query/` reads *Build › Web Layer › Data loading & cache*). Guard `{{ if crumb.data }}`. +Behavior change accepted: one deterministic trail replaces "last matching section." + +**Next/prev (`nextPrev.vto`).** Keep front-matter-driven for tutorials (per-chapter editorial +control; it never touched `navSections`, cannot break). For Reference (32 units, unmaintainable by +hand) auto-derive with the verified v2.5.4 signature — `nav.nextPage(url, query?, sort?)` / +`nav.previousPage(url, query?, sort?)` (there is **no** basePath argument; nav.ts:85/111). Scope to +the reference lane via the query: `nav.nextPage(url, "url^=/reference/", "order url")`. + +**Untouched chrome (verified nav-agnostic):** `<aside data-sidebar>` shell, brand header, mobile +toggle/backdrop JS, theme toggle, TOC/scroll-spy, code-copy, tabbed-code, edit-this-page footer, +pagefind, code_highlight, base_path, aiTooling, the entire `xref.ts` layer. + +## 4. Storyline + +**First-time visitor (evaluating).** Lands on `/` (locked outcome-led hero, Aspire named). The +sidebar reads top-down as a promise of the path ahead: **Start** "get running in minutes" → +**Learn** "build one thing end to end" → **Build** "add a capability" → the lookup tail. They never +guess a pillar first. They open the only-expanded **Start** lane — Why · Quickstart · Aspire +quickstart (hero path; `--no-aspire` opt-out one click into `/explanation/aspire/`) · Core concepts · +CLI reference · Glossary — then commit to **Learn**, the second lane, where a tutorial track walks +them chapter-to-chapter with a lane-prefixed breadcrumb (Learn › Storefront › 03). Alpha framing set +here. Learn-second ordering is the decisive newcomer win; the alternatives buried tutorials below +the pillar wall. + +**Returning builder (implementing).** Goes straight to **Build**, where all nine pillar names are +visible and contiguous (no Build/Operate fracture — Observability and Orchestration are right there +in the capability set). Expands the one pillar they need — say **Background jobs** — and sees its +whole world in one expand: the overview hub, the `workers` and `polyglot-tasks` guides (no longer +orphaned), then a **Recipes** node with `tune-worker-runtime`, `run-a-polyglot-task`, +`restrict-worker-task-permissions` right beside the guide they serve. Learn → do → look up is one +vertical scan, not a hunt through a decontextualized 27-item list. When they need a queue provider, +muscle memory sends them to **Data & Persistence** where `queue-kv-cron` and +`choose-a-queue-provider` live next to the `kv-queues-cron` guide (re-homed for semantic fit). The +pillar hub card-grids out to the matching tutorial and deep-dive via `xref`, so cross-cutting +content is one prose click away without polluting the tree. + +**API-lookup user (or agent).** Ignores the top three lanes; jumps to **Reference** (or ⌘K +pagefind, or the `.md` twin / `llms.txt`). Gets the flat, `order`-banded, byte-stable 32-unit tree +keyed by package — families clustered (`auth*`, `plugin*`) — now including the previously-orphaned +`mcp` and the `telemetry/convention` / `cli/commands` / `ai/skills` L3 subpages that surface +automatically because the tree is folder-derived. Auto next/prev pages through the set. Reference is +never scattered into pillars, so a symbol-hunter never touches the capability spine. + +**Concepts** sits last as the "graduate to understanding" tier — the essays a productive user reads +once (Concepts-second alternatives were penalized for inverting do-before-understand). + +## 5. Cross-reference & migration plan + +**Verified counts (re-checked in-repo 2026-07-18):** 32 reference unit dirs on disk (`mcp` present, +unregistered; today's `referenceUnits` nav array has 33 entries because `cli/commands` and +`ai/skills` ride as extra rows) · 27 recipes (flat `.md`, excluding `index.md`) · 26 `howto:` keys incl. `howto:index` → 25 recipe keys; +exactly 2 recipes UNKEYED: `build-a-durable-chat`, `deploy-deno-deploy` · ~517 hardcoded absolute +markdown links (`](/…)` form; 533 in the broader sweep incl. anchored/mixed forms) + 25 relative +links · 61 hardcoded inbound to `/how-to/<slug>` (markdown form) · 6 inbound to `agent-tooling` · +`capabilities/agent-tooling.md` is a real `base.vto` page (verified), movable. + +**Movement scope:** 28 URL moves (27 recipes → `/<pillar>/how-to/<recipe>/`; +`capabilities/agent-tooling` → `/ai/agent-tooling/`). `/cli-reference/` NOT moved (kept in Start, +reframed) — saves 14 inbound + the `cli:reference` xref. Reference: 0 moves. + +**Per moved URL, three actions:** + +1. **Redirect** via the Lume `redirects` plugin: add `oldUrl: /how-to/<slug>/` front matter to the + destination page (auto-sets `unlisted:true`, so no manual `nav_hide` and no shim file). 28 + one-line additions. GitHub Pages emits no 301s; the plugin's meta-refresh stub is the mechanism + (supersedes hand-rolled `redirect.vto` files for these moves; keeps the 21 existing + `redirect.vto` shims as-is). +2. **xref retarget:** repoint the 25 existing recipe `howto:` keys; **add 2 new keys** + (`howto:build-a-durable-chat`, `howto:deploy-deno-deploy`) so the build link-checker covers them + (they currently ride on nothing — a gap all three source proposals missed); add + `cap:agent-tooling → /ai/agent-tooling/` (currently unkeyed, 6 hardcoded inbound). = ~28 xref + edits. +3. **Hardcoded sweep (kill double-hops):** repoint the 61 `](/how-to/<slug>)` + 6 agent-tooling + markdown links to canonical new URLs — grep-scriptable (`/how-to/<slug>/` → + `/<pillar>/how-to/<slug>/` via a slug→pillar map). Redirects keep old links working, so this is + deferrable/incremental, not a build-blocker. + +**Orphan/registration fixes (no move):** add `mcp` to `referenceUnits` (data) + `REFERENCE_UNITS` +(xref) + a `ref:mcp` key (build throws on unknown key, so this must land before any `ref:mcp` use). +The ~203 `xref` links overall need zero shims (they move for free with href retargets). + +**aiTooling coupling (first-class step for every move):** after any URL change, regenerate +`llms.txt`/tiered variants and the per-page `.md` twins (aiTooling in `_config.ts` is URL-coupled); +external deep-links to old `.md` twins have no shim, so this is required, not optional. + +**Front-matter sweep (additive, zero link risk):** `order` on ~113 pages; `nav_title` on ~40; +`nav_hide` on the eis-chat shims + `/how-to/` catalog. Cannot break a link — only mis-sequence a +menu (visual QA catches it). + +**Ordered migration slices — each ends `deno task verify` (check-internal-links) green:** + +- **S0** — Register `nav` + `redirects` plugins; add `order`/`nav_title` front matter site-wide; + keep old `navSections` rendering. *(additive, no URL change → green)* +- **S1** — Register `mcp` (referenceUnits + REFERENCE_UNITS + `ref:mcp`); add the 2 missing + `howto:` keys pointing at *current* URLs. *(fixes orphans in place → green)* +- **S2** — Move `agent-tooling` → `/ai/agent-tooling/` (`oldUrl`, key, regenerate aiTooling). + *(1 move → green)* +- **S3.1–S3.9** — Move recipes **one pillar at a time**: per pillar, move N recipes to + `/<pillar>/how-to/`, add `how-to/index.md`, set `oldUrl`, retarget keys, regenerate aiTooling. + *(green after each pillar; re-home queue recipes to Data in the data-persistence slice)* +- **S4** — Swap `base.vto` from `navSections` to `navLanes`+`nav.menu`; swap `breadcrumb.vto` to + `nav.breadcrumb`; switch Reference `nextPrev` to `nav.nextPage`; delete + `navSections`/`referenceUnits` arrays. *(render swap; URLs already stable → green)* +- **S5** — Hardcoded double-hop sweep (incremental). *(green throughout)* +- **S6** — Prose rewrites (§6). *(green throughout)* + +**Net:** 28 moves, 28 `oldUrl` redirect lines, ~28 xref retargets + 3 additions, 9 new how-to +indexes, ~113 additive front-matter edits, ~67 optional hardcoded repoints. Reference: 0 URL +changes; 1 registration. + +## 6. Rewrite list + +**Priority 1 — required for the new IA to be coherent:** + +- 9 pillar `index.md` (`/web-layer/`, `/services-sdk/`, `/background-processing/`, + `/durable-workflows/`, `/ai/`, `/data-persistence/`, `/identity-access/`, + `/orchestration-runtime/`, `/observability/`): rewrite thin overviews into true **card-grid + hubs** — because the sidebar no longer cross-lists, each hub must surface its reference unit(s) + (`ref:`), its tutorial ("Start here → …"), and its deep-dive (`explain:`) as prose cards. This is + where all removed cross-listing re-lands. +- `/how-to/index.md`: rewrite from a live tier into a flat **all-recipes catalog** linking every + recipe at its new pillar URL via `howto:` xref; `nav_hide`, reachable from Start + pillar hubs. +- `/concepts/`: strip the "Quickstart: trace the model" (Observability cross-listing) framing; + single-home as Core concepts; deconflict altitude vs `/explanation/architecture/` (5-min model vs + deep why). + +**Priority 2 — de-duplication the IA exposes:** + +- `/cli-reference/`: reframe as a task-oriented CLI cheat-sheet; defer symbol tables to + `/reference/cli/commands/` (ends the 3-way CLI overlap). +- `/ai/engine/`: demote from "Reference: AI engine" to a guide; point to `/reference/ai/` + + `/reference/plugin-ai/` + `/reference/plugin-ai-core/` (ends 4-way AI-reference duplication). +- `/ai/agent-tooling/` (moved): reframe as an AI-pillar guide; dedup CLI+MCP overlap; refresh the + 2026 agent surface (llms.txt / docs-MCP / copy-as-markdown). +- `/explanation/{observability,auth-model,durability-model}/`: set explicit altitude ("this essay + is *why*; the pillar is *how*") and cross-link both ways so the Concepts lane doesn't read as + orphaned duplication. + +**Priority 3 — light edits:** + +- 27 moved recipes: add `order` (100+ band), `nav_title`; remove now-redundant "see the X pillar" + pointers (co-located now). +- 5 tutorial track indexes: add a closing "after this track → **Build › <pillar>**" hand-off + (replaces implicit cross-listing). +- Pillar hubs referencing mid-tutorial-chapter "Quickstart" anchors: repoint prose to the track + index or pillar quickstart (deep-links retired from nav). +- `/reference/mcp/index.md`: add `title`/`order` front matter to join the tree. +- `/quickstart/aspire/` (if authored): new thin page framed as TS AppHost inspection, never .NET. +- `/why/` + `/`: verify Aspire hero framing, alpha framing, no banned superlative claims. + +The 13 formerly nav-orphan pillar guides already in their pillar dirs need **no prose change** — they only gain nav homes. + +## 7. Rejected alternatives + +**P2's original journey lanes (the winning spine, but two features rejected).** *Argued:* 6 +lifecycle lanes (Start/Learn/Build/Operate/Reference/Concepts), 1 URL move, recipes as a flat +`nav.menu("/how-to/")` list under Build, deploy recipes surfaced a second time via hand-rolled +`extraLinks` under Operate. *Rejected features:* (a) the Build/Operate split fractured the +capability set — a builder wanting telemetry had to know Observability was under Operate, not Build +(reader judge: scent failure); replaced by nine contiguous pillars in one Build lane. (b) The flat +27-recipe list decontextualized recipes from their guides — P2 cited "Astro nests recipes inside +Guide" then declined to do it (reader + sota judges: its central unfulfilled claim); replaced by +pillar-nested recipes. (c) The `extraLinks` re-introduced duplicate listings via hand-rolled hrefs — +the exact disease the exercise cures (sota judge). *Kept from P2:* lane spine, subtitles, +Learn-second ordering, Concepts-last, lane-prefixed breadcrumb, `redirects`-plugin instinct, correct +plugin-registration reasoning. + +**P1 capability-native (2nd on reader, strong on migration/sota).** *Argued:* nine pillars +top-level in bands, recipes nested in real `/<pillar>/how-to/` subfolders, `/how-to/` demoted to +catalog. *Rejected as a whole because:* it kept ~13 collapsible top-level roots — above the 6–9 +SOTA ceiling, "the same over-count that ails the current site, merely collapsed" (sota judge); and +buried tutorials/reference at the bottom, failing the newcomer and API-lookup personas (reader +judge). It was also silent on the aiTooling `llms.txt`/`.md`-twin ripple of its own moves and on +the 2 unkeyed recipes. *Kept from P1:* the namespaced `/<pillar>/how-to/<recipe>/` subfolder +mechanism (adopted wholesale — plugin-native, collision-proof, depth-3 under top-level pillars), +the pillar-hub card-grid rewrite spec, the D-E1 fallback ladder. + +**P3 Diátaxis-separation (sota winner, but rejected as spine).** *Argued:* 5 groups +(Start/Concepts/Guides/Tutorials/Reference), pillars demoted into a single "Guides" collapse, +inline `diataxis:` cluster sublabels for in-pillar recipes. *Rejected because:* (a) it hid all nine +pillar names behind a generic "Guides" collapse — worst capability visibility (reader judge). (b) +It made the Diátaxis quadrant names the literal front-door labels while claiming to honor the +locked decision that demotes exactly those labels — an unreconciled contradiction. (c) It ordered +Concepts second, before doing (inverts newcomer flow). (d) It stacked two locked-decision +deviations — the highest ratification risk (migration judge). *Kept from P3:* the `redirects` +plugin (`oldUrl` + auto-`unlisted`), the aiTooling-coupling migration step, the two-orphan capture +(`mcp` + `telemetry/convention`), the queue-recipe re-home to Data. *Rejected in favor of P1's:* +the inline `diataxis:` sublabels (transition-detection fragility + collision-prone flat slugs) lost +to P1's real subfolder. + +## 8. Risks & open questions for the maintainer + +Supervisor recommendations are inline; items 1–4 need explicit maintainer sign-off before S2+. + +1. **The recipe-move trades the 1-move migration win.** Pillar-nested recipes = 28 moves + 28 + redirects + aiTooling regen instead of 1 move. **Recommendation: accept** — two of three judges + ruled the flat recipe list unacceptable, the cost is build-gated, and in-pillar co-location is + the core reader-scent payoff of the whole redesign. Fallback: flat `nav.menu("/how-to/")` list + (1 move) if you value URL stability above scent. +2. **Decision D-E1** ("`nav.ts` for the Reference sub-tree ONLY") lives in + `09-research-integration.md` §3, **not** in the user-binding `08-decisions-locked.md` — it is an + engineering caveat, not a user lock. **Recommendation: amend** to "curated data owns + cross-level/top-level order; `nav.menu` builds any single-home subtree." Fallback ladder: render + Build/Learn/Concepts from curated per-root link arrays and restrict `nav.menu` to `/reference/` + only — preserves every URL decision, loses folder-derived depth. +3. **`choose-a-queue-provider` re-home** to Data & Persistence is reader-endorsed for co-location + with `kv-queues-cron`, but is arguably a Background-Processing concern. + **Recommendation: Data & Persistence** (co-location wins; the move is one front-matter line to + reverse). `queue-kv-cron` → Data is unambiguous. +4. **`/quickstart/aspire/` new page** — the locked "Aspire hero-level" decision wants a hero CTA + landing. **Recommendation: author it** (thin page, small scope) rather than deep-linking into + `/orchestration-runtime/`. +5. **Loss of sidebar cross-listing is deliberate** (nav plugin enforces one home). Discoverability + re-lands entirely in the pillar-hub card-grid rewrites (Priority 1). **The hub rewrites are + load-bearing, not optional polish** — they ship in the same wave as S4. +6. **~113-page `order` sweep is broad and un-gated.** A wrong `order` mis-sequences a menu + silently. **Recommendation: add a lint** that fails any nav-rendered page missing `order`, and + any page with `oldUrl`/`redirectTo` not `unlisted`/`nav_hide`. +7. **Vento authoring landmines** (prior research): the literal word `function` inside a comp-tag + argument aborts the build; never run repo-wide `deno fmt`; pre-flight the recursive + `menu_item.vto` against the deepest paths (`/reference/cli/commands/`, + `/web-layer/how-to/customize-fresh-ui/`) with a real build. +8. **Reference count is 32, not "33/34".** `mcp` exists on disk but is unregistered (verified). Any + downstream doc or count built on "34 units" is wrong. diff --git a/docs/site/ai/agent-tooling.md b/docs/site/ai/agent-tooling.md new file mode 100644 index 000000000..fdce41b0f --- /dev/null +++ b/docs/site/ai/agent-tooling.md @@ -0,0 +1,209 @@ +--- +layout: layouts/base.vto +title: Agent tooling +description: What NetScript gives the coding agents that build your app — CLI, skills, the framework MCP server, and agent-readable docs. +templateEngine: [vento, md] +order: 5 +oldUrl: /capabilities/agent-tooling/ +--- + +# Agent tooling + +The rest of this pillar is about the AI you build *into* your app — the +[engine](/ai/engine/), the [MCP client stack](/ai/mcp/), +[durable chat](/ai/durable-chat/). This page is the mirror image: what NetScript +gives the coding agents (and the humans pairing with them) that build your app. +One vocabulary across three surfaces: + +- the `netscript` CLI performs direct, scriptable operations; +- installed skills explain which NetScript workflow to use and when to hand off to + the CLI; +- the MCP server returns compact framework-aware diagnostics, telemetry summaries, + and public docs. + +There is a fourth surface that is easy to miss: this documentation site is itself +built to be read by agents — every page has a Markdown twin, and the whole corpus +is published behind `llms.txt`. We cover that +[below](#reading-these-docs-as-an-agent). + +Prefer the CLI when a command already expresses the operation — it is easier to +reproduce in a terminal or CI job, and the +[CLI reference]({{ "cli:reference" |> xref |> url }}) is the task-oriented map of +what exists. Use MCP for interactive investigation, especially when one +framework-aware tool replaces several telemetry queries or when an agent needs to +discover a document before reading it. + +{{ comp callout { type: "note", title: "Two different MCP surfaces — don't conflate them" } }} +This page documents the NetScript MCP <strong>server</strong>: a standalone stdio +server that gives a coding agent diagnostics, telemetry summaries, and doc search +<em>about</em> the project it is editing. That is a separate surface from the +<a href="/ai/mcp/"><code>@netscript/ai/mcp</code> client stack</a>, which wires +<em>external</em> MCP servers <em>into</em> your product's own agent loop. Server +in, client out. The server's full per-tool contracts live in the +<a href="/reference/mcp/"><code>@netscript/mcp</code> reference</a>. +{{ /comp }} + +## Install into a project + +```bash +netscript agent init +``` + +Host detection installs the matching files. If neither host directory exists, +NetScript prepares both hosts. Use `--host claude`, `--host vscode`, or +`--host all` to select explicitly. + +| Host | Files written | +| ----------- | ---------------------------------------------------------------------------------------------------- | +| Claude Code | `.mcp.json`, NetScript skills under `.claude/skills/`, and a marked NetScript section in `AGENTS.md` | +| VS Code | `.vscode/mcp.json` | + +The generated MCP configuration runs `netscript agent mcp` for the current +project. Re-running `agent init` is idempotent: unchanged files are left alone, +and existing host configuration is preserved alongside the `netscript` server +entry. + +The host command includes the absolute project `deno.json` path. Deno 2.9 +normally holds newly published registry versions behind a 24-hour minimum +dependency age; the generated JSR workspace keeps that policy while excluding +only exact-version packages in the matching NetScript release train. Loading the +project configuration explicitly lets a newly released `@netscript/cli` MCP +server start immediately without changing the age policy for third-party +dependencies. + +## Run the server + +The generated host configuration runs `netscript agent mcp`, which starts the MCP +server over standard input/output. Its flags: + +| Flag | Purpose | +| ----------------------- | --------------------------------------------------------------------------------------------- | +| `--project-root <path>` | NetScript project root used for execution and doctor flows. `agent init` writes this for you. | +| `--endpoint <url>` | Telemetry endpoint URL; overrides discovery (below). Only `http:` and `https:` are accepted. | +| `--docs-root <path>` | Public documentation root for the docs tools; overrides the default corpus (below). | + +## What the server exposes + +Thirteen tools, every one returning a bounded structured result. Grouped by what +an agent is trying to do: + +- **Read the running app** — seven telemetry read models: `get_app_status`, + `list_runs`, `get_run`, `get_recent_errors`, `get_last_job_result`, + `analyze_service_performance`, and `analyze_db_bottlenecks`. Each replaces a + handful of raw telemetry queries with one aggregate answer. +- **Diagnose the project** — `doctor` aggregates telemetry reachability, Aspire + markers, project wiring, and plugin diagnostics into one verdict; the plugin + slice has a direct twin in `netscript plugin doctor`. +- **Search the docs** — `search_docs`, `list_docs`, and `get_doc` form a + search-to-get funnel over the documentation corpus (next section). +- **Bridge to the CLI** — `list_commands` discovers the current command tree + (the machine-readable twin of `netscript --help`), and `execute_command` runs + an allowlisted `netscript …` command with policy checking, timeout handling, + and a bounded output tail. + +We keep the per-tool schemas, output bounds, and the full `execute_command` +policy in the [`@netscript/mcp` reference]({{ "ref:mcp" |> xref |> url }}) rather +than restating them here; for the commands themselves, the +[CLI reference]({{ "cli:reference" |> xref |> url }}) is the cheat-sheet. + +## Documentation corpus + +Without an override, `list_docs`, `search_docs`, and `get_doc` index the +documentation shipped with the installed `@netscript/mcp` package (one document +under the `mcp` slug). Point the server at a richer corpus — a project docs +folder or a checkout of this site — with `--docs-root <path>` or the +`NETSCRIPT_DOCS_ROOT` environment variable. A configured path that does not exist +returns a structured `docs_corpus_not_found` error naming the missing path; the +server never silently reports an empty corpus. + +## Token-efficient use + +Tool inputs cap result counts, and the server truncates oversized results and +command output. Start with the narrowest filter that answers the question. For +documentation, use the search-to-get funnel: call `search_docs`, choose a slug, +then call `get_doc` for that document or section. + +## Data boundary + +The MCP server reads NetScript telemetry, project metadata and generated +registries used for diagnostics, and public documentation. It does not return +project source code, environment-variable values, credentials, or secrets. + +Telemetry requests go only to the resolved dashboard endpoint. Discovery uses, in +order, the `--endpoint` option, `NETSCRIPT_TELEMETRY_ENDPOINT`, +`ASPIRE_DASHBOARD_PORT`, and the local default `http://localhost:18888`. + +`execute_command` is default-deny. Explicit rules allow selected database, +generation, contract, service-read, plugin, and UI commands. Deny rules take +priority; deployment, project initialization, marketplace operations, database +reset, plugin removal, and every unmatched command are rejected with a structured +denial before a process is started. The rule-by-rule policy lives in the +[`@netscript/mcp` reference]({{ "ref:mcp" |> xref |> url }}). + +## Troubleshooting + +Start with the `doctor` tool: it aggregates four check families — `telemetry`, +`aspire`, `project`, and `plugins` — into one verdict. Each check carries a +`pass`, `warn`, or `fail` status, and warnings and failures may include a +suggested fix. Reading the result: + +- **`telemetry` warns or fails** — no reachable telemetry endpoint. Verify the + app is running, then check the discovery chain: `--endpoint`, + `NETSCRIPT_TELEMETRY_ENDPOINT`, `ASPIRE_DASHBOARD_PORT`, and the local default + `http://localhost:18888`. Telemetry tools still respond while the endpoint is + down — nothing crashes: `get_app_status` reports `status: "warn"` with zero + counts, the list and analytics tools (`list_runs`, `get_recent_errors`, + `analyze_service_performance`, `analyze_db_bottlenecks`, …) return their + ordinary empty or zero-valued results, and `get_run` returns a structured + `run_not_found` error. +- **`aspire` or `project` warns** — the project root is wrong or the workspace is + missing expected markers. Confirm the `--project-root` written into your host + configuration points at the project (re-run `netscript agent init` after moving + a project). +- **`plugins` warns or fails** — plugin diagnostics found an issue; the + equivalent direct command is `netscript plugin doctor`. +- **Docs tools return `docs_corpus_not_found`** — the configured `--docs-root` / + `NETSCRIPT_DOCS_ROOT` path does not exist; fix the path or remove the override + to fall back to the packaged corpus. +- **`execute_command` returns a denial** — the command did not match the + allowlist (see the data boundary above); run it directly in a terminal instead. + +## Reading these docs as an agent + +The site you are reading ships its own agent affordances, no MCP server +required: + +- **Every page has a Markdown twin.** The "View as Markdown" link near the top of + each page points at clean Markdown distilled from the rendered page — component + markup already resolved, links absolute — so an agent reads source-quality + text instead of parsing HTML chrome. The twin lives at the page URL plus + `index.md`: this page's twin is [`/ai/agent-tooling/index.md`](/ai/agent-tooling/index.md), + and the same suffix works on any page. +- **A tiered `llms.txt`.** [`/llms.txt`](/llms.txt) is the index tier: every real + page as an absolute canonical link with a one-line summary, grouped by section, + plus a short note telling agents how to reach the twins. + [`/llms-full.txt`](/llms-full.txt) is the full tier: every page's Markdown twin + concatenated into one corpus for bulk ingestion. + +The ladder, cheapest first: start at `llms.txt`, fetch the twin of the one page +you need, and reach for `llms-full.txt` only when you genuinely want everything. +And the two halves meet: point the MCP server's `--docs-root` at a checkout of +this site and `search_docs` runs over the same corpus. + +## Run the protocol smoke + +```bash +deno test --allow-all packages/cli/e2e/tests/agent/agent-mcp-stdio_test.ts +``` + +The smoke starts the public CLI binary, initializes MCP over stdio, verifies the +13-tool catalog, and checks docs, diagnostics, unreachable telemetry, and command +denial behavior. + +## Where to go next + +{{ comp.cardsGrid({ columns: 3, cards: [ + { eyebrow: "Reference", title: "@netscript/mcp", body: "Per-tool contracts, output bounds, and the execute_command policy of the agent-facing MCP server.", href: resolveXref("ref:mcp").href, icon: "R" }, + { eyebrow: "Reference", title: "CLI reference", body: "The task-oriented cheat-sheet for every netscript command, including agent init and agent mcp.", href: resolveXref("cli:reference").href, icon: "C" }, + { eyebrow: "Guide", title: "MCP client stack", body: "The other MCP surface: consume external MCP servers as tools inside your own product's agents.", href: "/ai/mcp/", icon: "M" } +] }) }} diff --git a/docs/site/ai/chat-ui.md b/docs/site/ai/chat-ui.md index 08760fdac..8eec14582 100644 --- a/docs/site/ai/chat-ui.md +++ b/docs/site/ai/chat-ui.md @@ -4,6 +4,7 @@ title: Chat UI templateEngine: [vento, md] prev: { label: "Durable chat", href: "/ai/durable-chat/" } next: { label: "AI engine", href: "/ai/engine/" } +order: 3 --- # Chat UI @@ -17,7 +18,7 @@ exports (`.`, `./primitives`, `./interactive`, `./registry`) carry no chat symbo chat surface is a set of files the NetScript CLI **copies into your app**, where you own and edit them like any other source file. This is the same copy-source model as the rest of the Fresh UI — see [Fresh UI & design](/capabilities/fresh-ui/) and -[Customize Fresh UI](/how-to/customize-fresh-ui/). +[Customize Fresh UI](/web-layer/how-to/customize-fresh-ui/). {{ comp callout { type: "important", title: "Copy-registry, not a dependency" } }} The chat components are delivered through the <code>ai</code> collection in the fresh-ui @@ -133,7 +134,7 @@ package boundary to work around. { title: "Do — customize Fresh UI", body: "How the CLI copies components into your app and how you own them afterward.", - href: "/how-to/customize-fresh-ui/", + href: "/web-layer/how-to/customize-fresh-ui/", icon: "◆" }, { diff --git a/docs/site/ai/durable-chat.md b/docs/site/ai/durable-chat.md index ed68a2497..bb859650d 100644 --- a/docs/site/ai/durable-chat.md +++ b/docs/site/ai/durable-chat.md @@ -4,6 +4,7 @@ title: Durable chat templateEngine: [vento, md] prev: { label: "AI", href: "/ai/" } next: { label: "Chat UI", href: "/ai/chat-ui/" } +order: 2 --- # Durable chat diff --git a/docs/site/ai/engine.md b/docs/site/ai/engine.md index 8ebbacf7a..f610ee519 100644 --- a/docs/site/ai/engine.md +++ b/docs/site/ai/engine.md @@ -4,15 +4,19 @@ title: AI engine templateEngine: [vento, md] prev: { label: "Chat UI", href: "/ai/chat-ui/" } next: null +order: 4 --- # AI engine -`@netscript/ai` is the provider-agnostic AI engine at the design center of the stack: a -zero-`@netscript/*`-dependency, ports-and-adapters core that owns the domain -vocabulary, the capability seams, the model registries, the tool system, the agent -loop, MCP transports, and opt-in provider adapters. It wraps `@tanstack/ai*` and -`@standard-schema/spec` and adds no schema DSL of its own. +This is the **engine guide** — how the pieces of `@netscript/ai` fit together and +when to reach for each one. `@netscript/ai` is the provider-agnostic AI engine at +the design center of the stack: a zero-`@netscript/*`-dependency, ports-and-adapters +core that owns the domain vocabulary, the capability seams, the model registries, +the tool system, the agent loop, MCP transports, and opt-in provider adapters. It +wraps `@tanstack/ai*` and `@standard-schema/spec` and adds no schema DSL of its own. +We keep this page at the "which piece, and why" altitude; the exact symbol tables +live in the generated reference, linked at the end. {{ comp callout { type: "note", title: "Published in " + releaseVersion } }} <code>@netscript/ai</code> is <strong>published on JSR</strong> and installs today: @@ -23,13 +27,13 @@ published <strong>{{ releaseVersion }}</strong> surface. The engine carries zero each stand alone and neither requires it. {{ /comp }} -## The export map +## The map: which subpath answers which question {{ comp.apiTable({ caption: "@netscript/ai — subpath exports", columns: ["Subpath", "Purpose"], rows: [ - ["<code>.</code>", "Composition root + model / embedding / vision registries."], + ["<code>.</code>", "Runtime wiring + the model / embedding / vision registries."], ["<code>./contracts</code>", "Domain vocabulary — pure types and the error hierarchy."], ["<code>./ports</code>", "Capability seams (hexagonal) plus default port / registry factories."], ["<code>./tools</code>", "Tool definition / validation / registry, plus the built-in <code>render_ui</code> contract."], @@ -44,156 +48,112 @@ each stand alone and neither requires it. ] }) }} -## The runtime and registries - -`createAiRuntime(config)` is a **pure wiring** function: it resolves every capability -port, defaulting each to a no-op or throwing implementation, with no IO and no global -mutation. `getAiRuntime(config?)` is the process singleton (shaped like `getKv()`), with -`resetAiRuntime()` and `isAiRuntimeInitialized()` alongside. The resolved `AiRuntime` -exposes `telemetry`, `tools`, `embeddings`, `vision`, `mcp`, `skills`, `agentLoop`, and -`memory`, plus `getModelProvider()` / `getModel()`. `AiRuntimeConfig` is the same field -set, all optional, so you inject only the ports you have real implementations for. - -Three registries sit at the root, each with the same register / get / list / reset -shape: - -{{ comp.apiTable({ - caption: "Model, embedding, and vision registries", - rows: [ - { name: "registerModelProvider / getModelProvider / getModel / listModelProviders / resetModelRegistry", type: "model", desc: "`getModel(ref, config?)` resolves a `ModelHandle {descriptor, providerId}` from a `ModelRef`." }, - { name: "registerEmbeddingProvider / getEmbeddingProvider / listEmbeddingProviders / resetEmbeddingRegistry", type: "embedding", desc: "The embedding provider seam." }, - { name: "registerVisionProvider / getVisionProvider / listVisionProviders / resetVisionRegistry", type: "vision", desc: "The vision provider seam." } - ] -}) }} - -Models are addressed by reference. A `ModelRef` is `string | ModelSelector`; the string -form is `"<provider>:<model>"`, e.g. `"anthropic:claude-sonnet-4-5"`. Errors are a small -hierarchy rooted at `AiError`: `AiNotConfiguredError` (`.capability`) and -`ModelProviderNotFoundError` (`.providerId`, `.availableProviders`). +A useful way to read that table: the top half is the engine (vocabulary, seams, +tools, loop), the bottom half is what you opt into (providers) and how you test +without them (fakes). -## Contracts — the domain vocabulary - -`@netscript/ai/contracts` is pure types with no IO — the vocabulary every layer above -the engine speaks. - -- **Messages.** `Message { role, content, name?, toolCallId?, toolCalls? }` with - `MessageRole = "system" | "user" | "assistant" | "tool"` and - `MessageContent = string | readonly ContentPart[]`. -- **Multimodal content.** `ContentPart` is a `Text | Image | Audio | Video | Document` - union; a `ContentSource` is either a base64 `DataContentSource` (with `mimeType`) or a - `UrlContentSource`; `ContentModality = "text" | "image" | "audio" | "video" | - "document"`. -- **Models.** `ModelDescriptor`, `ModelCapabilities` (`streaming?`, `tools?`, `vision?`, - `embeddings?`, `inputModalities?`, token maxima), and `ModelSelector { provider, - model }`. -- **Tools.** `ToolDescriptor`, `ToolParameters`, `ToolCall` (with a raw-JSON `arguments` - string and a `ToolCallState`), and `ToolResult`. - -The wire vocabulary the whole stack streams is the **`AgentChunk` union**: +## The runtime and registries -{{ comp.apiTable({ - caption: "AgentChunk — the streaming chunk union (7 discriminants)", - rows: [ - { name: "TextChunk", type: "text", desc: "A span of assistant text." }, - { name: "ToolCallChunk", type: "tool-call", desc: "A tool invocation (streamed input)." }, - { name: "ToolResultChunk", type: "tool-result", desc: "A tool result." }, - { name: "MessageChunk", type: "message", desc: "A completed message boundary." }, - { name: "UsageChunk", type: "usage", desc: "Token / cost usage (`Usage`, with prompt / completion detail and optional cost breakdown)." }, - { name: "ErrorChunk", type: "error", desc: "A terminal error before the final done." }, - { name: "DoneChunk", type: "done", desc: "The final chunk of a run." } - ] -}) }} +`createAiRuntime(config)` is a **pure wiring** function: it resolves every +capability port, defaulting each to a no-op or throwing implementation, with no IO +and no global mutation. `getAiRuntime(config?)` is the process singleton (shaped +like `getKv()`), with `resetAiRuntime()` and `isAiRuntimeInitialized()` alongside. +Use `createAiRuntime` when you want an isolated instance (tests, one runtime per +tenant); use `getAiRuntime` when the process should share one. `AiRuntimeConfig` +mirrors the resolved runtime field-for-field, all optional, so you inject only the +ports you have real implementations for — everything else stays a safe default +until you need it. + +Three registries sit at the root — models, embeddings, and vision — each with the +same register / get / list / reset shape. Models are addressed by reference: a +`ModelRef` is `string | ModelSelector`, and the string form is +`"<provider>:<model>"`, e.g. `"anthropic:claude-sonnet-4-5"`. Errors are a small +hierarchy rooted at `AiError`; the two you will actually catch are +`AiNotConfiguredError` (you called a capability whose port was never injected) and +`ModelProviderNotFoundError` (the reference names a provider that never +registered). + +## Contracts — the shared vocabulary + +`@netscript/ai/contracts` is pure types with no IO — the vocabulary every layer +above the engine speaks. It carries the `Message` / `MessageRole` conversation +model, the multimodal `ContentPart` union (text, image, audio, video, document, +each backed by a base64 or URL `ContentSource`), the model descriptors and +capability flags, and the tool-call shapes. + +The one contract worth internalizing before anything else is the **`AgentChunk` +union** — the wire vocabulary the whole stack streams. A run yields text spans, +tool calls, tool results, message boundaries, usage, and errors as discriminated +chunks, always terminating in a final `done` chunk. The durable-chat runtime +persists these chunks and the chat UI renders them, which is why the layers compose +without adapters: they all speak `AgentChunk`. The vocabulary also carries the generative-UI contract: `RENDER_UI_TOOL_NAME = "render_ui"`, `RenderUiResult`, and a `UiResource` whose `uri` is a `ui://` string (mirroring the MCP resource shape). -## Ports — the hexagonal seams +## Ports — the seams you wire -`@netscript/ai/ports` exposes the capability interfaces plus their registry and default -factories: `TelemetryPort`, `ToolRegistryPort`, `EmbeddingProviderPort`, -`VisionProviderPort`, `McpTransportPort`, `SkillLoaderPort`, `AgentLoopPort`, -`AgentMemoryPort`, `ChatClientPort`, `ChatModelProviderPort`, and `ModelProviderPort`. -Two are worth calling out: +`@netscript/ai/ports` exposes the capability interfaces — telemetry, tool +registry, embeddings, vision, MCP transport, skill loading, the agent loop, +memory, and the chat/model provider seams — plus their registry and default +factories. Two are worth calling out because they shape how you wire things: -- **`ModelProviderPort`** — `{ id, listModels(), getModel(), supports(), - createChatClient?() }`. `createChatClient` is optional on the base, so discovery-only - providers can omit it; `ChatModelProviderPort { id, createChatClient(modelId) }` is - the narrow seam the agent loop injects. -- **`AgentMemoryPort`** — `append(threadId, message)` and `load(threadId)` are the base; - **`recall?(threadId, query)` is optional and `undefined` by default**. There is no - built-in semantic recall — callers must guard `recall` and fall back to `load`. - -`ChatClientPort.stream()` yields `ChatClientEvent`s -(`ChatTextEvent | ChatToolCallEvent | ChatFinishEvent | ChatErrorEvent`) with a -`ChatFinishReason` of `stop | length | tool-calls | content-filter | error | unknown`. +- **`ModelProviderPort`** covers discovery (`listModels()`, `getModel()`, + `supports()`), and `createChatClient?()` is optional on it — so a discovery-only + provider can omit chat entirely. The agent loop injects the narrower + `ChatModelProviderPort`, which requires `createChatClient(modelId)`. +- **`AgentMemoryPort`** — `append(threadId, message)` and `load(threadId)` are the + base; **`recall?(threadId, query)` is optional and `undefined` by default**. + There is no built-in semantic recall — guard `recall` and fall back to `load`. ## Tools — Standard Schema, no DSL The tool system validates with **Standard Schema**, so you bring any conforming -validator (zod, valibot, arktype, or hand-rolled) — the core adds no schema language. - -- **`defineAiTool(name)`** returns a builder: `.describe()`, `.parameters(jsonSchema)`, - `.input(schema)`, terminating in either `.server(handler)` or `.client()` (a deferred - tool, e.g. `render_ui`). -- **`createToolRegistry(defs?)`** implements `ToolRegistryPort`: `.define()`, - `.getDefinition()`, `.listDefinitions()`, and `.dispatch(name, input, ctx?)`, which - validates input and throws `ToolNotFoundError` / `ToolInputValidationError`. +validator (zod, valibot, arktype, or hand-rolled) — the core adds no schema +language. + +- **`defineAiTool(name)`** returns a builder: `.describe()`, + `.parameters(jsonSchema)`, `.input(schema)`, terminating in either + `.server(handler)` or `.client()` (a deferred tool, e.g. `render_ui`). +- **`createToolRegistry(defs?)`** implements `ToolRegistryPort`; its + `.dispatch(name, input, ctx?)` validates input before your handler runs and + throws `ToolNotFoundError` / `ToolInputValidationError` when it should. - **`renderUiTool`** is the built-in `render_ui` wire contract: schema-only and - **client-deferred** (`result.deferred === true`). The engine runs **no renderer** — - rendering is the [chat UI's](/ai/chat-ui/) job. `AiToolExecutionKind` is - `"server" | "client"`. + **client-deferred** (`result.deferred === true`). The engine runs **no + renderer** — rendering is the [chat UI's](/ai/chat-ui/) job. ## The agent loop `createAgentLoop(deps)` builds the loop from injected collaborators: a required `modelProvider: ChatModelProviderPort`, plus optional `tools`, `history`, and -`defaultMaxSteps`. `loop.run(input, options?)` returns an `AsyncIterable<AgentChunk>`; -`input` is `{ model, messages, tools?, system? }` and `options` is `{ signal?, -maxSteps? }`. The loop exposes `loop.state` and `loop.stop()`. +`defaultMaxSteps`. `loop.run(input, options?)` returns an +`AsyncIterable<AgentChunk>`; the loop exposes `loop.state` and `loop.stop()`. -{{ comp.apiTable({ - caption: "Agent loop — states and defaults", - rows: [ - { name: "AgentLoopState", type: "\"idle\" | \"running\" | \"awaiting-tool\" | \"done\" | \"aborted\" | \"errored\"", desc: "`isTerminalState()` narrows to `TerminalAgentLoopState`." }, - { name: "slidingWindowHistory({ maxMessages?, preserveSystem? })", type: "HistoryStrategy", desc: "The built-in history strategy; `DEFAULT_HISTORY_WINDOW = 20`." }, - { name: "DEFAULT_MAX_STEPS", type: "8", desc: "Tool-calling steps before the loop must settle." }, - { name: "AgentMaxStepsExceededError", type: "error (.maxSteps)", desc: "Thrown when `maxSteps` is hit without a final answer; the run settles `errored`, yields an `error` chunk, then a final `done`." } - ] -}) }} +The defaults are deliberately conservative: the built-in +`slidingWindowHistory` strategy keeps `DEFAULT_HISTORY_WINDOW = 20` messages, and +`DEFAULT_MAX_STEPS = 8` bounds how many tool-calling steps a run may take before +it must settle. Hitting that bound throws `AgentMaxStepsExceededError` inside the +run — the loop settles `errored`, yields an `error` chunk, then the final `done`, +so a consumer never hangs on an unterminated stream. ## MCP transports -`@netscript/ai/mcp` adapts remote Model Context Protocol servers. -`createMcpTransport(config)` takes a discriminated config — -`{ kind: "stdio" } & StdioMcpTransportConfig` or `{ kind: "streamable-http" } & -StreamableHttpMcpTransportConfig` — and returns an `McpTransportPort`. -`registerMcpTools(registry, transport)` surfaces the remote tools into a registry and -returns a registration whose `.stop()` detaches. The Streamable-HTTP transport is -reconnectable with backoff (`McpConnectionState = disconnected | connecting | connected -| reconnecting | closed`). - -Auth is injected by the app composition root — `McpAuthConfig` is -`{ mode: "none" }`, `{ mode: "api-token", token, headerName?, scheme? }`, or -`{ mode: "oauth", accessToken, tokenType? }`. +`@netscript/ai/mcp` adapts remote Model Context Protocol servers into the same +tool registry the loop dispatches. `createMcpTransport(config)` takes a +discriminated config (`kind: "stdio"` or `kind: "streamable-http"`) and returns an +`McpTransportPort`; `registerMcpTools(registry, transport)` surfaces the remote +tools and returns a registration whose `.stop()` detaches. The Streamable-HTTP +transport reconnects with backoff, and auth (`none` / `api-token` / `oauth`) is +injected by your app's startup wiring rather than hardcoded. The full client +story — pooling multiple servers, auth modes, rendering `ui://` resources — is +the [MCP guide](/ai/mcp/). ## Provider adapters — opt-in side-effect imports -Providers self-register on import, mirroring `@netscript/kv/redis`. The base engine -pulls **no** provider SDK; you opt in per provider. - -{{ comp.apiTable({ - caption: "Provider adapters (import to self-register)", - rows: [ - { name: "@netscript/ai/anthropic", type: "chat", desc: "Registers `\"anthropic\"` + `AnthropicModelProvider`; catalog taken verbatim from `@tanstack/ai-anthropic`. Config `{ apiKey? (→ ANTHROPIC_API_KEY), baseURL? }`; cancellation via `stream(_, { signal })`." }, - { name: "@netscript/ai/openai-compatible", type: "chat", desc: "Registers `\"openai-compatible\"` + `OpenAiCompatibleModelProvider`. No fixed catalog — optimistic `supports()` when `models` is unset (the remote endpoint owns its catalog); throws `AiNotConfiguredError` if `baseURL` / `apiKey` are missing. Config `{ baseURL?, apiKey?, models?, api?, name? }`, `api = \"chat-completions\" | \"responses\"`." }, - { name: "@netscript/ai/openrouter", type: "chat", desc: "Registers `\"openrouter\"` + `OpenRouterModelProvider` over the OpenAI-compatible base (reuses `@tanstack/ai-openai`, no new dependency). Config `{ apiKey? (→ OPENROUTER_API_KEY), baseURL?, models?, reasoningEffort? }`; `reasoningEffort` is `\"low\" | \"medium\" | \"high\"`, normalized to the OpenRouter `{ reasoning: { effort } }` wire option via `openRouterReasoningModelOptions`." }, - { name: "@netscript/ai/ollama", type: "chat", desc: "Registers `\"ollama\"` + `OllamaModelProvider` over the OpenAI-compatible base for a local endpoint. Config `{ host? (→ DEFAULT_OLLAMA_HOST), models?, reachability?, fetch? }`; runs a `ReachabilityPort` preflight against the local host — ships `createHttpReachabilityPort` / `HttpReachabilityAdapter` (or `createAssumeReachablePort` to skip)." }, - { name: "@netscript/ai/openai-embeddings", type: "embedding + vision", desc: "Registers `\"openai-embeddings\"` for both seams: `.embed()` (`/embeddings`) and `.analyze()` (`/chat/completions`). Defaults `text-embedding-3-small` / `gpt-4o-mini`." } - ] -}) }} - -An import is all it takes — the provider is then discoverable by its id: +Providers self-register on import, mirroring `@netscript/kv/redis`. The base +engine pulls **no** provider SDK; you opt in per provider, and an import is all it +takes: ```ts import "@netscript/ai/anthropic"; // self-registers the "anthropic" provider @@ -201,22 +161,41 @@ import "@netscript/ai/anthropic"; // self-registers the "anthropic" provider const model = await getModel("anthropic:claude-sonnet-4-5"); ``` +Choosing between them is mostly a question of where your models live: + +- **`./anthropic`** talks to Anthropic directly, catalog taken verbatim from + `@tanstack/ai-anthropic`, `apiKey` defaulting to `ANTHROPIC_API_KEY`. +- **`./openai-compatible`** is the workhorse for any endpoint that speaks the + OpenAI API: no fixed catalog (the remote endpoint owns its model list), and it + throws `AiNotConfiguredError` rather than guessing when `baseURL` / `apiKey` + are missing. +- **`./openrouter`** builds on the OpenAI-compatible base for OpenRouter (key + from `OPENROUTER_API_KEY`) and adds a `reasoningEffort` option + (`"low" | "medium" | "high"`) normalized to OpenRouter's wire format. +- **`./ollama`** builds on the same base for a local endpoint and runs a + reachability preflight against the host first — so a missing local daemon fails + fast instead of at the first token. +- **`./openai-embeddings`** registers for the embedding and vision seams, not + chat: `.embed()` and `.analyze()`. + +Per-provider config fields and defaults are enumerated in the +[generated reference]({{ "ref:ai" |> xref |> url }}). + ## Testing — deterministic fakes -`@netscript/ai/testing` supplies port fakes so an agent or tool can be exercised without -a network: `createFakeChatModelProvider(id, turns)`, `createFakeAgentLoop(chunks)`, -`createFakeAgentMemory({ recall? })`, `createFakeModelProvider`, -`createFakeEmbeddingProvider(vector)`, `createFakeVisionProvider(text)`, -`createInMemoryToolRegistry()`, and `createFakeTelemetryPort()` (whose `.records` capture -emitted telemetry). +`@netscript/ai/testing` supplies port fakes so an agent or tool can be exercised +without a network: fake chat model providers and agent loops that replay scripted +turns and chunks, fake memory, embedding, and vision ports, an in-memory tool +registry, and `createFakeTelemetryPort()` (whose `.records` capture emitted +telemetry). The full factory list lives in the generated reference. ## Wiring tool and agent registries -Today you wire the engine's registries **directly** in your composition root: +Today you wire the engine's registries **directly** at app startup: `createToolRegistry(defs?)` from `@netscript/ai/tools` for tools and -`createAgentLoop(deps)` from `@netscript/ai/agent` for agent loops (both documented -above). No codegen step sits between your files and the runtime — you register against -the engine factories yourself. +`createAgentLoop(deps)` from `@netscript/ai/agent` for agent loops. No codegen +step sits between your files and the runtime — you register against the engine +factories yourself. {{ comp callout { type: "note", title: "Not in this release: netscript generate ai" } }} A <code>netscript generate ai</code> codegen — compiling app-owned tool and agent files @@ -226,31 +205,19 @@ the CLI today</strong>. The shipped <code>netscript generate</code> targets are target lands, register tools and agent loops against the engine factories directly. {{ /comp }} -## Reference - -{{ comp.featureGrid({ items: [ - { - title: "Look up — @netscript/ai", - body: "The generated reference for the engine package: runtime, contracts, ports, tools, agent loop, MCP transports, and provider adapters.", - href: "/reference/ai/", - icon: "≡" - }, - { - title: "Back — the AI overview", - body: "The stack story, the two planes, and the plugin thinness laws the engine anchors.", - href: "/ai/", - icon: "←" - }, - { - title: "Durable chat", - body: "The self-contained Fresh runtime for a durable AI chat — composes with this engine but does not require it.", - href: "/ai/durable-chat/", - icon: "◆" - }, - { - title: "Chat UI", - body: "The copy-registry components that render an agent transcript, including render_ui targets.", - href: "/ai/chat-ui/", - icon: "◆" - } -] }) }} +## Where the exact tables live + +This guide stops at the altitude of "which piece, and why". For the complete, +generated symbol tables — every export, signature, and config field — use: + +- [`@netscript/ai`]({{ "ref:ai" |> xref |> url }}) — the engine itself: runtime, + contracts, ports, tools, agent loop, MCP transports, provider adapters. +- [`@netscript/plugin-ai`]({{ "ref:plugin-ai" |> xref |> url }}) — the thin AI + plugin delivery shell. +- [`@netscript/plugin-ai-core`]({{ "ref:plugin-ai-core" |> xref |> url }}) — the + plugin's reusable `/v1/ai` contract core. + +And for the rest of the stack: the [AI overview](/ai/) tells the layering story, +[durable chat](/ai/durable-chat/) covers the Fresh runtime, and the +[chat UI](/ai/chat-ui/) covers the components that render what this engine +streams. diff --git a/docs/site/how-to/build-a-durable-chat.md b/docs/site/ai/how-to/build-a-durable-chat.md similarity index 95% rename from docs/site/how-to/build-a-durable-chat.md rename to docs/site/ai/how-to/build-a-durable-chat.md index c3de26528..99d5138c9 100644 --- a/docs/site/how-to/build-a-durable-chat.md +++ b/docs/site/ai/how-to/build-a-durable-chat.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Build a durable chat templateEngine: [vento, md] -prev: { label: "Customize Fresh UI", href: "/how-to/customize-fresh-ui/" } -next: { label: "Deploy", href: "/how-to/deploy/" } +order: 101 +oldUrl: /how-to/build-a-durable-chat/ --- # Build a durable chat @@ -152,7 +152,7 @@ const snapshot = await resolveChatSnapshot({ target: { sessionId } }); Pass `snapshot` into the island as its initial state. `renderParts` here is the **transport** shape (`text` | `tool`) — the minimal reducer output. The rich presentation parts (charts, tables) come from `parseBlocks` in the UI layer; see -[Customize Fresh UI](/how-to/customize-fresh-ui/) and the chat tutorial. +[Customize Fresh UI](/web-layer/how-to/customize-fresh-ui/) and the chat tutorial. ## 4. The client island @@ -220,13 +220,13 @@ Two <code>RenderPart</code> types exist and must not be conflated. <code>@netscr ## Next steps - Walk it end to end in the [AI Chat tutorial](/tutorials/chat/). -- Render rich blocks and citation chips: [Customize Fresh UI](/how-to/customize-fresh-ui/). +- Render rich blocks and citation chips: [Customize Fresh UI](/web-layer/how-to/customize-fresh-ui/). - The list/board/table live-data plane is different — see - [Publish a durable stream](/how-to/publish-a-durable-stream/) and + [Publish a durable stream](/durable-workflows/how-to/publish-a-durable-stream/) and [Live Dashboard, chapter 05](/tutorials/live-dashboard/05-live-stream/). - Look up exact signatures in the [fresh reference](/reference/fresh/). {{ comp.nextPrev({ - prev: { label: "Customize Fresh UI", href: "/how-to/customize-fresh-ui/" }, - next: { label: "Deploy", href: "/how-to/deploy/" } + prev: { label: "Customize Fresh UI", href: "/web-layer/how-to/customize-fresh-ui/" }, + next: { label: "Deploy", href: "/orchestration-runtime/how-to/deploy/" } }) }} diff --git a/docs/site/ai/how-to/index.md b/docs/site/ai/how-to/index.md new file mode 100644 index 000000000..3b97cb25d --- /dev/null +++ b/docs/site/ai/how-to/index.md @@ -0,0 +1,12 @@ +--- +layout: layouts/base.vto +title: Recipes +templateEngine: [vento, md] +order: 100 +--- + +# AI & Agents — recipes + +Task-oriented recipes for this area. Each one solves a single concrete problem and assumes you know +the basics from the guides above it in the sidebar. The full cross-area catalog lives at +[All how-to recipes]({{ "howto:index" |> xref |> url }}). diff --git a/docs/site/ai/index.md b/docs/site/ai/index.md index 3959136c7..4242c073a 100644 --- a/docs/site/ai/index.md +++ b/docs/site/ai/index.md @@ -148,3 +148,12 @@ seams those products are made of. { eyebrow: "API Reference", title: "@netscript/plugin-ai", body: "Generated symbols for the thin AI plugin delivery shell.", href: "/reference/plugin-ai/", icon: "R" }, { eyebrow: "API Reference", title: "@netscript/plugin-ai-core", body: "Generated symbols for the AI plugin's reusable contract and composition core.", href: "/reference/plugin-ai-core/", icon: "R" } ] }) }} + +## Learn, do, look up + +{{ comp.cardsGrid({ columns: 4, cards: [ + { eyebrow: "Learn", title: "AI chat tutorial", body: "A durable, tool-calling chat with MCP and live streaming.", href: resolveXref("tut:chat").href }, + { eyebrow: "Do", title: "Recipes", body: "Task-oriented recipes for this area, one problem each.", href: "/ai/how-to/" }, + { eyebrow: "Look up", title: "`@netscript/ai` reference", body: "Generated API reference. Related units: `plugin-ai`, `mcp`.", href: resolveXref("ref:ai").href }, + { eyebrow: "Understand", title: "The plugin system", body: "The design rationale behind this pillar.", href: resolveXref("explain:plugin-system").href }, +] }) }} diff --git a/docs/site/ai/mcp.md b/docs/site/ai/mcp.md index 51b7d9677..d2b3d7006 100644 --- a/docs/site/ai/mcp.md +++ b/docs/site/ai/mcp.md @@ -4,6 +4,7 @@ title: MCP templateEngine: [vento, md] prev: { label: "AI", href: "/ai/" } next: { label: "Durable chat", href: "/ai/durable-chat/" } +order: 1 --- # MCP @@ -22,7 +23,7 @@ coding agents that you run with <code>netscript agent mcp</code> and install wit <code>netscript agent init</code>. The server exposes framework-aware diagnostics, telemetry summaries, and doc search <em>about</em> your project; the client library on this page wires <em>remote</em> MCP servers <em>into</em> your product's agent loop. See -<a href="/capabilities/agent-tooling/">Agent tooling</a> and the +<a href="/ai/agent-tooling/">Agent tooling</a> and the <a href="/reference/mcp/"><code>@netscript/mcp</code> reference</a> for the server. {{ /comp }} @@ -143,7 +144,7 @@ Encore's MCP story points **inward**: it ships an MCP server that exposes a runn Encore backend's introspection surface to coding agents, so an agent can inspect and verify the backend it is editing. NetScript ships **both halves**. The inward half is the `netscript agent mcp` server (documented under -[Agent tooling](/capabilities/agent-tooling/) and the +[Agent tooling](/ai/agent-tooling/) and the [`@netscript/mcp` reference](/reference/mcp/)): a stdio server that gives a coding agent framework-aware diagnostics, telemetry summaries, and doc search over the project it is editing. The outward half — this page — is the `@netscript/ai/mcp` diff --git a/docs/site/how-to/add-a-task-runtime-adapter.md b/docs/site/background-processing/how-to/add-a-task-runtime-adapter.md similarity index 91% rename from docs/site/how-to/add-a-task-runtime-adapter.md rename to docs/site/background-processing/how-to/add-a-task-runtime-adapter.md index 16e969d01..3577157e1 100644 --- a/docs/site/how-to/add-a-task-runtime-adapter.md +++ b/docs/site/background-processing/how-to/add-a-task-runtime-adapter.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Add a task runtime adapter templateEngine: [vento, md] -prev: { label: "Roll out runtime overrides", href: "/how-to/roll-out-runtime-overrides/" } -next: { label: "Build a server-validated form", href: "/how-to/build-a-server-validated-form/" } +order: 103 +oldUrl: /how-to/add-a-task-runtime-adapter/ --- # Add a task runtime adapter @@ -181,11 +181,11 @@ adapter, which spawns `node ./tasks/render-invoice.mjs --invoice inv_123`, strea ## Next steps -- Restrict Deno task permissions with [Restrict worker task permissions](/how-to/restrict-worker-task-permissions/). -- See built-in runtime behavior in [Run a polyglot task](/how-to/run-a-polyglot-task/). +- Restrict Deno task permissions with [Restrict worker task permissions](/background-processing/how-to/restrict-worker-task-permissions/). +- See built-in runtime behavior in [Run a polyglot task](/background-processing/how-to/run-a-polyglot-task/). - Look up the executor surface in [workers reference](/reference/workers/). {{ comp.nextPrev({ - prev: { label: "Roll out runtime overrides", href: "/how-to/roll-out-runtime-overrides/" }, - next: { label: "Build a server-validated form", href: "/how-to/build-a-server-validated-form/" } + prev: { label: "Roll out runtime overrides", href: "/orchestration-runtime/how-to/roll-out-runtime-overrides/" }, + next: { label: "Build a server-validated form", href: "/web-layer/how-to/build-a-server-validated-form/" } }) }} diff --git a/docs/site/background-processing/how-to/index.md b/docs/site/background-processing/how-to/index.md new file mode 100644 index 000000000..13066c5d3 --- /dev/null +++ b/docs/site/background-processing/how-to/index.md @@ -0,0 +1,12 @@ +--- +layout: layouts/base.vto +title: Recipes +templateEngine: [vento, md] +order: 100 +--- + +# Background jobs — recipes + +Task-oriented recipes for this area. Each one solves a single concrete problem and assumes you know +the basics from the guides above it in the sidebar. The full cross-area catalog lives at +[All how-to recipes]({{ "howto:index" |> xref |> url }}). diff --git a/docs/site/how-to/restrict-worker-task-permissions.md b/docs/site/background-processing/how-to/restrict-worker-task-permissions.md similarity index 87% rename from docs/site/how-to/restrict-worker-task-permissions.md rename to docs/site/background-processing/how-to/restrict-worker-task-permissions.md index 09e7ffda2..07bfefbc4 100644 --- a/docs/site/how-to/restrict-worker-task-permissions.md +++ b/docs/site/background-processing/how-to/restrict-worker-task-permissions.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Restrict worker task permissions templateEngine: [vento, md] -prev: { label: "Publish a durable stream", href: "/how-to/publish-a-durable-stream/" } -next: { label: "How-to guides", href: "/how-to/" } +order: 104 +oldUrl: /how-to/restrict-worker-task-permissions/ --- # Restrict worker task permissions @@ -80,11 +80,11 @@ permissions unless the adapter adds its own sandbox. ## Next steps -- Tune process isolation with [Tune the worker runtime](/how-to/tune-worker-runtime/). -- Add a custom adapter with [Add a task runtime adapter](/how-to/add-a-task-runtime-adapter/). +- Tune process isolation with [Tune the worker runtime](/background-processing/how-to/tune-worker-runtime/). +- Add a custom adapter with [Add a task runtime adapter](/background-processing/how-to/add-a-task-runtime-adapter/). - Look up worker symbols in [workers reference](/reference/workers/). {{ comp.nextPrev({ - prev: { label: "Publish a durable stream", href: "/how-to/publish-a-durable-stream/" }, + prev: { label: "Publish a durable stream", href: "/durable-workflows/how-to/publish-a-durable-stream/" }, next: { label: "How-to guides", href: "/how-to/" } }) }} diff --git a/docs/site/how-to/run-a-polyglot-task.md b/docs/site/background-processing/how-to/run-a-polyglot-task.md similarity index 97% rename from docs/site/how-to/run-a-polyglot-task.md rename to docs/site/background-processing/how-to/run-a-polyglot-task.md index 3f274b0a8..191e9fea6 100644 --- a/docs/site/how-to/run-a-polyglot-task.md +++ b/docs/site/background-processing/how-to/run-a-polyglot-task.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Run a polyglot task templateEngine: [vento, md] -prev: { label: "Tune the worker runtime", href: "/how-to/tune-worker-runtime/" } -next: { label: "Graceful shutdown", href: "/how-to/graceful-shutdown/" } +order: 102 +oldUrl: /how-to/run-a-polyglot-task/ --- # Run a polyglot task @@ -182,4 +182,4 @@ NetScript is in alpha; the polyglot task API can still shift. Treat the import s {{ comp.xref({ key: "ref:workers" }) }} -{{ comp.nextPrev({ prev: { label: "Tune the worker runtime", href: "/how-to/tune-worker-runtime/" }, next: { label: "Graceful shutdown", href: "/how-to/graceful-shutdown/" } }) }} +{{ comp.nextPrev({ prev: { label: "Tune the worker runtime", href: "/background-processing/how-to/tune-worker-runtime/" }, next: { label: "Graceful shutdown", href: "/orchestration-runtime/how-to/graceful-shutdown/" } }) }} diff --git a/docs/site/how-to/tune-worker-runtime.md b/docs/site/background-processing/how-to/tune-worker-runtime.md similarity index 97% rename from docs/site/how-to/tune-worker-runtime.md rename to docs/site/background-processing/how-to/tune-worker-runtime.md index 21f8aeb8b..d1b41847a 100644 --- a/docs/site/how-to/tune-worker-runtime.md +++ b/docs/site/background-processing/how-to/tune-worker-runtime.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Tune the worker runtime templateEngine: [vento, md] -prev: { label: "Choose a queue provider", href: "/how-to/choose-a-queue-provider/" } -next: { label: "Run a polyglot task", href: "/how-to/run-a-polyglot-task/" } +order: 101 +oldUrl: /how-to/tune-worker-runtime/ --- # Tune the worker runtime @@ -271,7 +271,7 @@ slot for up to ~15 minutes of wall time before it gives up, and each retry <code>timeout</code> to the work, keep <code>maxRetries</code> small for slow tasks, and pair retries with an <code>idempotencyKey</code> on enqueue so a re-delivery after a forced restart does not double-charge. See -<a href="/how-to/choose-a-queue-provider/">Choose a queue provider</a> for delivery semantics. +<a href="/data-persistence/how-to/choose-a-queue-provider/">Choose a queue provider</a> for delivery semantics. {{ /comp }} ## See also @@ -287,6 +287,6 @@ does not double-charge. See {{ comp.xref({ key: "ref:workers" }) }} {{ comp.nextPrev({ - prev: { label: "Choose a queue provider", href: "/how-to/choose-a-queue-provider/" }, - next: { label: "Run a polyglot task", href: "/how-to/run-a-polyglot-task/" } + prev: { label: "Choose a queue provider", href: "/data-persistence/how-to/choose-a-queue-provider/" }, + next: { label: "Run a polyglot task", href: "/background-processing/how-to/run-a-polyglot-task/" } }) }} diff --git a/docs/site/background-processing/index.md b/docs/site/background-processing/index.md index 9cfeadc2d..1cd93b9ac 100644 --- a/docs/site/background-processing/index.md +++ b/docs/site/background-processing/index.md @@ -27,8 +27,17 @@ durable saga state model — for that, see [durable workflows](/durable-workflow {{ comp.cardsGrid({ columns: 3, cards: [ { eyebrow: "Overview & Concepts", title: "Workers and queues", body: "Worker tasks consume queued work with provider and runtime choices kept inside the leaf.", href: "/background-processing/workers/", icon: "O" }, { eyebrow: "Quickstart", title: "Polyglot transform", body: "Run a task step from the ERP Sync tutorial.", href: "/tutorials/erp-sync/03-polyglot-transform/", icon: "Q" }, - { eyebrow: "How-To", title: "Queue, KV, and cron", body: "Create the queue and scheduler loop used by background work.", href: "/how-to/queue-kv-cron/", icon: "H" }, - { eyebrow: "How-To", title: "Choose a queue provider", body: "Select the provider that fits your local and deployed runtime.", href: "/how-to/choose-a-queue-provider/", icon: "H" }, - { eyebrow: "How-To", title: "Run a polyglot task", body: "Execute work in a non-TypeScript task runtime.", href: "/how-to/run-a-polyglot-task/", icon: "H" }, + { eyebrow: "How-To", title: "Queue, KV, and cron", body: "Create the queue and scheduler loop used by background work.", href: "/data-persistence/how-to/queue-kv-cron/", icon: "H" }, + { eyebrow: "How-To", title: "Choose a queue provider", body: "Select the provider that fits your local and deployed runtime.", href: "/data-persistence/how-to/choose-a-queue-provider/", icon: "H" }, + { eyebrow: "How-To", title: "Run a polyglot task", body: "Execute work in a non-TypeScript task runtime.", href: "/background-processing/how-to/run-a-polyglot-task/", icon: "H" }, { eyebrow: "API Reference", title: "workers, queue, cron", body: "Generated symbols for task, queue, scheduler, and watcher packages.", href: "/reference/workers/", icon: "R" } ] }) }} + +## Learn, do, look up + +{{ comp.cardsGrid({ columns: 4, cards: [ + { eyebrow: "Learn", title: "ERP sync tutorial", body: "An import job, a polyglot transform, and a queue-driven cron.", href: resolveXref("tut:erp-sync").href }, + { eyebrow: "Do", title: "Recipes", body: "Task-oriented recipes for this area, one problem each.", href: "/background-processing/how-to/" }, + { eyebrow: "Look up", title: "`@netscript/workers` reference", body: "Generated API reference. Related units: `queue`, `cron`, `watchers`.", href: resolveXref("ref:workers").href }, + { eyebrow: "Understand", title: "The durability model", body: "The design rationale behind this pillar.", href: resolveXref("explain:durability-model").href }, +] }) }} diff --git a/docs/site/background-processing/polyglot-tasks.md b/docs/site/background-processing/polyglot-tasks.md index 6863cfa01..7370690d4 100644 --- a/docs/site/background-processing/polyglot-tasks.md +++ b/docs/site/background-processing/polyglot-tasks.md @@ -4,6 +4,7 @@ title: Polyglot tasks templateEngine: [vento, md] prev: { label: "Typed SDK & client", href: "/services-sdk/sdk/" } next: { label: "Runtime configuration", href: "/orchestration-runtime/runtime-config/" } +order: 2 --- # Polyglot tasks @@ -65,7 +66,7 @@ spawn entirely. { title: "Do — Run a polyglot task", body: "Task recipe: define a python (or shell) task, wire its entrypoint and permissions, and execute it through the runtime.", - href: "/how-to/run-a-polyglot-task/", + href: "/background-processing/how-to/run-a-polyglot-task/", icon: "◆" }, { @@ -230,7 +231,7 @@ in the workers reference. { title: "Do — Run a polyglot task", body: "Step-by-step recipe for a python and a shell task with explicit permissions and result capture.", - href: "/how-to/run-a-polyglot-task/", + href: "/background-processing/how-to/run-a-polyglot-task/", icon: "◆" }, { diff --git a/docs/site/background-processing/workers.md b/docs/site/background-processing/workers.md index 034121b47..141750ca6 100644 --- a/docs/site/background-processing/workers.md +++ b/docs/site/background-processing/workers.md @@ -4,6 +4,7 @@ title: Background jobs templateEngine: [vento, md] prev: { label: "Services & contracts", href: "/services-sdk/services/" } next: { label: "Durable sagas", href: "/durable-workflows/sagas/" } +order: 1 --- # Background jobs @@ -88,7 +89,7 @@ as a <a href="/durable-workflows/sagas/">durable saga</a>; jobs and sagas compos { title: "Do — Tune the worker runtime", body: "Recipe: pick the in-process / web-worker / subprocess runner, set WORKERS_CONCURRENCY, and choose a queue provider for your deployment.", - href: "/how-to/tune-worker-runtime/", + href: "/background-processing/how-to/tune-worker-runtime/", icon: "◆" }, { @@ -245,7 +246,7 @@ that matches your isolation, memory, and parallelism needs. {{ comp callout { type: "note", title: "Tune it without touching code" } }} The runner mode and pool size are deployment settings, not handler concerns — the same <code>process-payment</code> handler runs unchanged under any -<a href="/how-to/tune-worker-runtime/"><code>WORKER_RUNTIMES</code> mode</a>. Start on the +<a href="/background-processing/how-to/tune-worker-runtime/"><code>WORKER_RUNTIMES</code> mode</a>. Start on the <code>web-worker</code> default with a small <code>WORKERS_CONCURRENCY</code>, move to <code>subprocess</code> when you need hard isolation, and drop to <code>in-process</code> for tests and compiled single-binary deployments. Resolution precedence (schema default → config @@ -427,7 +428,7 @@ the API enqueues, the runner executes. A missing generated registry is tolerated so a fresh workspace boots before you author any job. Set <code>WORKERS_CONCURRENCY</code> on the worker background process when you need a specific process pool size. Current Aspire metadata also emits <code>WORKER_CONCURRENCY</code>, but the runtime entrypoint does not consume it; use -<a href="/how-to/tune-worker-runtime/">Tune the worker runtime</a> for the mismatch details. +<a href="/background-processing/how-to/tune-worker-runtime/">Tune the worker runtime</a> for the mismatch details. {{ /comp }} ## Observability: real job traces out of the box @@ -449,7 +450,7 @@ appear in the [Aspire dashboard](/explanation/aspire/) automatically once Aspire For spans you author *inside* a handler, import directly from **`@netscript/telemetry`** (e.g. `@netscript/telemetry/instrumentation` for `withChildSpan`). These nest correctly under the automatic dispatch span. See [Observability](/explanation/observability/) for the model and -[Add OpenTelemetry](/how-to/add-opentelemetry/) for the recipe. +[Add OpenTelemetry](/observability/how-to/add-opentelemetry/) for the recipe. ## How it compares @@ -482,7 +483,7 @@ so bring orchestration up before you exercise it. Step 1 is the database se step 2 is Aspire: <code>cd aspire && aspire start</code> provisions Postgres and Redis, then <code>netscript db init --name init</code> / <code>netscript db generate</code> wire the schema. Only after Aspire is up will <code>:8091</code> resolve jobs and record -executions. See <a href="/how-to/database-migration/">Database & migration</a>. +executions. See <a href="/data-persistence/how-to/database-migration/">Database & migration</a>. {{ /comp }} {{ comp callout { type: "warning", title: "Drain on redeploy, or lose in-flight runs" } }} @@ -491,7 +492,7 @@ A runner that exits without draining abandons jobs mid-execution. Wire a <code>timeoutMs</code>, and check <code>report.timedOut</code> in your logs. Pair the drain with an <code>idempotencyKey</code> on enqueue so a retried delivery after a forced exit does not double-charge. For the full recipe see -<a href="/how-to/tune-worker-runtime/">Tune the worker runtime</a>. +<a href="/background-processing/how-to/tune-worker-runtime/">Tune the worker runtime</a>. {{ /comp }} {{ comp.badge({ status: "partial" }) }} @@ -528,7 +529,7 @@ exported type and subpath) lives in the reference. { title: "Do — Tune the worker runtime", body: "Pick the runner mode, set WORKERS_CONCURRENCY, choose a queue provider, and wire graceful shutdown.", - href: "/how-to/tune-worker-runtime/", + href: "/background-processing/how-to/tune-worker-runtime/", icon: "◆" }, { diff --git a/docs/site/capabilities/agent-tooling.md b/docs/site/capabilities/agent-tooling.md deleted file mode 100644 index 68a41ea68..000000000 --- a/docs/site/capabilities/agent-tooling.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -layout: layouts/base.vto -title: Agent tooling -description: Install and use NetScript's shared CLI, skills, and MCP vocabulary. ---- - -# Agent tooling - -NetScript gives developers and coding agents one vocabulary across three surfaces: - -- the `netscript` CLI performs direct, scriptable operations; -- installed skills explain which NetScript workflow to use and when to hand off to the CLI; -- the MCP server returns compact framework-aware diagnostics, telemetry summaries, and public docs. - -Prefer the CLI when a command already expresses the operation. It is easier to reproduce in a -terminal or CI job. Use MCP for interactive investigation, especially when one framework-aware tool -replaces several telemetry queries or when an agent needs to discover a document before reading it. - -## Install into a project - -```bash -netscript agent init -``` - -Host detection installs the matching files. If neither host directory exists, NetScript prepares -both hosts. Use `--host claude`, `--host vscode`, or `--host all` to select explicitly. - -| Host | Files written | -| ----------- | ---------------------------------------------------------------------------------------------------- | -| Claude Code | `.mcp.json`, NetScript skills under `.claude/skills/`, and a marked NetScript section in `AGENTS.md` | -| VS Code | `.vscode/mcp.json` | - -The generated MCP configuration runs `netscript agent mcp` for the current project. Re-running -`agent init` is idempotent: unchanged files are left alone, and existing host configuration is -preserved alongside the `netscript` server entry. - -The host command includes the absolute project `deno.json` path. Deno 2.9 normally holds newly -published registry versions behind a 24-hour minimum dependency age; the generated JSR workspace -keeps that policy while excluding only exact-version packages in the matching NetScript release -train. Loading the project configuration explicitly lets a newly released `@netscript/cli` MCP -server start immediately without changing the age policy for third-party dependencies. - -## Run the server - -The generated host configuration runs `netscript agent mcp`, which starts the MCP server over -standard input/output. Its flags: - -| Flag | Purpose | -| ----------------------- | --------------------------------------------------------------------------------------------- | -| `--project-root <path>` | NetScript project root used for execution and doctor flows. `agent init` writes this for you. | -| `--endpoint <url>` | Telemetry endpoint URL; overrides discovery (below). Only `http:` and `https:` are accepted. | -| `--docs-root <path>` | Public documentation root for the docs tools; overrides the default corpus (below). | - -## Documentation corpus - -Without an override, `list_docs`, `search_docs`, and `get_doc` index the documentation shipped with -the installed `@netscript/mcp` package (one document under the `mcp` slug). Point the server at a -richer corpus — a project docs folder or a checkout of this site — with `--docs-root <path>` or the -`NETSCRIPT_DOCS_ROOT` environment variable. A configured path that does not exist returns a -structured `docs_corpus_not_found` error naming the missing path; the server never silently reports -an empty corpus. - -## Tool catalog - -Every tool returns a bounded structured result. A CLI twin is listed where a direct command covers -the same user intent; `—` means the MCP operation is an aggregate or read model rather than a CLI -command. - -| Tool | What one call replaces | CLI twin | -| ----------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------- | -| `get_app_status` | Health and recent activity across NetScript runtime domains | — | -| `list_runs` | Search and grouping of recent jobs, sagas, triggers, and other executions | — | -| `get_run` | Correlation of one execution's spans, logs, outcome, and error | — | -| `get_recent_errors` | Cross-service error search and grouping | — | -| `get_last_job_result` | Finding the latest matching job execution and its outcome | — | -| `analyze_service_performance` | Duration percentiles, throughput, and error-rate calculations | — | -| `analyze_db_bottlenecks` | Ranking database and KV operations by latency and frequency | — | -| `doctor` | Telemetry reachability, Aspire markers, project wiring, and plugin diagnostics | `netscript plugin doctor` covers plugin diagnostics | -| `search_docs` | Search over the public documentation corpus | — | -| `list_docs` | A bounded inventory of public documents | — | -| `get_doc` | Reading one document or named section without loading the full corpus | — | -| `list_commands` | Machine-readable discovery of the current CLI command tree | `netscript --help` | -| `execute_command` | Policy checking, execution, timeout handling, and a bounded output tail | Run the allowed `netscript …` command directly | - -## Token-efficient use - -Tool inputs cap result counts, and the server truncates oversized results and command output. Start -with the narrowest filter that answers the question. For documentation, use the search-to-get -funnel: call `search_docs`, choose a slug, then call `get_doc` for that document or section. - -## Data boundary - -The MCP server reads NetScript telemetry, project metadata and generated registries used for -diagnostics, and public documentation. It does not return project source code, environment-variable -values, credentials, or secrets. - -Telemetry requests go only to the resolved dashboard endpoint. Discovery uses, in order, the -`--endpoint` option, `NETSCRIPT_TELEMETRY_ENDPOINT`, `ASPIRE_DASHBOARD_PORT`, and the local default -`http://localhost:18888`. - -`execute_command` is default-deny. Explicit rules allow selected database, generation, contract, -service-read, plugin, and UI commands. Deny rules take priority; deployment, project initialization, -marketplace operations, database reset, plugin removal, and every unmatched command are rejected -with a structured denial before a process is started. - -## Troubleshooting - -Start with the `doctor` tool: it aggregates four check families — `telemetry`, `aspire`, `project`, -and `plugins` — into one verdict. Each check carries a `pass`, `warn`, or `fail` status, and -warnings and failures may include a suggested fix. Reading the result: - -- **`telemetry` warns or fails** — no reachable telemetry endpoint. Verify the app is running, then - check the discovery chain: `--endpoint`, `NETSCRIPT_TELEMETRY_ENDPOINT`, `ASPIRE_DASHBOARD_PORT`, - and the local default `http://localhost:18888`. Telemetry tools still respond while the endpoint - is down — nothing crashes: `get_app_status` reports `status: "warn"` with zero counts, the list - and analytics tools (`list_runs`, `get_recent_errors`, `analyze_service_performance`, - `analyze_db_bottlenecks`, …) return their ordinary empty or zero-valued results, and `get_run` - returns a structured `run_not_found` error. -- **`aspire` or `project` warns** — the project root is wrong or the workspace is missing expected - markers. Confirm the `--project-root` written into your host configuration points at the project - (re-run `netscript agent init` after moving a project). -- **`plugins` warns or fails** — plugin diagnostics found an issue; the equivalent direct command is - `netscript plugin doctor`. -- **Docs tools return `docs_corpus_not_found`** — the configured `--docs-root` / - `NETSCRIPT_DOCS_ROOT` path does not exist; fix the path or remove the override to fall back to the - packaged corpus. -- **`execute_command` returns a denial** — the command did not match the allowlist (see the data - boundary above); run it directly in a terminal instead. - -## Run the protocol smoke - -```bash -deno test --allow-all packages/cli/e2e/tests/agent/agent-mcp-stdio_test.ts -``` - -The smoke starts the public CLI binary, initializes MCP over stdio, verifies the 13-tool catalog, -and checks docs, diagnostics, unreachable telemetry, and command denial behavior. diff --git a/docs/site/cli-reference.md b/docs/site/cli-reference.md index 45fb8bd1c..ee60215c6 100644 --- a/docs/site/cli-reference.md +++ b/docs/site/cli-reference.md @@ -7,27 +7,21 @@ prev: { label: "Glossary", href: "/glossary/" } # CLI reference -A curated, task-grouped tour of the `netscript` commands you reach for daily. It is -the human companion to the [command reference](/reference/cli/commands/), which lists every -command, subcommand, and flag verbatim. When you need the exhaustive option spelling, go -there — this page covers the common path and the order things happen in. (The separate -[`@netscript/cli` package reference](/reference/cli/) documents the embeddable TypeScript -surface, not the terminal commands.) - -{{ comp callout { type: "note", title: "One CLI, public form" } }} -Every command here uses the public <code>netscript <cmd></code> form backed by the published -JSR package. The vendored <code>packages/cli/...</code> path you may see in a local-source checkout is a -contributor-only shape — a normal install has no <code>packages/</code> tree. Install once (below), then -use <code>netscript</code>. -{{ /comp }} +This is the cheat-sheet: which `netscript` command we reach for, grouped by task. Each +section lists the everyday spelling and stops there — every flag, subcommand, and +extended verb lives in the [command reference](/reference/cli/commands/), and the +embeddable TypeScript surface is on the [`@netscript/cli` package page]({{ "ref:cli" |> xref }}). +Every command here uses the public `netscript <cmd>` form backed by the published JSR +package; the vendored `packages/cli/...` path you may see in a local-source checkout is a +contributor-only shape. {{ comp callout { type: "important", title: "Database commands need Aspire running first" } }} -The <code>netscript db ...</code> commands provision and talk to your database <strong>through Aspire</strong> — Postgres is the recommended -engine, or <code>mysql</code> / <code>mssql</code> / <code>sqlite</code> when you scaffold with <code>--db</code>. Aspire is -step 2 of the everyday flow: <code>cd aspire && aspire start</code> brings up Postgres and Redis via Docker and -opens the dashboard at <a href="https://localhost:18888">:18888</a> — <strong>before</strong> any <code>db init</code>, -<code>db generate</code>, <code>db seed</code>, or <code>db status</code>. Run a <code>db</code> command with Aspire down and it fails to -find the database. See the <a href="/how-to/database-migration/">database & migration how-to</a>. +The <code>netscript db ...</code> commands provision and talk to your database <strong>through Aspire</strong>. +<code>cd aspire && aspire start</code> brings up Postgres and Redis via Docker and opens the dashboard at +<a href="https://localhost:18888">:18888</a> — do this <strong>before</strong> any <code>db</code> command +(<code>sqlite</code> is the file-backed exception with no container). Run a <code>db</code> command with Aspire +down and it fails to find the database — the “aspire start failed: project file does not exist” error almost +always means exactly this. See the <a href="/data-persistence/how-to/database-migration/">database & migration how-to</a>. {{ /comp }} ## Install @@ -53,16 +47,14 @@ The CLI is published to JSR as `@netscript/cli`. Install it globally for a tidy } ] }) }} -{{ comp callout { type: "tip" } }} -Use <code>deno x jsr:@netscript/cli{{ releaseSpecifier }}</code> for ad-hoc runs, or install the same default export as -<code>netscript</code> when you want a PATH command. -{{ /comp }} +`netscript --version` prints the installed CLI version; `netscript --help` and +`netscript <group> --help` (for example `netscript db --help`) show the exact flag +spelling your installed version ships. ## The everyday flow -Most sessions follow the same shape: scaffold a workspace, bring up Aspire, run the -database workflow, then iterate. The commands below are grouped to mirror that order — -and the order matters: **Aspire (step 2) must be up before any `db` command (step 3).** +Most sessions follow the same shape, and the order matters: **Aspire (step 2) must be up +before any `db` command (step 3).** {{ comp.featureGrid({ items: [ { @@ -72,12 +64,12 @@ and the order matters: **Aspire (step 2) must be up before any `db` command (ste }, { title: "2 · Orchestrate", - body: "cd aspire && aspire start brings up your database (Postgres is the recommended engine; or mysql/mssql/sqlite via --db) and Redis, and opens the dashboard at :18888. Do this before any db command.", + body: "cd aspire && aspire start brings up your database and Redis, and opens the dashboard at :18888. Do this before any db command.", icon: "▶" }, { title: "3 · Database", - body: "netscript db init / generate / seed / status — only after Aspire is up.", + body: "netscript db init / generate / migrate / seed — only after Aspire is up.", icon: "▤" }, { @@ -87,294 +79,177 @@ and the order matters: **Aspire (step 2) must be up before any `db` command (ste } ] }) }} -## Scaffold & project - -`netscript init` is the one command that creates a complete workspace: shared oRPC -contracts, an optional example service, a Fresh frontend, the plugin registry, and the -Aspire orchestration files. The flags below match the verified scaffold run. +## Scaffold a workspace {{ comp.apiTable({ - caption: "Scaffold a workspace", + caption: "netscript init", rows: [ - { name: "netscript init", type: "netscript init my-app", desc: "Create a NetScript workspace in <code>my-app/</code> — contracts, plugin registry, Fresh app, a default Redis cache, and the Aspire layer. On a terminal it prompts for anything you omit (name, database, service, cache); add <code>--dry-run</code> to preview every file without writing." }, - { name: "init (full happy path)", type: "netscript init my-app --db postgres --service --service-name users --service-port 3001 --yes", desc: "The fully-specified, non-interactive form (<code>--yes</code>): Postgres database support (swap <code>--db postgres</code> for <code>mysql</code>, <code>mssql</code>, <code>sqlite</code>, or <code>none</code>), an example oRPC <code>users</code> service on port 3001, and the default Redis cache resource." }, - { name: "init --model-name", type: "netscript init my-app --db postgres --service --model-name Product", desc: "Name the Prisma model for the scaffolded CRUD surface. With a database engine and an example service, <code>netscript init</code> now generates a real Prisma-backed CRUD contract + handlers (authored under <code>contracts/versions/v1/</code>) and an oRPC playground landing page at <code>GET /</code>. Omit the flag and the model name is derived from the singularized service name — service <code>users</code> → model <code>User</code>; it must be a PascalCase identifier." }, - { name: "init --cache / --cache-backend", type: "netscript init my-app --cache-backend garnet", desc: "The shared cache is on by default with the <code>redis</code> backend. Pick another with <code>--cache-backend</code>: <code>redis</code> (default) or <code>garnet</code> are provisioned as Aspire container resources; <code>deno-kv</code> is app-level and needs no container. Pass <code>--cache=false</code> to scaffold without a cache." }, - { name: "init --no-aspire", type: "netscript init my-app --no-aspire", desc: "Scaffold without the .NET Aspire footprint. You start the Fresh app directly with <code>deno task --cwd apps/dashboard dev</code> and lose the dashboard + multi-resource wiring." }, - { name: "init --path / --editor", type: "netscript init my-app --path ./apps --editor zed", desc: "Place the project under a different directory and emit editor settings (<code>none</code> | <code>zed</code> | <code>vscode</code>)." } + { name: "Create a workspace", type: "netscript init my-app", desc: "Scaffold everything — contracts, plugin registry, Fresh app, a default Redis cache, and the Aspire layer. On a terminal it prompts for whatever you omit (name, database, service, cache)." }, + { name: "Preview first", type: "netscript init my-app --dry-run", desc: "Print every file and directory the scaffold would create, and write nothing." }, + { name: "Fully specified, no prompts", type: "netscript init my-app --db postgres --service --service-name users --service-port 3001 --yes", desc: "Postgres database support, an example oRPC <code>users</code> service on port 3001, defaults for the rest. <code>--yes</code> accepts defaults, <code>--ci</code> is non-interactive; both engage automatically when stdin is not a terminal." }, + { name: "Pick a database engine", type: "netscript init my-app --db postgres", desc: "<code>postgres</code> (recommended), <code>mysql</code>, <code>mssql</code>, <code>sqlite</code>, or <code>none</code> — the default is no database unless you pass <code>--db</code>." }, + { name: "Skip Aspire", type: "netscript init my-app --no-aspire", desc: "Scaffold without the .NET Aspire footprint; start the Fresh app directly with <code>deno task --cwd apps/dashboard dev</code>." }, + { name: "Tune the rest", type: "--cache-backend garnet · --model-name Product · --path ./apps · --editor zed", desc: "Cache backend (<code>redis</code> default, <code>garnet</code>, or app-level <code>deno-kv</code>; <code>--cache=false</code> for none), the Prisma model name for the scaffolded CRUD surface, the target directory, and editor settings." } ] }) }} -{{ comp callout { type: "tip", title: "Preview before you commit to disk" } }} -<code>netscript init my-app --dry-run</code> prints every file and directory it would create and writes -nothing — a safe way to inspect the scaffold plan first. -{{ /comp }} - -{{ comp callout { type: "note", title: "Interactive vs non-interactive" } }} -On a terminal, <code>netscript init</code> prompts for any option you do not pass on the command line — -the project name, database engine, example service (and its name), the frontend application name, and -the two cache questions (enable the cache, then choose <code>redis</code> | <code>garnet</code> | -<code>deno-kv</code>). For scripts and CI, pass <code>--yes</code> (accept defaults) or <code>--ci</code> -(non-interactive) to skip every prompt — both also engage automatically when stdin is not a terminal. -The defaults scaffold a Fresh + Aspire workspace with a <code>redis</code> cache and <strong>no database</strong> -unless you pass <code>--db</code>. Run <code>netscript --version</code> to print the installed CLI version. -{{ /comp }} +Every `init` flag — including `--app-name`, `--no-git`, `--force`, `--json`, and +`--from <preset>` — is spelled out in the [command reference](/reference/cli/commands/). -## Contracts +## Run & iterate -Contracts live in explicit version folders and are aggregated for typed service and client -consumers. The lifecycle surface in this release creates and discovers v1 contract modules; evolve -breaking shapes in a parallel version rather than overwriting a contract that existing consumers -still use. +These are workspace `deno task`s, not `netscript` subcommands — the day-to-day loop once +the scaffold exists. {{ comp.apiTable({ - caption: "Contract workspace commands", + caption: "Run and gate the workspace", rows: [ - { name: "netscript contract add", type: "netscript contract add catalog-items", desc: "Create <code>contracts/versions/v1/catalog-items.contract.ts</code> from the NetScript oRPC contract template and regenerate the v1 aggregate exports. Run it from the workspace root, or pass <code>--path <workspace></code>. Existing contract files are preserved unless you pass <code>--force</code>." }, - { name: "contract add --version", type: "netscript contract add catalog-items --version v1", desc: "Select the target contract version. This release supports <code>v1</code>; parallel-version evolution is tracked separately and is not implied by <code>--force</code>." }, - { name: "netscript contract list", type: "netscript contract list", desc: "List v1 contract modules and show whether each has a matching service workspace. Pass <code>--path <workspace></code> when invoking it from outside the project." } + { name: "Orchestrate everything", type: "cd aspire && aspire start", desc: "Bring up the database, Redis, services, and plugin processors, with the dashboard at :18888." }, + { name: "Run the dashboard alone", type: "deno task --cwd apps/dashboard dev", desc: "Start the Fresh frontend directly (or let <code>aspire start</code> orchestrate it)." }, + { name: "Run a service alone", type: "deno task --cwd services/users dev", desc: "Start the example <code>users</code> oRPC service on port 3001." }, + { name: "Check, lint, test", type: "deno task check · deno task lint · deno task fmt · deno task test", desc: "Type-check, lint, format, and test the whole workspace." } ] }) }} -{{ comp callout { type: "note", title: "Contract add creates the typed starting point" } }} -<code>contract add</code> replaces the manual step of creating and exporting an initial versioned -contract file. Add your domain-specific procedures to that generated module. Route-level mutation, -handler generation, detailed JSON inspection, removal, and v2 promotion belong to the follow-up -contract-evolution surface. -{{ /comp }} - -## Plugins - -Plugins add capabilities — background workers, durable sagas, webhook triggers, durable -streams, and authentication. Public install adds the plugin package dependency, emits -workspace-owned glue and samples that import it, and registers its contributions; the host application never changes. After adding plugins, -regenerate the registry so the project picks them up. +## Services & contracts -{{ comp.apiTable({ - caption: "Public plugin install (netscript)", - rows: [ - { name: "netscript plugin install", type: "netscript plugin install <kind-or-package> --name <name> [--project-root <path>]", desc: "Install a plugin dependency, emit the workspace glue that imports it, and register it with Aspire. The positional value accepts official bare aliases such as <code>workers</code> or <code>auth</code>, scoped package specs such as <code>@netscript/plugin-workers</code>, and <code>jsr:</code> package specs." }, - { name: "netscript plugin install workers", type: "netscript plugin install workers --name workers", desc: "Install the official workers plugin via its verified bare alias. The equivalent package-spec form is <code>netscript plugin install @netscript/plugin-workers --name workers</code>." }, - { name: "netscript plugin install auth", type: "netscript plugin install auth --name auth", desc: "Install the official auth plugin — the <code>auth-api</code> oRPC service on port 8094 exposing <code>/api/v1/auth/{signin,callback,signout,session,me}</code>. Pulls in <code>auth.prisma</code> and a single active backend selected by <code>NETSCRIPT_AUTH_BACKEND</code> (default <code>kv-oauth</code>). See <a href=\"/how-to/add-authentication/\">add authentication</a>." }, - { name: "netscript plugin new", type: "netscript plugin new billing", desc: "Scaffold a brand-new first-party plugin as a two-tier pair: a JSR-publishable core engine package under <code>packages/plugin-<name>-core/</code> (domain, ports, application, contracts, testing doubles) and a thin connector under <code>plugins/<name>/</code> (manifest, adapter, aspire, cli, scaffold, services) that re-exports the core contract. Scaffolds a proxy connector by default; pass <code>--feature</code> for a route-backed feature connector, and <code>--force</code> to overwrite existing files. See <a href=\"/how-to/author-a-plugin/\">author a plugin</a>." }, - { name: "netscript plugin list", type: "netscript plugin list", desc: "List the plugins registered in the current workspace." }, - { name: "netscript plugin doctor", type: "netscript plugin doctor", desc: "Check the health of installed NetScript plugins — a fast wiring sanity check." }, - { name: "netscript plugin info", type: "netscript plugin info workers", desc: "Run a plugin's published info command for details about a single plugin." }, - { name: "netscript plugin remove", type: "netscript plugin remove workers", desc: "Remove a configured plugin and update workspace registration." } - ] -}) }} +A NetScript workspace is contract-first: you define an oRPC contract, then a service +implements it. {{ comp.apiTable({ - caption: "Local contributor plugin scaffolding (netscript-dev)", + caption: "Services and contracts", rows: [ - { name: "plugin install worker", type: "deno run -A packages/cli/bin/netscript-dev.ts plugin install worker --name workers --samples", desc: "Local-source contributor path for first-party worker samples against the monorepo checkout." }, - { name: "plugin install saga", type: "deno run -A packages/cli/bin/netscript-dev.ts plugin install saga --name sagas --samples", desc: "Local-source contributor path for sagas samples." }, - { name: "plugin install trigger", type: "deno run -A packages/cli/bin/netscript-dev.ts plugin install trigger --name triggers --samples", desc: "Local-source contributor path for triggers samples." }, - { name: "plugin install stream", type: "deno run -A packages/cli/bin/netscript-dev.ts plugin install stream --name streams --samples", desc: "Local-source contributor path for streams samples." }, - { name: "plugin install options", type: "--name --port --service-refs --plugin-refs --db/--no-db --samples/--no-samples --force", desc: "These framework-level install flags are shared with the public <code>netscript plugin install</code> command; <code>netscript-dev</code> uses local monorepo sources for contributor validation." } + { name: "Add a service", type: "netscript service add --name orders --port 3002", desc: "Add a service workspace member, its v1 contract, and the Aspire registration." }, + { name: "Add a contract", type: "netscript contract add catalog-items", desc: "Create <code>contracts/versions/v1/catalog-items.contract.ts</code> from the oRPC contract template and regenerate the v1 aggregate exports." }, + { name: "Add a route + handler", type: "netscript contract add-route · netscript service add-handler", desc: "Append a typed procedure to a contract, then bind it with a compiling service handler stub." }, + { name: "See what exists", type: "netscript service list · netscript contract list · netscript contract inspect <name>", desc: "List services, list v1 contract modules (and whether each has a matching service), and inspect a contract's procedures and schemas." }, + { name: "Regenerate Aspire helpers", type: "netscript service generate", desc: "Regenerate the Aspire helper files from your service configuration." } ] }) }} -{{ comp callout { type: "note", title: "Public install emits glue, not copied internals" } }} -A public <code>plugin install</code> runs the plugin package's scaffolder and emits user-owned glue -such as <code>workers/mod.ts</code>, <code>workers/runtime.ts</code>, or <code>auth/mod.ts</code>. -The plugin's service, runtime, contract, and schema internals stay in the installed dependency. -Contributor workflows can still materialize full local source from a NetScript checkout. See the -<a href="/how-to/add-a-plugin/">add-a-plugin how-to</a>. -{{ /comp }} +The full groups — `service set` / `remove` / `ref add`, `contract remove` / +`version add`, and every flag — are in the [command reference](/reference/cli/commands/). -### Authentication plugin +## Plugins -The `auth` plugin is a first-class official plugin scaffolded exactly like the others — -`netscript plugin install auth` installs the `@netscript/plugin-auth` dependency, emits -the user-owned `auth/mod.ts` glue barrel, registers the `auth-api` service on port 8094, -and contributes the package-provided `auth.prisma` schema to the standard `netscript db` -workflow alongside every other plugin schema. It composes **one -active backend** at a time, chosen at runtime by the `NETSCRIPT_AUTH_BACKEND` env var. +Plugins add capabilities — background workers, durable sagas, webhook triggers, durable +streams, authentication. Public install adds the plugin package dependency, emits +workspace-owned glue that imports it, and registers its contributions; the plugin's +internals stay in the installed dependency. {{ comp.apiTable({ - caption: "Auth backend selection (NETSCRIPT_AUTH_BACKEND)", + caption: "Plugin lifecycle", rows: [ - { name: "kv-oauth (default)", type: "NETSCRIPT_AUTH_BACKEND=kv-oauth", desc: "The default and the only interactive backend — full OAuth/OIDC redirect flow with KV-backed sessions. Needs provider env (<code>NETSCRIPT_AUTH_CLIENT_ID</code>, <code>NETSCRIPT_AUTH_CLIENT_SECRET</code>, <code>NETSCRIPT_AUTH_ISSUER</code>, <code>NETSCRIPT_AUTH_REDIRECT_URI</code>, …)." }, - { name: "workos", type: "NETSCRIPT_AUTH_BACKEND=workos", desc: "Non-interactive AuthKit backend — set <code>WORKOS_API_KEY</code>, <code>WORKOS_CLIENT_ID</code>, <code>WORKOS_COOKIE_PASSWORD</code>. The <code>signin</code>/<code>callback</code> endpoints return a typed unsupported-operation error (no interactive flow)." }, - { name: "better-auth", type: "NETSCRIPT_AUTH_BACKEND=better-auth", desc: "Non-interactive Prisma-backed backend — set <code>BETTER_AUTH_SECRET</code> and <code>DB_PROVIDER</code>. Like WorkOS, the interactive endpoints are unsupported." } + { name: "Install an official plugin", type: "netscript plugin install workers --name workers", desc: "Bare aliases (<code>workers</code>, <code>auth</code>, …), scoped specs (<code>@netscript/plugin-workers</code>), and <code>jsr:</code> specs all work. After auth, pick the runtime backend with <code>NETSCRIPT_AUTH_BACKEND</code> — see <a href=\"/identity-access/how-to/add-authentication/\">add authentication</a>." }, + { name: "Wire the registry", type: "netscript generate plugins", desc: "Regenerate the plugin registries from project source. Run this after every <code>plugin install</code>." }, + { name: "Check health", type: "netscript plugin list · netscript plugin doctor · netscript plugin info workers", desc: "List registered plugins, run the wiring sanity check, and show a single plugin's details." }, + { name: "Author your own", type: "netscript plugin new billing", desc: "Scaffold a new two-tier plugin: a JSR-publishable core package plus a thin connector. See <a href=\"/orchestration-runtime/how-to/author-a-plugin/\">author a plugin</a>." }, + { name: "Discover & maintain", type: "netscript marketplace search <query> · netscript plugin update <name> · netscript plugin remove <name>", desc: "Search the plugin marketplace, re-pin and regenerate an installed plugin, or remove one and update workspace registration." } ] }) }} -{{ comp callout { type: "note", title: "Auth migrates like any other plugin schema" } }} -After <code>netscript plugin install auth</code>, run the normal database workflow with Aspire up: -<code>netscript db generate</code> then <code>netscript db migrate</code> picks up the auth plugin's package-provided Prisma schema -(the better-auth-shaped <code>auth_users</code>, <code>auth_sessions</code>, <code>auth_accounts</code>, <code>auth_verifications</code> -tables) exactly like the other plugins. Only the <code>better-auth</code> backend reads these tables — -<code>kv-oauth</code> stores sessions in KV and <code>workos</code> is stateless. Full env table and happy-path setup -are in <a href="/how-to/add-authentication/">add authentication</a>; the architecture is in -<a href="/explanation/auth-model/">the auth model</a>. -{{ /comp }} - -## Services & contracts - -A NetScript workspace is contract-first: you define an oRPC contract, then a service -implements it. The example `users` service runs on port 3001 and serves its RPC surface -at `/api/rpc/*`. - -{{ comp.apiTable({ - caption: "Services and contracts", - rows: [ - { name: "netscript service add", type: "netscript service add --name orders --port 3002", desc: "Add a new service workspace member and wire its contract. The example <code>users</code> service serves <code>/api/v1/users/*</code> (and oRPC at <code>/api/rpc/*</code>) on port 3001." }, - { name: "netscript service list", type: "netscript service list", desc: "List the services configured in the workspace." }, - { name: "netscript service generate", type: "netscript service generate", desc: "Regenerate the Aspire helper files from your service configuration." }, - { name: "netscript contract add", type: "netscript contract add orders", desc: "Add a versioned oRPC contract (<code>oc.route().input(zod).output(zod)</code> + <code>implement()</code>) to the <code>contracts/</code> workspace." }, - { name: "netscript contract list", type: "netscript contract list", desc: "List the contracts available in the workspace." } - ] -}) }} +The extended verbs — `plugin sync`, `enable` / `disable` / `setup`, `item-add`, and the +`plugin auth` backend/provider/session subcommands — are in the +[command reference](/reference/cli/commands/). ## Database -The database workflow uses Prisma with a Deno runtime, and the engine is **polyglot**: -`netscript init --db postgres` (the recommended default) or `mysql`, `mssql`, or `sqlite`. -Postgres, MySQL, and SQL Server each run as an Aspire container resource; **`sqlite` is -file-backed and has no Aspire container**. **All of the container-backed engines require -Aspire to be running** — Aspire provisions the database, so start it first with -`cd aspire && aspire start`. Plugin schemas (`workers`, `sagas`, `triggers`, **`auth`**) -are picked up by the same `generate` / `migrate` pass. The full task walkthrough is in the -[database & migration how-to](/how-to/database-migration/). +The database workflow uses Prisma with a Deno runtime, and every command below requires +Aspire to be running first (`cd aspire && aspire start`) — `sqlite` being the file-backed +exception. Plugin schemas (`workers`, `sagas`, `triggers`, `auth`) are picked up by the +same `generate` / `migrate` pass. The walkthrough is the +[database & migration how-to]({{ "howto:database-migration" |> xref }}). {{ comp.apiTable({ caption: "Database workflow (Aspire must be running)", rows: [ - { name: "netscript db init", type: "netscript db init --name init", desc: "Initialize database tooling and create the named migration. Requires Aspire up — it provisions Postgres through the AppHost." }, - { name: "netscript db generate", type: "netscript db generate", desc: "Run database code generation — the Deno-runtime Prisma client (and zod) into <code>database/postgres/schema/.generated</code>. Includes plugin schemas such as <code>auth.prisma</code>." }, - { name: "netscript db migrate", type: "netscript db migrate", desc: "Apply migrations against the provisioned database — including each plugin's contributed schema (e.g. the <code>auth_*</code> tables from <code>auth.prisma</code>)." }, - { name: "netscript db seed", type: "netscript db seed", desc: "Run the workspace seed scripts to populate initial data." }, - { name: "netscript db status", type: "netscript db status", desc: "Show database migration / tooling status." }, - { name: "netscript db studio", type: "netscript db studio", desc: "Open the database studio tool for browsing data." }, - { name: "netscript db introspect / reset", type: "netscript db reset", desc: "Introspect the configured database, or reset it back to a clean state." } + { name: "Initialize + first migration", type: "netscript db init --name init", desc: "Initialize database tooling and create the named migration." }, + { name: "Generate the client", type: "netscript db generate", desc: "Generate the Deno-runtime Prisma client (and zod) — including plugin schemas such as <code>auth.prisma</code>." }, + { name: "Migrate & seed", type: "netscript db migrate · netscript db seed", desc: "Apply migrations (including each plugin's contributed schema), then run the workspace seed scripts." }, + { name: "Inspect", type: "netscript db status · netscript db studio", desc: "Show migration/tooling status, or open the database studio for browsing data." }, + { name: "Recover", type: "netscript db introspect · netscript db reset", desc: "Introspect the configured database, or reset it back to a clean state." }, + { name: "Multiple databases", type: "netscript db add <engine> · netscript db list", desc: "Add a second database workspace to an existing project and list registered targets." } ] }) }} -{{ comp callout { type: "note", title: "Two ways to run the DB workflow: with Aspire and without" } }} -Every <code>netscript db <op></code> command is <strong>Aspire-coupled</strong> — it shells out to the -Aspire AppHost, so <code>aspire</code> must be on your PATH and the apphost running. The scaffolded -workspace <em>also</em> defines <strong>aspire-less</strong> <code>deno task db:*</code> tasks -(<code>db:generate</code>, <code>db:migrate</code>, <code>db:seed</code>, <code>db:studio</code>, …) inside -<code>database/<engine>/</code> that run Prisma directly with no Aspire. Use the <code>deno task db:*</code> -form in deno-only or CI jobs that have no Aspire; use <code>netscript db *</code> for the orchestrated -local flow. -{{ /comp }} - -{{ comp callout { type: "warning", title: "“aspire start failed: project file does not exist”" } }} -This almost always means a <code>db</code> command was run with Aspire down (or from the wrong directory). -The fix is the dev flow order: <code>cd aspire && aspire start</code> first, leave it running, then run -<code>netscript db init</code> from the project root in a second terminal. -{{ /comp }} +The scaffolded workspace also defines Aspire-less `deno task db:*` tasks +(`db:generate`, `db:migrate`, `db:seed`, `db:studio`, …) inside `database/<engine>/` that +run Prisma directly — the form to use in deno-only or CI jobs. The target-management and +migration-history verbs (`db deploy`, `validate`, `resolve`, `remove`) are in the +[command reference](/reference/cli/commands/). -## Code generation +## Generate -After adding or changing plugins, regenerate the artifacts the project consumes so the -registry and runtime schemas stay in sync. +After adding or changing plugins or configuration, regenerate the artifacts the project +consumes. {{ comp.apiTable({ - caption: "Generate registries & schemas", + caption: "Code generation", rows: [ - { name: "netscript generate plugins", type: "netscript generate plugins", desc: "Generate the plugin registries from project source. Run this after every <code>plugin install</code> so the workspace picks up new contributions." }, - { name: "netscript generate runtime-schemas", type: "netscript generate runtime-schemas", desc: "Generate runtime configuration schemas from registered plugin metadata." }, - { name: "netscript generate aspire", type: "netscript generate aspire", desc: "Regenerate the Aspire AppHost helpers from <code>appsettings.json</code> without re-scaffolding the project. Pass <code>--project-root <path></code> to target a workspace outside the current directory." }, + { name: "Plugin registries", type: "netscript generate plugins", desc: "Regenerate the plugin registries from project source — the post-install step." }, + { name: "Runtime config schemas", type: "netscript generate runtime-schemas", desc: "Generate JSON Schema files for runtime configuration topics." }, + { name: "Aspire helpers", type: "netscript generate aspire", desc: "Regenerate the Aspire AppHost helpers from <code>appsettings.json</code> without re-scaffolding." } ] }) }} +Related: `netscript config inspect` / `get` / `set` read and write the resolved project +configuration, and `netscript config override` manages versioned runtime overrides — the +full subcommand table is in the [command reference](/reference/cli/commands/). + ## Fresh UI The frontend is copy-source: components are copied into your repo under `apps/dashboard`, and the code is yours to own and edit. See -[customize Fresh UI](/how-to/customize-fresh-ui/). - -{{ comp.apiTable({ - caption: "UI workspace tasks", - rows: [ - { name: "ui:init", type: "netscript ui:init --project-root apps/dashboard", desc: "Initialize the fresh-ui design system into the dashboard app (copy-source components + tokens)." }, - { name: "ui:add", type: "netscript ui:add <item> --project-root apps/dashboard", desc: "Copy an additional fresh-ui component into your repo — you own the copied source from that point." } - ] -}) }} - -## Dev & workspace tasks - -These are workspace `deno task`s, not `netscript` subcommands — the day-to-day loop once -the scaffold exists. Use `--cwd <member>` to target a specific workspace member. +[customize Fresh UI]({{ "howto:customize-fresh-ui" |> xref }}). {{ comp.apiTable({ - caption: "Run and gate the workspace", + caption: "UI registry commands", rows: [ - { name: "Run the dashboard", type: "deno task --cwd apps/dashboard dev", desc: "Start the Fresh frontend (or let <code>aspire start</code> orchestrate it for you)." }, - { name: "Run a service", type: "deno task --cwd services/users dev", desc: "Start the example <code>users</code> oRPC service on port 3001." }, - { name: "Type-check", type: "deno task check", desc: "Type-check the whole workspace." }, - { name: "Lint", type: "deno task lint", desc: "Lint the workspace sources." }, - { name: "Format", type: "deno task fmt", desc: "Apply the repo formatting (2-space, single-quote, lineWidth 100)." }, - { name: "Test", type: "deno task test", desc: "Run the workspace test suite." } + { name: "Initialize the design system", type: "netscript ui:init --project-root apps/dashboard", desc: "Copy the fresh-ui components and tokens into the dashboard app." }, + { name: "Add a component", type: "netscript ui:add <item> --project-root apps/dashboard", desc: "Copy an additional registry item — you own the copied source from that point." }, + { name: "List & maintain", type: "netscript ui:list · netscript ui:update · netscript ui:remove <name>", desc: "List registry items, update only files you have not modified, or remove a copied item." } ] }) }} ## Deploy -Two deploy paths are wired today: the **Deno Deploy** cloud target and the pre-existing -**Windows Service** (Servy) path. See [deploy](/how-to/deploy/) for the portability story, -including the `--no-aspire` escape hatch and bare-`deno task` targets. - -### Deno Deploy (cloud target) - -`netscript deploy deno-deploy <op>` is a thin router over the native `deno deploy` CLI — it -shells `deno deploy …`, so that CLI must be on your PATH and authentication is delegated to it -(NetScript issues no credentials of its own). +Two deploy paths are wired today: the **Deno Deploy** cloud target and the +**Windows Service** (Servy) path. `netscript deploy docker` and `deploy compose` exist as +command groups but are not wired — they only print help. See +[deploy]({{ "howto:deploy" |> xref }}) for the portability story. {{ comp.apiTable({ - caption: "netscript deploy deno-deploy <op>", + caption: "Deploy commands", rows: [ - { name: "deno-deploy plan", type: "netscript deploy deno-deploy plan", desc: "Preflight the project for Deno Deploy (runs the unstable-API guard only; never pushes)." }, - { name: "deno-deploy up", type: "netscript deploy deno-deploy up [--prod] [--dry-run]", desc: "Push a deployment (<code>deno deploy [--prod]</code>). <code>--dry-run</code> is equivalent to <code>plan</code> and does not push." }, - { name: "deno-deploy down", type: "netscript deploy deno-deploy down", desc: "Delete the deployment." }, - { name: "deno-deploy status", type: "netscript deploy deno-deploy status", desc: "Show deployment status." }, - { name: "deno-deploy logs", type: "netscript deploy deno-deploy logs", desc: "Show deployment logs." } + { name: "Deno Deploy: preflight", type: "netscript deploy deno-deploy plan", desc: "Run the unstable-API guard (scans for <code>Deno.openKv</code>, <code>Deno.cron</code>, <code>BroadcastChannel</code>, <code>Temporal</code>) without pushing. The same guard <strong>blocks</strong> <code>up --prod</code> on a violation; a preview push warns but proceeds." }, + { name: "Deno Deploy: lifecycle", type: "netscript deploy deno-deploy up [--prod] · down · status · logs", desc: "Push, delete, and inspect the deployment. A thin router over the native <code>deno deploy</code> CLI — it must be on your PATH and handles authentication." }, + { name: "Windows Service: build", type: "netscript deploy build", desc: "Build the Windows Service deployment artifacts from a deployment manifest via Servy." }, + { name: "Windows Service: lifecycle", type: "netscript deploy install · start · stop · status · logs · upgrade · uninstall", desc: "Install, run, inspect, upgrade, and remove Windows Services from the manifest." } ] }) }} -Every op shares five flags: `--org <slug>`, `--app <name>`, `--entrypoint <path>`, -`--env-file <path>`, and `--project-root <dir>`. CLI flags override the optional -`deploy.targets['deno-deploy']` config block, and flags alone are sufficient (config is optional). - -{{ comp callout { type: "warning", title: "The unstable-API guard blocks `up --prod`" } }} -<code>plan</code> and <code>up</code> run a best-effort <strong>unstable-API guard</strong> that scans -<code>deno.json#unstable</code> and your entrypoint for <code>Deno.openKv</code>, <code>Deno.cron</code>, -<code>new BroadcastChannel</code>, and <code>Temporal.</code>. Deno Deploy rejects <code>--unstable-*</code> -flags, so <code>up --prod</code> <strong>refuses to push</strong> when the guard finds a violation; a preview -(non-prod) push warns but proceeds. -{{ /comp }} - -{{ comp callout { type: "note", title: "docker / compose / linux are not wired yet" } }} -<code>netscript deploy docker</code> and <code>netscript deploy compose</code> command groups exist but -are <strong>not wired</strong> — they expose no runnable verbs and only print help. Bare-metal -<code>linux</code> deploy is likewise planning-only. Only the Windows Service path and -<code>deno-deploy</code> run today. -{{ /comp }} - -### Windows Service (Servy) +The shared flags (`--org`, `--app`, `--entrypoint`, `--env-file`, `--project-root`), the +planning-only cloud targets, and the artifact-copy verbs are in the +[command reference](/reference/cli/commands/). -The pre-existing Windows path builds and manages Windows Service artifacts from a deployment -manifest via Servy. +## Agent tooling {{ comp.apiTable({ - caption: "Windows Service deployment lifecycle", + caption: "AI agent commands", rows: [ - { name: "netscript deploy build", type: "netscript deploy build", desc: "Build the Windows Service deployment artifacts." }, - { name: "netscript deploy package-cli", type: "netscript deploy package-cli", desc: "Compile a standalone deployment CLI artifact." }, - { name: "netscript deploy install / start / stop", type: "netscript deploy install", desc: "Install, start, and stop Windows Services from a deployment manifest." }, - { name: "netscript deploy status / logs", type: "netscript deploy status", desc: "Show service status and print deployment logs." }, - { name: "netscript deploy upgrade / uninstall", type: "netscript deploy upgrade", desc: "Upgrade an installed deployment, or uninstall the services entirely." } + { name: "Install agent tooling", type: "netscript agent init", desc: "Install NetScript MCP and skills for detected agent hosts (<code>--host claude</code>, <code>vscode</code>, or <code>all</code>)." }, + { name: "Run the MCP server", type: "netscript agent mcp", desc: "Start the NetScript MCP server over standard input/output." } ] }) }} +See [Agent tooling](/ai/agent-tooling/) for the mental model. + ## The full surface -This page is curated for the common path. For every command, every subcommand, and every -flag, see the command reference: +This page is the curated common path. For every command, every subcommand, and every +flag — spelled exactly as the installed CLI prints it — go to the +[command reference](/reference/cli/commands/); for the embeddable package API, the +[`@netscript/cli` package page]({{ "ref:cli" |> xref }}). {{ comp.featureGrid({ items: [ { @@ -392,20 +267,15 @@ flag, see the command reference: { title: "Database & migration", body: "The full db workflow, with the Aspire-up dependency spelled out step by step.", - href: "/how-to/database-migration/", + href: "/data-persistence/how-to/database-migration/", icon: "▤" }, { - title: "Add authentication", - body: "Add the auth plugin, pick a backend via NETSCRIPT_AUTH_BACKEND, migrate auth.prisma, and wire the kv-oauth happy path.", - href: "/how-to/add-authentication/", - icon: "🔑" + title: "@netscript/cli package", + body: "The generated package reference — the embeddable TypeScript surface, not the command tree.", + href: "/reference/cli/", + icon: "◇" } ] }) }} -{{ comp callout { type: "tip", title: "Discover flags in your installed version" } }} -The flags here are the common ones. For the exact spelling in <em>your</em> installed CLI, run -<code>netscript --help</code> or <code>netscript <group> --help</code> (for example <code>netscript db --help</code>). -{{ /comp }} - {{ comp.nextPrev({ prev: { label: "Glossary", href: "/glossary/" } }) }} diff --git a/docs/site/concepts.vto b/docs/site/concepts.vto index 2fa6fafda..da6643939 100644 --- a/docs/site/concepts.vto +++ b/docs/site/concepts.vto @@ -15,7 +15,7 @@ next: { label: "Quickstart — 5 min", href: "/quickstart/" } }) }} {{ comp callout { type: "tip", title: "Read this once" } }} -This is the five-minute mental model. Every capability hub, tutorial, and reference page assumes the three ideas below — so the time you spend here is paid back on every other page. Nothing here is aspirational: each claim links to the published surface that proves it. +This is the five-minute mental model — what talks to what, and nothing deeper; the *why* behind this shape lives in the {{ comp.xref({ key: "explain:architecture", text: "architecture essay" }) }}. Every capability hub, tutorial, and reference page assumes the three ideas below — so the time you spend here is paid back on every other page. Nothing here is aspirational: each claim links to the published surface that proves it. {{ /comp }} NetScript is a Deno-native backend framework that generates a workspace you own and run yourself. It is not a hosted service and not a single library — it is a coordinated package family plus a CLI that wires the boring seams together. To reason about *any* part of it, you only need three ideas. @@ -74,7 +74,7 @@ They are one idea seen from three angles. The **contract** defines what a capabi {{ comp.featureGrid({ items: [ { title: "Learn it by building", body: "Pick a track and ship one complete app end to end — the fastest way to feel all three ideas at once.", href: "/tutorials/", icon: "🛠️" }, { title: "Map the capabilities", body: "Every feature, hub by hub, each with a Learn / Do / Reference triplet.", href: "/capabilities/", icon: "🧭" }, - { title: "Read the architecture", body: "Why the framework is shaped this way — the doctrine behind the three ideas.", href: "/explanation/architecture/", icon: "🏛️" } + { title: "Read the architecture", body: "The deep why behind the three ideas — the full essay this page is the five-minute version of.", href: "/explanation/architecture/", icon: "🏛️" } ] }) }} {{ comp callout { type: "note", title: "Still beta" } }} diff --git a/docs/site/data-persistence/database.md b/docs/site/data-persistence/database.md index f30cdf53f..074e3d4f3 100644 --- a/docs/site/data-persistence/database.md +++ b/docs/site/data-persistence/database.md @@ -4,6 +4,7 @@ title: Database & Prisma templateEngine: [vento, md] prev: { label: "Durable streams", href: "/durable-workflows/streams/" } next: { label: "KV, queues & cron", href: "/data-persistence/kv-queues-cron/" } +order: 1 --- # Database & Prisma @@ -55,7 +56,7 @@ href="/data-persistence/kv-queues-cron/">the Postgres queue backend</a> below. - **Learn** — the [Team Workspace tutorial, step 03](/tutorials/workspace/03-workspace-data/) wires workspace data through the database from scratch. -- **Do** — the [Use a second database](/how-to/use-a-second-database/) recipe adds a second +- **Do** — the [Use a second database](/data-persistence/how-to/use-a-second-database/) recipe adds a second adapter-backed datasource (MSSQL or MySQL) beside the primary Postgres. ## How persistence is wired @@ -315,7 +316,7 @@ A second engine is a separate Prisma schema workspace (for example <code>database/mysql/schema/</code>) with its own <code>generate</code> output and its own migrations — it does <em>not</em> merge into the Postgres aggregation above. Generate and migrate each datasource independently. The full step-by-step lives in the -<a href="/how-to/use-a-second-database/">Use a second database</a> recipe. +<a href="/data-persistence/how-to/use-a-second-database/">Use a second database</a> recipe. {{ /comp }} ## Endpoints & ports @@ -389,13 +390,13 @@ lane that matches what you're doing. { title: "Do — Use a second database", body: "Task recipe: add an MSSQL or MySQL datasource beside Postgres with a driver adapter.", - href: "/how-to/use-a-second-database/", + href: "/data-persistence/how-to/use-a-second-database/", icon: "◆" }, { title: "Do — Database & migration", body: "Task recipe: bring up Postgres with Aspire, then run db init → generate → seed → status → migrate.", - href: "/how-to/database-migration/", + href: "/data-persistence/how-to/database-migration/", icon: "◆" }, { diff --git a/docs/site/how-to/choose-a-queue-provider.md b/docs/site/data-persistence/how-to/choose-a-queue-provider.md similarity index 97% rename from docs/site/how-to/choose-a-queue-provider.md rename to docs/site/data-persistence/how-to/choose-a-queue-provider.md index abafebc0c..f0ad9911c 100644 --- a/docs/site/how-to/choose-a-queue-provider.md +++ b/docs/site/data-persistence/how-to/choose-a-queue-provider.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Choose a queue provider templateEngine: [vento, md] -prev: { label: "Use a second database", href: "/how-to/use-a-second-database/" } -next: { label: "Tune the worker runtime", href: "/how-to/tune-worker-runtime/" } +order: 104 +oldUrl: /how-to/choose-a-queue-provider/ --- # Choose a queue provider @@ -12,7 +12,7 @@ next: { label: "Tune the worker runtime", href: "/how-to/tune-worker-runtime/" } adapter, the zero-config Deno KV default, Redis, RabbitMQ (AMQP), and PostgreSQL — and how to either let auto-discovery select one or pin one explicitly with `provider` + `connection`. This is the *decision* recipe; for the enqueue/consume/cron mechanics see -[Queue / KV / cron](/how-to/queue-kv-cron/). +[Queue / KV / cron](/data-persistence/how-to/queue-kv-cron/). A NetScript queue is provider-agnostic by design: the same `createQueue("jobs")` runs on an in-memory adapter in a unit test, on Deno KV on your laptop, and on a real broker under @@ -177,7 +177,7 @@ await queue.listen(async (message) => { ``` For CPU-bound work, prefer Web Workers over queue concurrency — see -[Tune the worker runtime](/how-to/tune-worker-runtime/). +[Tune the worker runtime](/background-processing/how-to/tune-worker-runtime/). ## Prove it end to end — same call site, two backends @@ -256,4 +256,4 @@ For the full `QueueProvider` enum, the `QueueConnectionOptions` shape, `createPa / `ParallelQueueOptions`, the `MessageQueue` interface, and the `QueueError` hierarchy, see {{ comp.xref({ key: "ref:queue" }) }}. -{{ comp.nextPrev({ prev: { label: "Use a second database", href: "/how-to/use-a-second-database/" }, next: { label: "Tune the worker runtime", href: "/how-to/tune-worker-runtime/" } }) }} +{{ comp.nextPrev({ prev: { label: "Use a second database", href: "/data-persistence/how-to/use-a-second-database/" }, next: { label: "Tune the worker runtime", href: "/background-processing/how-to/tune-worker-runtime/" } }) }} diff --git a/docs/site/how-to/database-migration.md b/docs/site/data-persistence/how-to/database-migration.md similarity index 97% rename from docs/site/how-to/database-migration.md rename to docs/site/data-persistence/how-to/database-migration.md index bcc1b7633..2942cf7da 100644 --- a/docs/site/how-to/database-migration.md +++ b/docs/site/data-persistence/how-to/database-migration.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Database & migration templateEngine: [vento, md] -prev: { label: "Add a service", href: "/how-to/add-a-service/" } -next: { label: "Queue / KV / cron", href: "/how-to/queue-kv-cron/" } +order: 101 +oldUrl: /how-to/database-migration/ --- # Database & migration @@ -64,7 +64,7 @@ Aspire is keeping alive in a second terminal. ] }) }} If you scaffolded with `--no-aspire`, you are responsible for pointing the workspace at -your own Postgres via `POSTGRES_URI` / `DATABASE_URL` (see [Deploy](/how-to/deploy/)); +your own Postgres via `POSTGRES_URI` / `DATABASE_URL` (see [Deploy](/orchestration-runtime/how-to/deploy/)); there is no AppHost to start, so you skip Step 2, but the migration commands in Step 3 are otherwise identical and talk to whatever database those variables resolve to. @@ -238,7 +238,7 @@ import the generated client in a service or worker and read the rows the seed wr Once the schema is live, the generated client is what every other capability reads and writes through. Queue jobs persist their `message_queue` rows (the -[PostgreSQL queue backend](/how-to/queue-kv-cron/) shares this same datasource), durable +[PostgreSQL queue backend](/data-persistence/how-to/queue-kv-cron/) shares this same datasource), durable sagas persist runtime state when configured with the Prisma store backend, and your services query their models directly. The migration loop you just ran is the foundation the rest of the workspace stands on. @@ -251,8 +251,8 @@ rest of the workspace stands on. {{ comp.card({ title: "Orchestration with Aspire", body: "Why the AppHost (aspire/apphost.mts) provisions Postgres and Redis, and how the resource graph fits together.", href: "/explanation/aspire/", icon: "◆" }) }} -{{ comp.card({ title: "Queue / KV / cron", body: "The next recipe: use the KV and queue backends — including the PostgreSQL queue provider that shares this datasource.", href: "/how-to/queue-kv-cron/", icon: "→" }) }} +{{ comp.card({ title: "Queue / KV / cron", body: "The next recipe: use the KV and queue backends — including the PostgreSQL queue provider that shares this datasource.", href: "/data-persistence/how-to/queue-kv-cron/", icon: "→" }) }} -{{ comp.card({ title: "Deploy without Aspire", body: "Point the workspace at your own Postgres via POSTGRES_URI / DATABASE_URL using the --no-aspire escape hatch.", href: "/how-to/deploy/", icon: "→" }) }} +{{ comp.card({ title: "Deploy without Aspire", body: "Point the workspace at your own Postgres via POSTGRES_URI / DATABASE_URL using the --no-aspire escape hatch.", href: "/orchestration-runtime/how-to/deploy/", icon: "→" }) }} For the MySQL adapter surface, see [`prisma-adapter-mysql`](/reference/prisma-adapter-mysql/). diff --git a/docs/site/data-persistence/how-to/index.md b/docs/site/data-persistence/how-to/index.md new file mode 100644 index 000000000..858a0ab27 --- /dev/null +++ b/docs/site/data-persistence/how-to/index.md @@ -0,0 +1,12 @@ +--- +layout: layouts/base.vto +title: Recipes +templateEngine: [vento, md] +order: 100 +--- + +# Data & Persistence — recipes + +Task-oriented recipes for this area. Each one solves a single concrete problem and assumes you know +the basics from the guides above it in the sidebar. The full cross-area catalog lives at +[All how-to recipes]({{ "howto:index" |> xref |> url }}). diff --git a/docs/site/how-to/queue-kv-cron.md b/docs/site/data-persistence/how-to/queue-kv-cron.md similarity index 98% rename from docs/site/how-to/queue-kv-cron.md rename to docs/site/data-persistence/how-to/queue-kv-cron.md index 68d2fa57e..8bcad4d78 100644 --- a/docs/site/how-to/queue-kv-cron.md +++ b/docs/site/data-persistence/how-to/queue-kv-cron.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Queue / KV / cron templateEngine: [vento, md] -prev: { label: "Database & migration", href: "/how-to/database-migration/" } -next: { label: "Add OpenTelemetry", href: "/how-to/add-opentelemetry/" } +order: 103 +oldUrl: /how-to/queue-kv-cron/ --- # Queue / KV / cron @@ -350,9 +350,9 @@ adapter classes are documented in [`@netscript/cron`](/reference/cron/). { title: "Database & migration", body: "The previous recipe — provision Postgres under Aspire and run the db init → generate → seed flow.", - href: "/how-to/database-migration/", + href: "/data-persistence/how-to/database-migration/", icon: "→" } ] }) }} -{{ comp.nextPrev({ prev: { label: "Database & migration", href: "/how-to/database-migration/" }, next: { label: "Add OpenTelemetry", href: "/how-to/add-opentelemetry/" } }) }} +{{ comp.nextPrev({ prev: { label: "Database & migration", href: "/data-persistence/how-to/database-migration/" }, next: { label: "Add OpenTelemetry", href: "/observability/how-to/add-opentelemetry/" } }) }} diff --git a/docs/site/how-to/use-a-second-database.md b/docs/site/data-persistence/how-to/use-a-second-database.md similarity index 98% rename from docs/site/how-to/use-a-second-database.md rename to docs/site/data-persistence/how-to/use-a-second-database.md index de510b489..121f08bab 100644 --- a/docs/site/how-to/use-a-second-database.md +++ b/docs/site/data-persistence/how-to/use-a-second-database.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Use a second database templateEngine: [vento, md] -prev: { label: "Expose OpenAPI & Scalar", href: "/how-to/expose-openapi-scalar/" } -next: { label: "Choose a queue provider", href: "/how-to/choose-a-queue-provider/" } +order: 102 +oldUrl: /how-to/use-a-second-database/ --- # Use a second database @@ -19,7 +19,7 @@ file-backed SQLite primary instead. Everything below applies regardless of which primary uses; the examples simply show the common Postgres-primary case. NetScript's default scaffold gives you **one primary datasource** that every plugin -aggregates its `.prisma` models into (see [Database & migration](/how-to/database-migration/)). +aggregates its `.prisma` models into (see [Database & migration](/data-persistence/how-to/database-migration/)). A second database is the opposite shape: a **separate** Prisma schema workspace with its own `generate` output and its own migration history. It never merges into the primary aggregation. @@ -265,4 +265,4 @@ The order is always: build the adapter, call <code>getDriverAdapter()</code>, pa {{ comp.xref({ key: "explain:aspire", text: "Orchestration with Aspire" }) }} · {{ comp.xref({ key: "cap:kv-queues-cron", text: "KV, queues & cron — the Postgres queue backend" }) }} -{{ comp.nextPrev({ prev: { label: "Expose OpenAPI & Scalar", href: "/how-to/expose-openapi-scalar/" }, next: { label: "Choose a queue provider", href: "/how-to/choose-a-queue-provider/" } }) }} +{{ comp.nextPrev({ prev: { label: "Expose OpenAPI & Scalar", href: "/services-sdk/how-to/expose-openapi-scalar/" }, next: { label: "Choose a queue provider", href: "/data-persistence/how-to/choose-a-queue-provider/" } }) }} diff --git a/docs/site/data-persistence/index.md b/docs/site/data-persistence/index.md index cc7f99066..52abf1d5c 100644 --- a/docs/site/data-persistence/index.md +++ b/docs/site/data-persistence/index.md @@ -28,8 +28,17 @@ workspace resolves a second database. {{ comp.cardsGrid({ columns: 3, cards: [ { eyebrow: "Overview & Concepts", title: "Database and KV", body: "The persistence layer behind services, queues, and workflow state.", href: "/data-persistence/database/", icon: "O" }, { eyebrow: "Quickstart", title: "Data contracts", body: "Follow the Storefront data step before service and saga work expand it.", href: "/tutorials/storefront/03-cart-contracts/", icon: "Q" }, - { eyebrow: "How-To", title: "Database and migration", body: "Initialize and migrate the primary datasource.", href: "/how-to/database-migration/", icon: "H" }, - { eyebrow: "How-To", title: "Second database", body: "Add and address another datasource from the workspace.", href: "/how-to/use-a-second-database/", icon: "H" }, + { eyebrow: "How-To", title: "Database and migration", body: "Initialize and migrate the primary datasource.", href: "/data-persistence/how-to/database-migration/", icon: "H" }, + { eyebrow: "How-To", title: "Second database", body: "Add and address another datasource from the workspace.", href: "/data-persistence/how-to/use-a-second-database/", icon: "H" }, { eyebrow: "API Reference", title: "database", body: "Generated database package symbols.", href: "/reference/database/", icon: "R" }, { eyebrow: "API Reference", title: "kv and Prisma adapter", body: "Generated KV and adapter package symbols.", href: "/reference/kv/", icon: "R" } ] }) }} + +## Learn, do, look up + +{{ comp.cardsGrid({ columns: 4, cards: [ + { eyebrow: "Learn", title: "Storefront tutorial", body: "Cart contracts back a real database schema from chapter 3 on.", href: resolveXref("tut:storefront").href }, + { eyebrow: "Do", title: "Recipes", body: "Task-oriented recipes for this area, one problem each.", href: "/data-persistence/how-to/" }, + { eyebrow: "Look up", title: "`@netscript/database` reference", body: "Generated API reference. Related units: `kv`, `prisma-adapter-mysql`.", href: resolveXref("ref:database").href }, + { eyebrow: "Understand", title: "Architecture", body: "The design rationale behind this pillar.", href: resolveXref("explain:architecture").href }, +] }) }} diff --git a/docs/site/data-persistence/kv-queues-cron.md b/docs/site/data-persistence/kv-queues-cron.md index cfc41f327..ffadd7af3 100644 --- a/docs/site/data-persistence/kv-queues-cron.md +++ b/docs/site/data-persistence/kv-queues-cron.md @@ -4,6 +4,7 @@ title: KV, queues & cron templateEngine: [vento, md] prev: { label: "Database & Prisma", href: "/data-persistence/database/" } next: { label: "Telemetry & logging", href: "/observability/telemetry/" } +order: 2 --- # KV, queues & cron @@ -72,7 +73,7 @@ for you with no manual instrumentation. { title: "Do — Choose a queue provider", body: "Recipe: pick between Deno KV, Redis, RabbitMQ, and PostgreSQL — and wire the Aspire-provisioned, local-fallback, and explicit-provider paths.", - href: "/how-to/choose-a-queue-provider/", + href: "/data-persistence/how-to/choose-a-queue-provider/", icon: "→" }, { @@ -353,7 +354,7 @@ for business-hours schedules. <strong>(5)</strong> always <code>await scheduler. { title: "Do — Choose a queue provider", body: "Task recipe: stand up each primitive in an existing workspace, with the Aspire-provisioned, local-fallback, and explicit-PostgreSQL paths spelled out.", - href: "/how-to/choose-a-queue-provider/", + href: "/data-persistence/how-to/choose-a-queue-provider/", icon: "◆" }, { diff --git a/docs/site/deno.lock b/docs/site/deno.lock index c9ccaa43c..311255423 100644 --- a/docs/site/deno.lock +++ b/docs/site/deno.lock @@ -464,8 +464,10 @@ "https://deno.land/x/lume@v2.5.4/plugins/markdown.ts": "c7027605edee274762edb20f7040ccba6415c5fe656cc6e25ce91c448f467fd8", "https://deno.land/x/lume@v2.5.4/plugins/modify_urls.ts": "137ba076b652f625b72de34692ea34b0fa5bbb5ba55cf7aa1ee789f2ce244bd5", "https://deno.land/x/lume@v2.5.4/plugins/modules.ts": "d88ea1c1ab3bbbb501834f5409e5e00771a3dd4c9661b20e63445d1a80859257", + "https://deno.land/x/lume@v2.5.4/plugins/nav.ts": "d069569183b733866f8390943f3efda12e999985fc58390af5b7c418fc975c18", "https://deno.land/x/lume@v2.5.4/plugins/pagefind.ts": "9b38d8ffb322d7fa4a4e9e8db36f6688835df00eb9901cebeb0b719ea65204fb", "https://deno.land/x/lume@v2.5.4/plugins/paginate.ts": "7dfee977a205dfe0af33a3e406f73017badd2d4593cf27e5bd897da7ab12ba8a", + "https://deno.land/x/lume@v2.5.4/plugins/redirects.ts": "5c408b0de722c8a695f82e295625f881c4ed70b8e6a57a8aa5ecd3c5b4a9df31", "https://deno.land/x/lume@v2.5.4/plugins/search.ts": "ff570560c6ca95598a1cbfb3a77611477ee7dbb53300bcc3ba14d18c9e5eba79", "https://deno.land/x/lume@v2.5.4/plugins/toml.ts": "72c75546056e503a59752e33dc25542f2aa21d743bd47f498d722b97958212f5", "https://deno.land/x/lume@v2.5.4/plugins/url.ts": "3718185697778f3b4dd17924d9d282d0a5a74030301e7fcae8a7f1b21f0ef9a9", diff --git a/docs/site/how-to/build-a-validated-ingestion-queue.md b/docs/site/durable-workflows/how-to/build-a-validated-ingestion-queue.md similarity index 83% rename from docs/site/how-to/build-a-validated-ingestion-queue.md rename to docs/site/durable-workflows/how-to/build-a-validated-ingestion-queue.md index ca92960de..c62526e58 100644 --- a/docs/site/how-to/build-a-validated-ingestion-queue.md +++ b/docs/site/durable-workflows/how-to/build-a-validated-ingestion-queue.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Build a validated ingestion queue templateEngine: [vento, md] -prev: { label: "Build a server-validated form", href: "/how-to/build-a-server-validated-form/" } -next: { label: "Publish a durable stream", href: "/how-to/publish-a-durable-stream/" } +order: 101 +oldUrl: /how-to/build-a-validated-ingestion-queue/ --- # Build a validated ingestion queue @@ -84,11 +84,11 @@ await queue.listen(async (message, context) => { ## Next steps -- Compare providers in [Choose a queue provider](/how-to/choose-a-queue-provider/). -- Use queues with KV and cron in [Queue / KV / cron](/how-to/queue-kv-cron/). +- Compare providers in [Choose a queue provider](/data-persistence/how-to/choose-a-queue-provider/). +- Use queues with KV and cron in [Queue / KV / cron](/data-persistence/how-to/queue-kv-cron/). - Look up the full API in [queue reference](/reference/queue/). {{ comp.nextPrev({ - prev: { label: "Build a server-validated form", href: "/how-to/build-a-server-validated-form/" }, - next: { label: "Publish a durable stream", href: "/how-to/publish-a-durable-stream/" } + prev: { label: "Build a server-validated form", href: "/web-layer/how-to/build-a-server-validated-form/" }, + next: { label: "Publish a durable stream", href: "/durable-workflows/how-to/publish-a-durable-stream/" } }) }} diff --git a/docs/site/durable-workflows/how-to/index.md b/docs/site/durable-workflows/how-to/index.md new file mode 100644 index 000000000..6769e47a8 --- /dev/null +++ b/docs/site/durable-workflows/how-to/index.md @@ -0,0 +1,12 @@ +--- +layout: layouts/base.vto +title: Recipes +templateEngine: [vento, md] +order: 100 +--- + +# Durable workflows — recipes + +Task-oriented recipes for this area. Each one solves a single concrete problem and assumes you know +the basics from the guides above it in the sidebar. The full cross-area catalog lives at +[All how-to recipes]({{ "howto:index" |> xref |> url }}). diff --git a/docs/site/how-to/publish-a-durable-stream.md b/docs/site/durable-workflows/how-to/publish-a-durable-stream.md similarity index 87% rename from docs/site/how-to/publish-a-durable-stream.md rename to docs/site/durable-workflows/how-to/publish-a-durable-stream.md index 26b9fe35c..7c753de07 100644 --- a/docs/site/how-to/publish-a-durable-stream.md +++ b/docs/site/durable-workflows/how-to/publish-a-durable-stream.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Publish a durable stream templateEngine: [vento, md] -prev: { label: "Build a validated ingestion queue", href: "/how-to/build-a-validated-ingestion-queue/" } -next: { label: "Restrict worker task permissions", href: "/how-to/restrict-worker-task-permissions/" } +order: 102 +oldUrl: /how-to/publish-a-durable-stream/ --- # Publish a durable stream @@ -107,6 +107,6 @@ addEventListener('unload', async () => { - Look up the API in [streams reference](/reference/streams/). {{ comp.nextPrev({ - prev: { label: "Build a validated ingestion queue", href: "/how-to/build-a-validated-ingestion-queue/" }, - next: { label: "Restrict worker task permissions", href: "/how-to/restrict-worker-task-permissions/" } + prev: { label: "Build a validated ingestion queue", href: "/durable-workflows/how-to/build-a-validated-ingestion-queue/" }, + next: { label: "Restrict worker task permissions", href: "/background-processing/how-to/restrict-worker-task-permissions/" } }) }} diff --git a/docs/site/durable-workflows/index.md b/docs/site/durable-workflows/index.md index c6b7dad0e..9281ba079 100644 --- a/docs/site/durable-workflows/index.md +++ b/docs/site/durable-workflows/index.md @@ -43,8 +43,17 @@ outcomes differ from a retry loop — read the {{ comp.cardsGrid({ columns: 3, cards: [ { eyebrow: "Overview & Concepts", title: "Durability model", body: "How saga state, trigger ingress, retries, and durable streams fit together.", href: "/explanation/durability-model/", icon: "O" }, { eyebrow: "Quickstart", title: "Checkout saga", body: "Build a multi-step checkout flow in the Storefront tutorial.", href: "/tutorials/storefront/04-checkout-saga/", icon: "Q" }, - { eyebrow: "How-To", title: "Validated ingestion queue", body: "Validate incoming work before handing it to durable processing.", href: "/how-to/build-a-validated-ingestion-queue/", icon: "H" }, - { eyebrow: "How-To", title: "Durable stream", body: "Publish stream events for consumers.", href: "/how-to/publish-a-durable-stream/", icon: "H" }, + { eyebrow: "How-To", title: "Validated ingestion queue", body: "Validate incoming work before handing it to durable processing.", href: "/durable-workflows/how-to/build-a-validated-ingestion-queue/", icon: "H" }, + { eyebrow: "How-To", title: "Durable stream", body: "Publish stream events for consumers.", href: "/durable-workflows/how-to/publish-a-durable-stream/", icon: "H" }, { eyebrow: "API Reference", title: "sagas", body: "Generated saga API symbols.", href: "/reference/sagas/", icon: "R" }, { eyebrow: "API Reference", title: "triggers and streams", body: "Generated trigger and stream package symbols.", href: "/reference/triggers/", icon: "R" } ] }) }} + +## Learn, do, look up + +{{ comp.cardsGrid({ columns: 4, cards: [ + { eyebrow: "Learn", title: "Storefront tutorial", body: "The checkout saga chapter models a multi-step flow with compensation.", href: resolveXref("tut:storefront").href }, + { eyebrow: "Do", title: "Recipes", body: "Task-oriented recipes for this area, one problem each.", href: "/durable-workflows/how-to/" }, + { eyebrow: "Look up", title: "`@netscript/sagas` reference", body: "Generated API reference. Related units: `triggers`, `streams`.", href: resolveXref("ref:sagas").href }, + { eyebrow: "Understand", title: "The durability model", body: "The design rationale behind this pillar.", href: resolveXref("explain:durability-model").href }, +] }) }} diff --git a/docs/site/durable-workflows/sagas.md b/docs/site/durable-workflows/sagas.md index f1f4ab7d7..5cca960a3 100644 --- a/docs/site/durable-workflows/sagas.md +++ b/docs/site/durable-workflows/sagas.md @@ -4,6 +4,7 @@ title: Durable sagas templateEngine: [vento, md] prev: { label: "Background jobs", href: "/background-processing/workers/" } next: { label: "Triggers & ingress", href: "/durable-workflows/triggers/" } +order: 1 --- # Durable sagas diff --git a/docs/site/durable-workflows/streams.md b/docs/site/durable-workflows/streams.md index 354de3fc7..565d5112c 100644 --- a/docs/site/durable-workflows/streams.md +++ b/docs/site/durable-workflows/streams.md @@ -4,6 +4,7 @@ title: Durable streams templateEngine: [vento, md] prev: { label: "Triggers & ingress", href: "/durable-workflows/triggers/" } next: { label: "Database & Prisma", href: "/data-persistence/database/" } +order: 2 --- # Durable streams @@ -92,7 +93,7 @@ report, which is as useful to a CLI doctor or a coding agent as it is to a test. { title: "Do — add the streams plugin", body: "Public package install adds the stream plugin dependency and user-owned glue; local netscript-dev scaffolding supports contributor-source samples.", - href: "/how-to/add-a-plugin/", + href: "/orchestration-runtime/how-to/add-a-plugin/", icon: "◆" }, { diff --git a/docs/site/durable-workflows/triggers.md b/docs/site/durable-workflows/triggers.md index 15ac1c921..4cb5c2ef4 100644 --- a/docs/site/durable-workflows/triggers.md +++ b/docs/site/durable-workflows/triggers.md @@ -4,6 +4,7 @@ title: Triggers & ingress templateEngine: [vento, md] prev: { label: "Durable sagas", href: "/durable-workflows/sagas/" } next: { label: "Durable streams", href: "/durable-workflows/streams/" } +order: 3 --- # Triggers & ingress @@ -73,7 +74,7 @@ processor/ingress runtime (`@netscript/plugin-triggers-core/runtime`). { title: "Do — Add a plugin", body: "The how-to recipe for public plugin package install and the local netscript-dev contributor path for source-backed trigger samples.", - href: "/how-to/add-a-plugin/", + href: "/orchestration-runtime/how-to/add-a-plugin/", icon: "◆" } ] }) }} diff --git a/docs/site/explanation/architecture.md b/docs/site/explanation/architecture.md index 8f89361e8..0d073b59a 100644 --- a/docs/site/explanation/architecture.md +++ b/docs/site/explanation/architecture.md @@ -4,6 +4,7 @@ title: Architecture templateEngine: [vento, md] prev: { label: "Explanation", href: "/explanation/" } next: { label: "Contracts & type flow", href: "/explanation/contracts/" } +order: 1 --- # The NetScript architecture diff --git a/docs/site/explanation/aspire.md b/docs/site/explanation/aspire.md index 35920566b..b93c8ff7b 100644 --- a/docs/site/explanation/aspire.md +++ b/docs/site/explanation/aspire.md @@ -4,6 +4,7 @@ title: Orchestration with Aspire templateEngine: [vento, md] prev: { label: "Observability", href: "/explanation/observability/" } next: { label: "Capabilities", href: "/capabilities/" } +order: 7 --- # Orchestration with Aspire diff --git a/docs/site/explanation/auth-model.md b/docs/site/explanation/auth-model.md index e506c24e3..dcb81772b 100644 --- a/docs/site/explanation/auth-model.md +++ b/docs/site/explanation/auth-model.md @@ -4,17 +4,20 @@ title: The pure-backend auth model templateEngine: [vento, md] prev: { label: "The plugin system", href: "/explanation/plugin-system/" } next: { label: "Durability model", href: "/explanation/durability-model/" } +order: 4 --- # The pure-backend auth model +*This essay is the **why** — the model and its trade-offs. The day-to-day *how* (guides, recipes, API) lives in **Build › [Identity & Access](/identity-access/)**.* + This page explains *what* NetScript authentication actually is, *why* it is designed as a **pure-backend seam** rather than a built-in identity provider, and *how* a single typed port lets you swap GitHub OAuth for WorkOS or better-auth without touching one line of application code. It is understanding-oriented — read it to build the mental model before you wire authentication into a project. When you want the headline API and endpoints, see the [auth capability](/capabilities/auth/); when you want to do the task, follow -[Add authentication](/how-to/add-authentication/); when you want exact exported symbols, follow +[Add authentication](/identity-access/how-to/add-authentication/); when you want exact exported symbols, follow [`reference/service/`](/reference/service/) and the auth adapter references it links to. {{ comp callout { type: "important", title: "Alpha status" } }} @@ -248,7 +251,7 @@ boundary, you own the logic) — authentication is just the identity-shaped inst - **The capability:** [Authentication](/capabilities/auth/) — the headline API, the `auth-api` service on :8094, the five endpoints, and the backend matrix at a glance. -- **Do it:** [Add authentication](/how-to/add-authentication/) — add the `auth` plugin, choose a +- **Do it:** [Add authentication](/identity-access/how-to/add-authentication/) — add the `auth` plugin, choose a backend with `NETSCRIPT_AUTH_BACKEND`, run the migration, and wire provider env. - **Related model:** [The plugin system](/explanation/plugin-system/) — why a thin plugin composes a pure capability, the same shape this auth seam follows. diff --git a/docs/site/explanation/contracts.md b/docs/site/explanation/contracts.md index b2d0a03b1..7cc34f2d4 100644 --- a/docs/site/explanation/contracts.md +++ b/docs/site/explanation/contracts.md @@ -4,6 +4,7 @@ title: Contracts & type flow templateEngine: [vento, md] prev: { label: "Architecture", href: "/explanation/architecture/" } next: { label: "The plugin system", href: "/explanation/plugin-system/" } +order: 2 --- # Contracts & type flow diff --git a/docs/site/explanation/durability-model.md b/docs/site/explanation/durability-model.md index c21fd1589..8cdf97d72 100644 --- a/docs/site/explanation/durability-model.md +++ b/docs/site/explanation/durability-model.md @@ -4,10 +4,13 @@ title: Durability model templateEngine: [vento, md] prev: { label: "Auth model", href: "/explanation/auth-model/" } next: { label: "Observability", href: "/explanation/observability/" } +order: 5 --- # Durability model +*This essay is the **why** — the model and its trade-offs. The day-to-day *how* (guides, recipes, API) lives in **Build › [Durable workflows](/durable-workflows/)**.* + This essay is **understanding-oriented**. It answers one question: *how does NetScript make long-running, message-driven work survive a process restart?* It builds the mental model — what a saga is, how its state is persisted, where that state physically lives, and how compensation and diff --git a/docs/site/explanation/observability.md b/docs/site/explanation/observability.md index a6a05a353..81828ded8 100644 --- a/docs/site/explanation/observability.md +++ b/docs/site/explanation/observability.md @@ -4,16 +4,19 @@ title: Observability templateEngine: [vento, md] prev: { label: "Durability model", href: "/explanation/durability-model/" } next: { label: "Orchestration with Aspire", href: "/explanation/aspire/" } +order: 6 --- # Observability +*This essay is the **why** — the model and its trade-offs. The day-to-day *how* (guides, recipes, API) lives in **Build › [Observability](/observability/)**.* + This essay answers one question: how does NetScript make a *distributed, multi-process* application observable, so that one logical operation reads as one story even though it crosses HTTP, a queue, a saga, and a worker subprocess? The answer is a single idea applied everywhere — **the trace context travels with the work** — wired into the framework boundaries so you inherit it for free. Read this to build the mental model; to wire spans yourself, follow the -[how-to: add OpenTelemetry](/how-to/add-opentelemetry/); for the headline API and ports, see the +[how-to: add OpenTelemetry](/observability/how-to/add-opentelemetry/); for the headline API and ports, see the {{ comp.xref({ key: "cap:telemetry" }) }} hub; for exact exported symbols, see {{ comp.xref({ key: "ref:telemetry" }) }}. @@ -272,7 +275,7 @@ the same span model. ## Where to go next -- **Do it:** the [how-to: add OpenTelemetry](/how-to/add-opentelemetry/) walks adding a custom span, +- **Do it:** the [how-to: add OpenTelemetry](/observability/how-to/add-opentelemetry/) walks adding a custom span, structured logs, and `traceparent` propagation against a running service. - **Hub:** the {{ comp.xref({ key: "cap:telemetry" }) }} hub covers the headline API and the OTel-wired-into-boundaries model with the real endpoints; {{ comp.xref({ key: "cap:auth" }) }} diff --git a/docs/site/explanation/plugin-system.md b/docs/site/explanation/plugin-system.md index 2a279d017..e7dea879d 100644 --- a/docs/site/explanation/plugin-system.md +++ b/docs/site/explanation/plugin-system.md @@ -4,6 +4,7 @@ title: The plugin system templateEngine: [vento, md] prev: { label: "Contracts & type flow", href: "/explanation/contracts/" } next: { label: "Auth model", href: "/explanation/auth-model/" } +order: 3 --- # The plugin system @@ -269,7 +270,7 @@ and topics through the same vocabulary — each running as its own isolated reso ## Where to go next {{ comp.featureGrid({ items: [ - { title: "Add a first-party plugin", body: "The task-oriented recipe: scaffold, register, regenerate, verify.", href: "/how-to/add-a-plugin/", icon: "🔌" }, + { title: "Add a first-party plugin", body: "The task-oriented recipe: scaffold, register, regenerate, verify.", href: "/orchestration-runtime/how-to/add-a-plugin/", icon: "🔌" }, { title: "The auth model", body: "The five-unit core/adapter/plugin split — the model at its richest.", href: "/explanation/auth-model/", icon: "🔐" }, { title: "Orchestration with Aspire", body: "How the AppHost turns manifests into running API and background resources.", href: "/explanation/aspire/", icon: "🧩" }, { title: "Plugin authoring contract", body: "definePlugin(), the contribution axes, and the registry emitter.", href: "/reference/plugin/", icon: "📐" } diff --git a/docs/site/glossary.md b/docs/site/glossary.md index 527abac06..31cb722e2 100644 --- a/docs/site/glossary.md +++ b/docs/site/glossary.md @@ -37,7 +37,7 @@ These are the words you will meet first — in the [Quickstart](/quickstart/), t {{ comp.apiTable({ caption: "A–C", rows: [ { name: "AppHost", type: "aspire/apphost.mts", desc: "The Aspire orchestration entry point that boots your whole workspace — Postgres, Redis, services, and plugin processors — as one resource graph with a dashboard at https://localhost:18888. In NetScript the AppHost is a <strong>generated Node/TypeScript</strong> file (<code>aspire/apphost.mts</code>) driven by <code>aspire.config.json</code> and <code>appsettings.json</code>, not a dotnet project. You start it with <code>cd aspire && aspire start</code>, and it must be up <em>before</em> any <code>netscript db</code> command. See <a href=\"/explanation/aspire/\">Explanation → Aspire</a> and <a href=\"/reference/aspire/\">reference/aspire/</a>." }, { name: "archetype", type: "doctrine classification", desc: "The architectural category a package or plugin belongs to (for example a contract unit, an adapter, a runtime, or a plugin), which determines its public surface, quality gates, and allowed dependencies. Archetype is a <em>framework-internal</em> doctrine term; as an app author you mostly meet it indirectly through the shape of a scaffolded plugin. See <a href=\"/explanation/plugin-system/\">Explanation → Plugin system</a>." }, - { name: "Aspire", type: "aspire start", desc: "The .NET Aspire orchestration layer NetScript uses for local development. It provisions your database (Postgres is the recommended engine; or <code>mysql</code> / <code>mssql</code> via <code>--db</code>, each an Aspire container resource — <code>sqlite</code> is file-backed and adds no container) and Redis as Docker containers and wires every service and plugin processor together with health, logs, and OTLP traces — no manual <code>docker compose</code>. The escape hatch is <code>netscript init --no-aspire</code> for a leaner single-process loop. See <a href=\"/explanation/aspire/\">Explanation → Aspire</a> and <a href=\"/how-to/deploy/\">How-to → Deploy</a>." }, + { name: "Aspire", type: "aspire start", desc: "The .NET Aspire orchestration layer NetScript uses for local development. It provisions your database (Postgres is the recommended engine; or <code>mysql</code> / <code>mssql</code> via <code>--db</code>, each an Aspire container resource — <code>sqlite</code> is file-backed and adds no container) and Redis as Docker containers and wires every service and plugin processor together with health, logs, and OTLP traces — no manual <code>docker compose</code>. The escape hatch is <code>netscript init --no-aspire</code> for a leaner single-process loop. See <a href=\"/explanation/aspire/\">Explanation → Aspire</a> and <a href=\"/orchestration-runtime/how-to/deploy/\">How-to → Deploy</a>." }, { name: "AuthBackendPort", type: "@netscript/plugin-auth-core/ports", desc: "The single seam every authentication backend implements — defined by <code>@netscript/plugin-auth-core</code>, it composes provider, session-store, crypto, and principal-mapper sub-ports plus an <code>authenticate(request)</code> method and an <em>optional</em> <code>interactive</code> flow. Backends are pure adapters behind this port; the host never special-cases a vendor. See <a href=\"/capabilities/auth/\">Capabilities → Authentication</a> and <a href=\"/explanation/auth-model/\">Explanation → Auth model</a>." }, { name: "AuthBackendOperationUnsupportedError", type: "typed capability boundary", desc: "The typed error a backend throws when you call an operation it does not implement — for example a session mutation on a stateless backend, or an interactive sign-in on a non-interactive one. It makes the <strong>single-active-backend</strong> capability matrix fail loud rather than silently no-op, so missing surface is a visible error, not a mystery. See <a href=\"/explanation/auth-model/\">Explanation → Auth model</a> and the <a href=\"/capabilities/auth/\">Capabilities → Auth</a> export list." }, { name: "AuthSession", type: "@netscript/plugin-auth-core/domain", desc: "The domain record for an authenticated session — id, user, account, and state (<code>active</code> | <code>expired</code> | <code>revoked</code>) — defined as a Zod-validated type in <code>@netscript/plugin-auth-core</code>. It is the durable shape the session store reads and writes and the entity the auth stream projects. See <a href=\"/explanation/auth-model/\">Explanation → Auth model</a> and <a href=\"/capabilities/auth/\">Capabilities → Authentication</a>." }, @@ -59,7 +59,7 @@ These are the words you will meet first — in the [Quickstart](/quickstart/), t { name: "manifest", type: "plugins/<name>/mod.ts", desc: "A plugin's public face — its <code>mod.ts</code> — which exports the plugin object (for example <code>workersPlugin</code> or <code>authPlugin</code>), an inspector (<code>inspectWorkers</code>, <code>inspectAuth</code>), and the contribution types the host discovers. The composition root references each plugin by its manifest path, and a generated <strong>registry</strong> aggregates them. See <a href=\"/explanation/plugin-system/\">Explanation → Plugin system</a>." }, { name: "oRPC", type: "@orpc/contract · @orpc/server", desc: "The contract-and-RPC toolkit (<a href=\"https://orpc.unnoq.com\">@orpc/*</a>, pinned at ^1.14.6) that underpins NetScript's typed APIs: contracts are declared with <code>oc.route().input().output()</code>, implemented with <code>implement(...)</code>, served by <code>defineService</code> / <code>createService</code>, and consumed by a fully typed client. Workers, sagas, the auth service, and triggers all expose oRPC (mounted under <code>/api/rpc/*</code>); triggers' only exception is the raw, HMAC-verifying <strong>webhook ingress endpoint</strong>. See <a href=\"/explanation/contracts/\">Explanation → Contracts</a>." }, { name: "opaque session token (HMAC)", type: "createHmacSessionTokenCrypto(secret)", desc: "The default session-token scheme in the auth core — a WebCrypto HMAC-SHA256 token that carries no readable claims (opaque to the client) and is verified server-side against a secret. It is what the <code>AuthSessionCryptoPort</code> produces by default, so a stolen token reveals nothing and cannot be forged without the secret. See <a href=\"/explanation/auth-model/\">Explanation → Auth model</a> and <a href=\"/capabilities/auth/\">Capabilities → Auth</a>." }, - { name: "plugin", type: "package + generated glue", desc: "An installable, thread-isolated capability. Public workspaces install plugin packages with commands such as <code>netscript plugin install @netscript/plugin-workers</code>, which add the dependency and emit user-owned glue/sample files that import the package; local-source contributor samples use <code>deno run -A packages/cli/bin/netscript-dev.ts plugin install worker --name workers --samples</code>. The plugin's contracts, services, runtime entrypoints, and Prisma schema stay in the installed package unless you are authoring or syncing full source. See <a href=\"/explanation/plugin-system/\">Explanation → Plugin system</a> and <a href=\"/how-to/add-a-plugin/\">How-to → Add a plugin</a>." }, + { name: "plugin", type: "package + generated glue", desc: "An installable, thread-isolated capability. Public workspaces install plugin packages with commands such as <code>netscript plugin install @netscript/plugin-workers</code>, which add the dependency and emit user-owned glue/sample files that import the package; local-source contributor samples use <code>deno run -A packages/cli/bin/netscript-dev.ts plugin install worker --name workers --samples</code>. The plugin's contracts, services, runtime entrypoints, and Prisma schema stay in the installed package unless you are authoring or syncing full source. See <a href=\"/explanation/plugin-system/\">Explanation → Plugin system</a> and <a href=\"/orchestration-runtime/how-to/add-a-plugin/\">How-to → Add a plugin</a>." }, { name: "Principal", type: "@netscript/service/auth", desc: "The authenticated identity an authenticator resolves from a request — id, scopes, and a <code>scheme</code> (<code>'api-key'</code> | <code>'bearer'</code> | <code>'trusted-header'</code> | <code>'custom'</code>). The auth-plugin backends map their sessions to a Principal with <code>scheme: 'custom'</code>, so service-layer authz treats vendor-backed identities uniformly. See <a href=\"/explanation/auth-model/\">Explanation → Auth model</a>." } ] }) }} @@ -83,7 +83,7 @@ the data layer, the auth seams, and the moving parts under a running plugin. {{ comp.apiTable({ caption: "Infrastructure, data & auth internals", rows: [ { name: "appsettings.json", type: "infra config", desc: "The root infrastructure manifest the Aspire <strong>AppHost</strong> actually reads — declaring <code>NetScript.Databases</code> (the database engine — Postgres is the recommended engine, or <code>mysql</code> / <code>mssql</code> / <code>sqlite</code> selected at scaffold with <code>--db</code> — container mode, primary database), <code>NetScript.Cache</code> (the <code>redis</code> cache by default; <code>garnet</code> or <code>deno-kv</code> via <code>--cache-backend</code>), the services, and the per-plugin <code>Workdir</code>s. It is also where backend selections like <code>sagas.store.backend</code> and <code>auth.backend</code> can live. Note that <code>netscript.config.ts</code>'s <code>databases</code> block is intentionally near-empty; the live DB/cache config lives here. See <a href=\"/explanation/aspire/\">Explanation → Aspire</a>." }, - { name: "auth backend", type: "@netscript/auth-*", desc: "A pure adapter that implements <strong>AuthBackendPort</strong>: <code>kv-oauth</code> (interactive OAuth/OIDC redirect flow, KV sessions — the only one exposing <strong>InteractiveFlowPort</strong>), <code>workos</code> (AuthKit sealed cookie, non-interactive), and <code>better-auth</code> (Prisma-backed, non-interactive). The plugin composes exactly one — see <strong>single-active-backend</strong> — and serves the unified auth oRPC surface on port <code>:8094</code>. See <a href=\"/explanation/auth-model/\">Explanation → Auth model</a> and <a href=\"/how-to/add-authentication/\">How-to → Add authentication</a>." }, + { name: "auth backend", type: "@netscript/auth-*", desc: "A pure adapter that implements <strong>AuthBackendPort</strong>: <code>kv-oauth</code> (interactive OAuth/OIDC redirect flow, KV sessions — the only one exposing <strong>InteractiveFlowPort</strong>), <code>workos</code> (AuthKit sealed cookie, non-interactive), and <code>better-auth</code> (Prisma-backed, non-interactive). The plugin composes exactly one — see <strong>single-active-backend</strong> — and serves the unified auth oRPC surface on port <code>:8094</code>. See <a href=\"/explanation/auth-model/\">Explanation → Auth model</a> and <a href=\"/identity-access/how-to/add-authentication/\">How-to → Add authentication</a>." }, { name: "background processor", type: "bin/combined.ts", desc: "The separate, long-running process that executes a plugin's work, distinct from its HTTP API service. Workers run from <code>bin/combined.ts</code>; sagas run from <code>src/runtime/saga-runner.ts</code>; triggers run from <code>src/runtime/trigger-processor.ts</code>. The AppHost starts these as their own Aspire resources. See <a href=\"/explanation/plugin-system/\">Explanation → Plugin system</a>." }, { name: "Garnet", type: "Cache.garnet (KV)", desc: "One of the Redis-compatible cache/KV backends Aspire can provision as a container alongside Postgres (the default is <code>redis</code>; select Garnet with <code>--cache-backend garnet</code>), used for executions, saga registry metadata, kv-oauth sessions, and Deno KV-backed state. See <a href=\"/capabilities/kv-queues-cron/\">Capabilities → KV / queues / cron</a> and <a href=\"/reference/kv/\">reference/kv/</a>." }, { name: "OTel / observability", type: "@netscript/telemetry", desc: "OpenTelemetry tracing and structured logging wired into the framework. Job dispatch, execution, step events, progress, scheduler runs, and subprocess trace continuation emit <em>real</em> spans — traces show up in Aspire automatically (OTLP endpoint at http://localhost:4318). The one remaining gap is the scaffold <code>createJobTools(ctx)</code> handler helpers (<code>trace.addEvent</code> / <code>withChildSpan</code> / <code>progress</code>), which are still no-op stubs (tracked debt, fix planned) — for custom handler spans call <code>@netscript/telemetry</code> helpers directly. See <a href=\"/explanation/observability/\">Explanation → Observability</a> and <a href=\"/capabilities/telemetry/\">Capabilities → Telemetry</a>." }, diff --git a/docs/site/how-to/index.md b/docs/site/how-to/index.md index 38911a8a5..0bd41cdbb 100644 --- a/docs/site/how-to/index.md +++ b/docs/site/how-to/index.md @@ -1,99 +1,136 @@ --- layout: layouts/base.vto -title: How-to guides +title: All recipes templateEngine: [vento, md] prev: null -next: { label: "Add a plugin", href: "/how-to/add-a-plugin/" } +next: { label: "Add a plugin", href: "/orchestration-runtime/how-to/add-a-plugin/" } +nav_hide: true --- -How-to guides are **goal-first recipes**: each one starts from a concrete -intent — *"I need to add a service,"* *"I need auth,"* *"I need this to deploy -without Aspire"* — and gives you the shortest reliable path from that intent to a -working, verified change. They assume you already have a NetScript workspace and -know the basics; they do **not** re-teach the framework. +# All recipes -If NetScript is new to you, start with the [tutorials](/tutorials/) — they build -one continuous application from zero. For exact API signatures, use the -[reference](/reference/). For the concepts *behind* a task — why services are -contracts-first, what "durable" means, how Aspire wires dependencies — read the -[explanation](/explanation/) pages. Each recipe links back to the capability hub -and reference that go deeper. +Recipes live inside their pillar in the sidebar — each area's **Recipes** section +sits below the guides that teach the basics it assumes. This page is the +cross-area index: every recipe in the docs, grouped by pillar, so you can scan +the whole catalog in one place. Each one is goal-first — it starts from a +concrete intent and ends with a command that proves the change works. {{ comp callout { tone: "info", title: "One prerequisite spans almost every recipe" } }} Anything that touches Postgres, Redis/Garnet, or a plugin service expects Aspire to be running first. From your workspace: <code>cd aspire && aspire start</code> brings up the dependencies and the dashboard on <a href="https://localhost:18888"><code>https://localhost:18888</code></a> -<strong>before</strong> any <code>netscript db</code> command or service call. The -recipes call this out where it matters, but it is the single most common missing -step. +<strong>before</strong> any <code>netscript db</code> command or service call. +The recipes call this out where it matters, but it is the single most common +missing step. {{ /comp }} -## Build & extend a workspace - -These recipes add capabilities to an existing workspace and verify the wiring. -Each emits the relevant workspace-owned glue, samples, or service files and ends with a -command you can run to confirm it works. - -{{ comp.featureGrid({ items: [ - { title: "Add a plugin", body: "Install a first-party plugin through the public package flow, or use the local netscript-dev path for contributor-source samples. Emits user-owned glue, regenerates the registry, and verifies the service answers on its port.", href: "/how-to/add-a-plugin/" }, - { title: "Add a service", body: "Stand up a new typed oRPC service: define an @orpc/contract + zod contract, implement() the handlers, serve it with defineService(...) one-shot or createService(...).serve() fluent, and confirm it answers on /api/rpc/*.", href: "/how-to/add-a-service/" }, - { title: "Add authentication", body: "Add the official auth plugin (auth-api on :8094, five endpoints under /api/v1/auth/*). Pick one active backend via NETSCRIPT_AUTH_BACKEND — kv-oauth (interactive, default), WorkOS, or better-auth — run the auth.prisma migration, and sign in.", href: "/how-to/add-authentication/" }, - { title: "Database & migration", body: "Initialize, generate, seed, and inspect the Postgres schema: netscript db init --name init → db generate → db seed → db status. Requires aspire start first so Postgres is provisioned.", href: "/how-to/database-migration/" } -] }) }} - -## Set up the developer environment - -Recipes for the toolchain around a NetScript workspace: editor intelligence, orchestration, and -runtime setup that should behave the same on every device. - -{{ comp.featureGrid({ items: [ - { title: "Deno LSP code intelligence", body: "Install the Claude Code Deno LSP plugin, enable the LSP tool globally, and keep go-to-definition, hover, references, symbols, and diagnostics aligned across CLI, VS Code, and Zed.", href: "/how-to/deno-lsp-code-intelligence/" }, - { title: "Deploy locally with Aspire", body: "Run the full local resource graph from the generated Aspire AppHost: dashboard, infrastructure, services, plugin APIs, and background processors.", href: "/how-to/deploy-local-aspire/" } -] }) }} - -## Wire primitives & observability - -Recipes for the shared building blocks every plugin leans on — queues, KV, cron, -and the OpenTelemetry traces that make them visible in the Aspire dashboard. - -{{ comp.featureGrid({ items: [ - { title: "Queue / KV / cron", body: "Use the reactive KV store, the durable queue (four backends — RabbitMQ, Redis, Deno KV, and explicit-provider PostgreSQL), and cron schedules. Covers --unstable-kv and the auto-discovery order vs. an explicit provider:'postgres'.", href: "/how-to/queue-kv-cron/" }, - { title: "Add OpenTelemetry", body: "Emit custom spans and structured logs with @netscript/telemetry helpers, propagate traceparent across services, and read the traces that land in the Aspire dashboard. Worker job dispatch/execution traces are already real and automatic.", href: "/how-to/add-opentelemetry/" } -] }) }} - -## Ship the UI & deploy - -Recipes for the front end and for taking a workspace to production — including -the Aspire-free portability path. - -{{ comp.featureGrid({ items: [ - { title: "Customize the Fresh UI", body: "Bring in and own the dashboard UI with the ui:init / ui:add tasks. The scaffold uses copy-source ownership — the components land in your workspace, so you edit them directly rather than depending on a hidden package.", href: "/how-to/customize-fresh-ui/" }, - { title: "Build a durable chat", body: "Wire an AI chat onto a Fresh route whose transcript survives reload and reconnect via a durable session stream; hydrate the fresh-ui chat components; and add one server-side tool.", href: "/how-to/build-a-durable-chat/" }, - { title: "Deploy", body: "The portability and config story: the raw deno task entry points behind each service, plus the --no-aspire escape hatch when you provision Postgres and Redis (or Garnet) yourself. Docker, Compose, and Linux targets are config-only scaffolding today, not runnable deploy verbs — for a first-class hosted path, see Deploy to Deno Deploy.", href: "/how-to/deploy/" }, - { title: "Deploy to Deno Deploy", body: "Push a workspace to Deno Deploy with the first-class netscript deploy deno-deploy plan | up | down | status | logs command: preflight the unstable-API guard, push a preview, promote to prod, and read status and logs.", href: "/how-to/deploy-deno-deploy/" }, - { title: "Author a plugin", body: "Advanced: build a custom plugin from scratch. Defines the scaffold.plugin.json provider kind, the manifest exports, and the mod.ts contract the host discovers — the same shape the first-party plugins use.", href: "/how-to/author-a-plugin/" } -] }) }} - -## How a recipe is shaped - -Every how-to page follows the same contract so you always know where to look: - -- **Goal** — one sentence stating exactly what you will have when you finish. -- **Prerequisites** — the workspace state and running dependencies the recipe - assumes (almost always including a live `aspire start`). -- **Steps** — added-lines code blocks, annotated with the file path they belong - in, using the public `netscript <cmd>` command form throughout. -- **Production pitfalls** — the caveats that bite in real deployments, stated - plainly rather than glossed over. -- **See also** — the capability hub, reference page, and related recipes that - take the topic further. - -{{ comp callout { tone: "note", title: "Pick by intent, not by feature name" } }} -If you are not sure which recipe you want, name the outcome first. <em>"Users -must sign in"</em> → <a href="/how-to/add-authentication/">Add authentication</a>. -<em>"This event should fan out to a background job"</em> → -<a href="/how-to/add-a-plugin/">Add a plugin</a> (triggers + workers). -<em>"It has to run where there is no Aspire"</em> → -<a href="/how-to/deploy/">Deploy</a>. The recipes are deliberately small and -composable; most real features chain two or three of them. -{{ /comp }} +## Web Layer + +- [Customize the Fresh UI]({{ "howto:customize-fresh-ui" |> xref |> url }}) — + bring the dashboard components into your workspace with `ui:init` / `ui:add` + and edit them directly. +- [Build a server-validated form]({{ "howto:build-a-server-validated-form" |> xref |> url }}) — + route-bound form state, server validation, mutation, and success handling in + one typed page definition with `definePage().withForm()`. +- [Build a desktop frontend]({{ "howto:build-a-desktop-frontend" |> xref |> url }}) — + one Fresh frontend that runs as an ordinary browser app and gains native + capabilities on the desktop. + +## Services & SDK + +- [Add a service]({{ "howto:add-a-service" |> xref |> url }}) — define a typed + contract, implement the handlers, and confirm the service answers on + `/api/rpc/*`. +- [Discover services]({{ "howto:discover-services" |> xref |> url }}) — call + another plugin's or workspace member's typed service without hardcoding its + address. +- [Expose OpenAPI & Scalar]({{ "howto:expose-openapi-scalar" |> xref |> url }}) — + publish an OpenAPI document and browsable Scalar docs for a service. + +## Background jobs + +- [Run a polyglot task]({{ "howto:run-a-polyglot-task" |> xref |> url }}) — + define a non-TypeScript script (Python, shell, .NET, any executable) as a + task with a permission sandbox and run it through the executor. +- [Tune the worker runtime]({{ "howto:tune-worker-runtime" |> xref |> url }}) — + trade throughput against isolation with concurrency, runner mode, per-task + permissions, and timeouts/retries. +- [Restrict worker task permissions]({{ "howto:restrict-worker-task-permissions" |> xref |> url }}) — + give every Deno task explicit permissions; an omitted permission object + compiles to `--allow-all`. +- [Add a task runtime adapter]({{ "howto:add-a-task-runtime-adapter" |> xref |> url }}) — + advanced: add a custom runtime adapter to the built-in task executor. + +## Durable workflows + +- [Build a validated ingestion queue]({{ "howto:build-a-validated-ingestion-queue" |> xref |> url }}) — + a typed queue whose messages are schema-checked before they enter the queue + and again before a consumer handles them. +- [Publish a durable stream]({{ "howto:publish-a-durable-stream" |> xref |> url }}) — + let server-side state be subscribed to by browsers and other consumers + through a durable stream service. + +## AI & Agents + +- [Build a durable chat]({{ "howto:build-a-durable-chat" |> xref |> url }}) — + an AI chat on a Fresh route whose transcript survives reload and reconnect, + with one server-side tool. + +## Data & Persistence + +- [Database & migration]({{ "howto:database-migration" |> xref |> url }}) — + initialize, generate, seed, and inspect the Postgres schema with the + `netscript db` commands. +- [Queue / KV / cron]({{ "howto:queue-kv-cron" |> xref |> url }}) — the reactive + KV store, the durable queue, and cron schedules, including `--unstable-kv`. +- [Choose a queue provider]({{ "howto:choose-a-queue-provider" |> xref |> url }}) — + pick the right queue backend, and either let auto-discovery select one or pin + one explicitly. +- [Use a second database]({{ "howto:use-a-second-database" |> xref |> url }}) — + add a second Postgres, or a MySQL/SQL Server instance, alongside the default + database. + +## Identity & Access + +- [Add authentication]({{ "howto:add-authentication" |> xref |> url }}) — install + the official auth plugin, pick one active backend via + `NETSCRIPT_AUTH_BACKEND`, migrate, and sign in. + +## Orchestration & Runtime + +- [Add a plugin]({{ "howto:add-a-plugin" |> xref |> url }}) — install a + first-party plugin, regenerate the registry, and verify the service answers. +- [Deploy locally with Aspire]({{ "howto:deploy-local-aspire" |> xref |> url }}) — + run the full local resource graph from the generated Aspire AppHost. +- [Deploy]({{ "howto:deploy" |> xref |> url }}) — the portability story: raw + `deno task` entry points and the `--no-aspire` escape hatch when you provision + dependencies yourself. +- [Deploy to Deno Deploy]({{ "howto:deploy-deno-deploy" |> xref |> url }}) — + push a preview, promote to prod, and read status and logs with the + first-class deploy command. +- [Graceful shutdown]({{ "howto:graceful-shutdown" |> xref |> url }}) — drain + in-flight requests and jobs, run teardown hooks, and close connections on + `SIGINT`/`SIGTERM`. +- [Roll out runtime overrides]({{ "howto:roll-out-runtime-overrides" |> xref |> url }}) — + change a deployed behavior without rebuilding the workspace. +- [Author a plugin]({{ "howto:author-a-plugin" |> xref |> url }}) — advanced: + build a custom plugin with the same manifest and `mod.ts` contract the + first-party plugins use. +- [Deno LSP code intelligence]({{ "howto:deno-lsp-code-intelligence" |> xref |> url }}) — + keep go-to-definition, hover, and diagnostics aligned across CLI and editors. + +## Observability + +- [Add OpenTelemetry]({{ "howto:add-opentelemetry" |> xref |> url }}) — emit + custom spans and structured logs, propagate `traceparent`, and read the + traces in the Aspire dashboard. + +--- + +Not sure which recipe you want? Name the outcome first. *"Users must sign in"* → +[Add authentication]({{ "howto:add-authentication" |> xref |> url }}). +*"This event should fan out to a background job"* → +[Add a plugin]({{ "howto:add-a-plugin" |> xref |> url }}). +*"It has to run where there is no Aspire"* → +[Deploy]({{ "howto:deploy" |> xref |> url }}). The recipes are deliberately +small and composable; most real features chain two or three of them. diff --git a/docs/site/identity-access/auth.md b/docs/site/identity-access/auth.md index 558dd44ee..1e74eabc6 100644 --- a/docs/site/identity-access/auth.md +++ b/docs/site/identity-access/auth.md @@ -4,6 +4,7 @@ title: Authentication templateEngine: [vento, md] prev: { label: "Fresh UI & design", href: "/web-layer/fresh-ui/" } next: null +order: 1 --- # Authentication @@ -42,7 +43,7 @@ or OIDC provider</strong> — a typed <code>/api/v1/auth/*</code> surface your s UI can call to sign users in, resolve the current session, and sign out. The default <code>kv-oauth</code> backend gives you a complete interactive redirect flow with KV-stored sessions and an opaque HMAC-signed session token. To wire it into a workspace step by step, see -<a href="/how-to/add-authentication/">Add authentication</a>; to understand the pure-backend +<a href="/identity-access/how-to/add-authentication/">Add authentication</a>; to understand the pure-backend port model and why only one backend is active, see <a href="/explanation/auth-model/">The authentication model</a>. {{ /comp }} @@ -106,7 +107,7 @@ and how `InteractiveFlowPort` gates the redirect flow — is in { title: "Do — Add authentication", body: "Task recipe: add the auth plugin, set NETSCRIPT_AUTH_BACKEND, run the auth.prisma migration, and wire kv-oauth with a provider preset.", - href: "/how-to/add-authentication/", + href: "/identity-access/how-to/add-authentication/", icon: "◆" }, { @@ -296,7 +297,7 @@ better-auth-shaped models — `User` → `auth_users`, `Session` → `auth_sessi `Account` → `auth_accounts`, `Verification` → `auth_verifications`. These tables back the better-auth adapter; `kv-oauth` keeps its sessions in Deno KV, and WorkOS is stateless. As with every plugin schema, you bring the tables to life by running the database workflow **after Aspire -is up** — see [Database migrations](/how-to/database-migration/). +is up** — see [Database migrations](/data-persistence/how-to/database-migration/). The plugin also emits five durable `auth.*` runtime events through the durable-streams runtime — the `AUTH_STREAM_EVENT_TYPES`: `auth.signin.started`, `auth.signin.failed`, @@ -375,7 +376,7 @@ adapters now have dedicated generated reference pages. { title: "Do — Add authentication", body: "Task recipe: add the auth plugin, set NETSCRIPT_AUTH_BACKEND, run the auth.prisma migration, and wire kv-oauth with a provider preset.", - href: "/how-to/add-authentication/", + href: "/identity-access/how-to/add-authentication/", icon: "◆" }, { diff --git a/docs/site/identity-access/better-auth-plugins.md b/docs/site/identity-access/better-auth-plugins.md index ddcd86495..65787fb08 100644 --- a/docs/site/identity-access/better-auth-plugins.md +++ b/docs/site/identity-access/better-auth-plugins.md @@ -2,6 +2,7 @@ layout: layouts/base.vto title: better-auth plugins templateEngine: [vento, md] +order: 2 --- # better-auth plugins diff --git a/docs/site/how-to/add-authentication.md b/docs/site/identity-access/how-to/add-authentication.md similarity index 99% rename from docs/site/how-to/add-authentication.md rename to docs/site/identity-access/how-to/add-authentication.md index c0cbe89c3..5ce355e39 100644 --- a/docs/site/how-to/add-authentication.md +++ b/docs/site/identity-access/how-to/add-authentication.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Add authentication templateEngine: [vento, md] -prev: { label: "Author a plugin", href: "/how-to/author-a-plugin/" } -next: null +order: 101 +oldUrl: /how-to/add-authentication/ --- # Add authentication @@ -157,7 +157,7 @@ netscript db seed # optional seed data netscript db status # confirm the migration is applied ``` -See [Run a database migration](/how-to/database-migration/) for the full DB workflow and the +See [Run a database migration](/data-persistence/how-to/database-migration/) for the full DB workflow and the Aspire-up dependency. ## Step 4 — Configure the provider and secrets @@ -354,7 +354,7 @@ aspire start</code> first.</li> {{ comp.card({ title: "Run a database migration", body: "The full db init / generate / seed / status workflow that applies auth.prisma.", - href: "/how-to/database-migration/", + href: "/data-persistence/how-to/database-migration/", icon: "+" }) }} diff --git a/docs/site/identity-access/how-to/index.md b/docs/site/identity-access/how-to/index.md new file mode 100644 index 000000000..b561c099d --- /dev/null +++ b/docs/site/identity-access/how-to/index.md @@ -0,0 +1,12 @@ +--- +layout: layouts/base.vto +title: Recipes +templateEngine: [vento, md] +order: 100 +--- + +# Identity & Access — recipes + +Task-oriented recipes for this area. Each one solves a single concrete problem and assumes you know +the basics from the guides above it in the sidebar. The full cross-area catalog lives at +[All how-to recipes]({{ "howto:index" |> xref |> url }}). diff --git a/docs/site/identity-access/index.md b/docs/site/identity-access/index.md index 064de5f6e..ffdaf2689 100644 --- a/docs/site/identity-access/index.md +++ b/docs/site/identity-access/index.md @@ -26,8 +26,17 @@ failure modes the contract closes off — is on the { eyebrow: "Capability", title: "Authentication", body: "The auth plugin story: one active backend, five endpoints, fail-loud typed boundaries, and the salt-gated audit surface.", href: "/identity-access/auth/", icon: "C" }, { eyebrow: "Overview & Concepts", title: "Auth model", body: "Principal, session, backend, and authorization vocabulary.", href: "/explanation/auth-model/", icon: "O" }, { eyebrow: "Quickstart", title: "Workspace auth", body: "Add authentication in the Workspace tutorial.", href: "/tutorials/workspace/02-auth/", icon: "Q" }, - { eyebrow: "How-To", title: "Add authentication", body: "Wire authentication into a generated workspace.", href: "/how-to/add-authentication/", icon: "H" }, + { eyebrow: "How-To", title: "Add authentication", body: "Wire authentication into a generated workspace.", href: "/identity-access/how-to/add-authentication/", icon: "H" }, { eyebrow: "How-To", title: "better-auth plugins", body: "Mount better-auth plugins through a typed passthrough; bearer and jwt run as-is, table-backed and interactive plugins carry caveats.", href: "/identity-access/better-auth-plugins/", icon: "H" }, { eyebrow: "API Reference", title: "auth backends", body: "Generated symbols for auth, better-auth, kv-oauth, and WorkOS packages.", href: "/reference/auth/", icon: "R" }, { eyebrow: "API Reference", title: "plugin auth", body: "Generated plugin-auth package symbols.", href: "/reference/plugin-auth/", icon: "R" } ] }) }} + +## Learn, do, look up + +{{ comp.cardsGrid({ columns: 4, cards: [ + { eyebrow: "Learn", title: "Workspace tutorial", body: "Auth, workspace data, and route authorization in one track.", href: resolveXref("tut:workspace").href }, + { eyebrow: "Do", title: "Recipes", body: "Task-oriented recipes for this area, one problem each.", href: "/identity-access/how-to/" }, + { eyebrow: "Look up", title: "`@netscript/auth` reference", body: "Generated API reference. Related units: `auth-better-auth`, `auth-kv-oauth`, `auth-workos`, `plugin-auth`.", href: resolveXref("ref:auth").href }, + { eyebrow: "Understand", title: "The auth model", body: "The design rationale behind this pillar.", href: resolveXref("explain:auth-model").href }, +] }) }} diff --git a/docs/site/how-to/add-opentelemetry.md b/docs/site/observability/how-to/add-opentelemetry.md similarity index 99% rename from docs/site/how-to/add-opentelemetry.md rename to docs/site/observability/how-to/add-opentelemetry.md index 591123517..797aa9cf2 100644 --- a/docs/site/how-to/add-opentelemetry.md +++ b/docs/site/observability/how-to/add-opentelemetry.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Add OpenTelemetry templateEngine: [vento, md] -prev: { "label": "Queue / KV / cron", "href": "/how-to/queue-kv-cron/" } -next: { "label": "Customize Fresh UI", "href": "/how-to/customize-fresh-ui/" } +order: 101 +oldUrl: /how-to/add-opentelemetry/ --- # Add OpenTelemetry diff --git a/docs/site/observability/how-to/index.md b/docs/site/observability/how-to/index.md new file mode 100644 index 000000000..aef58c6de --- /dev/null +++ b/docs/site/observability/how-to/index.md @@ -0,0 +1,12 @@ +--- +layout: layouts/base.vto +title: Recipes +templateEngine: [vento, md] +order: 100 +--- + +# Observability — recipes + +Task-oriented recipes for this area. Each one solves a single concrete problem and assumes you know +the basics from the guides above it in the sidebar. The full cross-area catalog lives at +[All how-to recipes]({{ "howto:index" |> xref |> url }}). diff --git a/docs/site/observability/index.md b/docs/site/observability/index.md index 0cfa7a4f8..a60907bf5 100644 --- a/docs/site/observability/index.md +++ b/docs/site/observability/index.md @@ -17,7 +17,7 @@ or connect package-level telemetry to the running system. {{ comp.cardsGrid({ columns: 3, cards: [ { eyebrow: "Overview & Concepts", title: "Telemetry model", body: "Trace context and logging across services, workers, sagas, and orchestration.", href: "/explanation/observability/", icon: "O" }, { eyebrow: "Quickstart", title: "Turn on tracing", body: "Run aspire start, trigger a job, and watch the runtime's automatic spans land in the dashboard — the lowest-effort path to a first trace.", href: "/observability/telemetry/", icon: "Q" }, - { eyebrow: "How-To", title: "Add OpenTelemetry", body: "Wire OTel into a workspace.", href: "/how-to/add-opentelemetry/", icon: "H" }, + { eyebrow: "How-To", title: "Add OpenTelemetry", body: "Wire OTel into a workspace.", href: "/observability/how-to/add-opentelemetry/", icon: "H" }, { eyebrow: "API Reference", title: "telemetry", body: "Generated telemetry package symbols.", href: "/reference/telemetry/", icon: "R" }, { eyebrow: "API Reference", title: "logger", body: "Generated logger package symbols.", href: "/reference/logger/", icon: "R" } ] }) }} @@ -44,3 +44,12 @@ learn the model: {{ comp.xref({ key: "ref:logger", text: "logger" }) }} references, and the [telemetry convention](/reference/telemetry/convention/) (span naming, SpanKind, the `netscript.*` attribute rules). + +## Learn, do, look up + +{{ comp.cardsGrid({ columns: 4, cards: [ + { eyebrow: "Learn", title: "Trace the request model", body: "Follow one request through services, jobs, and streams in the core-concepts tour.", href: resolveXref("concept:concepts").href }, + { eyebrow: "Do", title: "Recipes", body: "Task-oriented recipes for this area, one problem each.", href: "/observability/how-to/" }, + { eyebrow: "Look up", title: "`@netscript/telemetry` reference", body: "Generated API reference. Related units: `logger`.", href: resolveXref("ref:telemetry").href }, + { eyebrow: "Understand", title: "Observability", body: "The design rationale behind this pillar.", href: resolveXref("explain:observability").href }, +] }) }} diff --git a/docs/site/observability/telemetry.md b/docs/site/observability/telemetry.md index b97e95422..63b4b82dc 100644 --- a/docs/site/observability/telemetry.md +++ b/docs/site/observability/telemetry.md @@ -4,6 +4,7 @@ title: Telemetry & logging templateEngine: [vento, md] prev: { label: "KV, queues & cron", href: "/data-persistence/kv-queues-cron/" } next: { label: "Fresh UI & design", href: "/web-layer/fresh-ui/" } +order: 1 --- # Telemetry & logging @@ -44,7 +45,7 @@ orchestrator — they are not separate processes you start by hand. Run command (Aspire provisions Postgres and Redis first), then open the dashboard URL printed in the console (<code>https://localhost:18888</code>, with a one-time auth token). Until Aspire is running there is no <code>:18888</code> surface to view traces or logs on. See -<a href="/how-to/database-migration/">Database & migration</a> for the full startup order. +<a href="/data-persistence/how-to/database-migration/">Database & migration</a> for the full startup order. {{ /comp }} ## The story: one grouped trace, end to end @@ -100,7 +101,7 @@ NetScript-owned keys under the single proprietary root `netscript.*` — correla { title: "Do — Add OpenTelemetry", body: "Task-oriented recipe: lean on the automatic job spans, add custom spans via @netscript/telemetry withChildSpan, propagate traceparent across services and subprocesses, and point the OTLP export (:4318) at the dashboard.", - href: "/how-to/add-opentelemetry/", + href: "/observability/how-to/add-opentelemetry/", icon: "◆" }, { @@ -485,7 +486,7 @@ This hub is intentionally thin — the full generated API lives in the reference { title: "Do — Add OpenTelemetry", body: "Task-oriented recipe: lean on the automatic job spans, add custom spans via @netscript/telemetry withChildSpan, propagate traceparent across services and subprocesses, and point the OTLP export (:4318) at the dashboard.", - href: "/how-to/add-opentelemetry/", + href: "/observability/how-to/add-opentelemetry/", icon: "◆" }, { diff --git a/docs/site/orchestration-runtime/cli-scaffold.md b/docs/site/orchestration-runtime/cli-scaffold.md index 78ca4ea7e..ede4a6c47 100644 --- a/docs/site/orchestration-runtime/cli-scaffold.md +++ b/docs/site/orchestration-runtime/cli-scaffold.md @@ -4,6 +4,7 @@ title: CLI and Scaffold templateEngine: [vento, md] prev: { label: "Orchestration & Runtime", href: "/orchestration-runtime/" } next: { label: "Runtime configuration", href: "/orchestration-runtime/runtime-config/" } +order: 1 --- # CLI & scaffold diff --git a/docs/site/how-to/add-a-plugin.md b/docs/site/orchestration-runtime/how-to/add-a-plugin.md similarity index 97% rename from docs/site/how-to/add-a-plugin.md rename to docs/site/orchestration-runtime/how-to/add-a-plugin.md index 579236345..d2bfb58a5 100644 --- a/docs/site/how-to/add-a-plugin.md +++ b/docs/site/orchestration-runtime/how-to/add-a-plugin.md @@ -2,12 +2,8 @@ layout: layouts/base.vto title: Add a plugin templateEngine: [vento, md] -prev: - label: How-to guides - href: /how-to/ -next: - label: Add a service - href: /how-to/add-a-service/ +order: 106 +oldUrl: /how-to/add-a-plugin/ --- # Add a plugin @@ -102,7 +98,7 @@ dependency, emits a user-owned <code>auth/mod.ts</code> glue barrel, registers t <code>auth-api</code> service on port <strong>8094</strong>, and contributes its package-provided Prisma models. The active backend is selected at runtime with <code>NETSCRIPT_AUTH_BACKEND</code> (default <code>kv-oauth</code>). See -<a href="/how-to/add-authentication/">Configure authentication</a> for the backend setup. +<a href="/identity-access/how-to/add-authentication/">Configure authentication</a> for the backend setup. {{ /comp }} ## Step 2 — Add the plugin @@ -258,8 +254,8 @@ Run `netscript plugin --help` for the complete, version-accurate command set. ### Where to go next - **Build on the plugin you just added.** Next up: - [Add a service](/how-to/add-a-service/) to give the plugin something to call, or - [Configure authentication](/how-to/add-authentication/) if you added the auth plugin. + [Add a service](/services-sdk/how-to/add-a-service/) to give the plugin something to call, or + [Configure authentication](/identity-access/how-to/add-authentication/) if you added the auth plugin. - **Understand the model.** Read [Plugin architecture](/explanation/plugin-system/) for the design behind installable capabilities, ports, and runtime registration. - **Browse capabilities.** The [capabilities](/capabilities/) section maps each plugin to the diff --git a/docs/site/how-to/author-a-plugin.md b/docs/site/orchestration-runtime/how-to/author-a-plugin.md similarity index 98% rename from docs/site/how-to/author-a-plugin.md rename to docs/site/orchestration-runtime/how-to/author-a-plugin.md index 020372b8f..d30422796 100644 --- a/docs/site/how-to/author-a-plugin.md +++ b/docs/site/orchestration-runtime/how-to/author-a-plugin.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Author a plugin templateEngine: [vento, md] -prev: { label: "Deploy to Deno Deploy", href: "/how-to/deploy-deno-deploy/" } -next: { label: "Add authentication", href: "/how-to/add-authentication/" } +order: 107 +oldUrl: /how-to/author-a-plugin/ --- # Author a plugin @@ -11,7 +11,7 @@ next: { label: "Add authentication", href: "/how-to/add-authentication/" } **Scope.** This recipe shows how to author a *new* custom plugin from scratch — its canonical location, the manifest it exports through `definePlugin(...)`, the contribution shape the kernel reads, and the generated registry that makes those contributions discoverable at runtime. It is the -advanced companion to [Add a first-party plugin](/how-to/add-a-plugin/), which only *installs* one +advanced companion to [Add a first-party plugin](/orchestration-runtime/how-to/add-a-plugin/), which only *installs* one of the four official plugins (`workers`, `sagas`, `triggers`, `streams`). If you just want a worker or a saga, install it there. Come here when you need a capability NetScript does not ship. @@ -346,7 +346,7 @@ When your plugin grows beyond a single package — a core seam plus swappable ad The pattern to copy: a **core package owns the port** (`AuthBackendPort`), each **adapter is pure** (implements the port, declares no service), and the **plugin under `plugins/<name>/`** composes one active adapter into a single service and registry. That keeps adapters swappable and the kernel-facing -manifest small. Build the full thing in [Add authentication](/how-to/add-authentication/) — that page +manifest small. Build the full thing in [Add authentication](/identity-access/how-to/add-authentication/) — that page is the concrete walkthrough for the auth plugin specifically. {{ comp callout { type: "note", title: "Alpha package pins" } }} @@ -390,14 +390,14 @@ declare it with <code>.withDependencies({...})</code> on the manifest and in {{ comp.card({ title: "Add a first-party plugin", body: "Install one of the four official plugins instead of writing your own.", - href: "/how-to/add-a-plugin/", + href: "/orchestration-runtime/how-to/add-a-plugin/", icon: "+" }) }} {{ comp.card({ title: "Add authentication", body: "Build the multi-package auth plugin — the production exemplar for a core seam plus swappable backends.", - href: "/how-to/add-authentication/", + href: "/identity-access/how-to/add-authentication/", icon: "→" }) }} diff --git a/docs/site/how-to/deno-lsp-code-intelligence.md b/docs/site/orchestration-runtime/how-to/deno-lsp-code-intelligence.md similarity index 97% rename from docs/site/how-to/deno-lsp-code-intelligence.md rename to docs/site/orchestration-runtime/how-to/deno-lsp-code-intelligence.md index 9c2a4d189..5a8be798b 100644 --- a/docs/site/how-to/deno-lsp-code-intelligence.md +++ b/docs/site/orchestration-runtime/how-to/deno-lsp-code-intelligence.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Deno LSP code intelligence in Claude Code templateEngine: [vento, md] -prev: { label: "How-to guides", href: "/how-to/" } -next: { label: "Deploy locally with Aspire", href: "/how-to/deploy-local-aspire/" } +order: 108 +oldUrl: /how-to/deno-lsp-code-intelligence/ --- # Deno LSP code intelligence in Claude Code diff --git a/docs/site/how-to/deploy-deno-deploy.md b/docs/site/orchestration-runtime/how-to/deploy-deno-deploy.md similarity index 97% rename from docs/site/how-to/deploy-deno-deploy.md rename to docs/site/orchestration-runtime/how-to/deploy-deno-deploy.md index 2cd7dc848..a228d1e0e 100644 --- a/docs/site/how-to/deploy-deno-deploy.md +++ b/docs/site/orchestration-runtime/how-to/deploy-deno-deploy.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Deploy to Deno Deploy templateEngine: [vento, md] -prev: { label: "Deploy", href: "/how-to/deploy/" } -next: { label: "Author a plugin", href: "/how-to/author-a-plugin/" } +order: 103 +oldUrl: /how-to/deploy-deno-deploy/ --- # Deploy to Deno Deploy @@ -23,7 +23,7 @@ config-to-argv wiring, nothing more. <code>netscript deploy deno-deploy</code> is fully wired and runnable. The Docker, Compose, and Linux/systemd targets are <strong>config schema only</strong> — there is no runnable <code>netscript deploy docker|compose|linux</code> verb yet. For those targets you assemble the -deployment yourself from the primitives in the <a href="/how-to/deploy/">Deploy</a> recipe. +deployment yourself from the primitives in the <a href="/orchestration-runtime/how-to/deploy/">Deploy</a> recipe. {{ /comp }} ## Before you start @@ -185,7 +185,7 @@ Many NetScript runtime plugins (workers, sagas, triggers) require Deno KV and th `--unstable-kv`. Those background processors are not a fit for a single Deno Deploy isolate — deploy a KV-free entrypoint (for example a Fresh app or a stateless oRPC service) here, and run the KV-backed processors on infrastructure that permits `--unstable-kv`, as covered in the -[Deploy](/how-to/deploy/) recipe. +[Deploy](/orchestration-runtime/how-to/deploy/) recipe. ## Troubleshooting @@ -207,19 +207,19 @@ processors on infrastructure that permits `--unstable-kv`, as covered in the { title: "Deploy (containers & bare metal)", body: "The manual path: deployable units from appsettings.json, backing services, per-process deno run commands, and the --no-aspire escape hatch.", - href: "/how-to/deploy/", + href: "/orchestration-runtime/how-to/deploy/", icon: "◆" }, { title: "Deploy locally with Aspire", body: "Run the whole graph on one machine under the Aspire AppHost before you ship anything remote.", - href: "/how-to/deploy-local-aspire/", + href: "/orchestration-runtime/how-to/deploy-local-aspire/", icon: "◎" }, { title: "Add OpenTelemetry", body: "Wire spans and traceparent propagation to your OTLP collector so a deployed process is observable.", - href: "/how-to/add-opentelemetry/", + href: "/observability/how-to/add-opentelemetry/", icon: "≋" } ] }) }} diff --git a/docs/site/how-to/deploy-local-aspire.md b/docs/site/orchestration-runtime/how-to/deploy-local-aspire.md similarity index 97% rename from docs/site/how-to/deploy-local-aspire.md rename to docs/site/orchestration-runtime/how-to/deploy-local-aspire.md index e0bda9505..6c90bfe8e 100644 --- a/docs/site/how-to/deploy-local-aspire.md +++ b/docs/site/orchestration-runtime/how-to/deploy-local-aspire.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Deploy locally with Aspire templateEngine: [vento, md] -prev: { label: "Graceful shutdown", href: "/how-to/graceful-shutdown/" } -next: { label: "How-to guides", href: "/how-to/" } +order: 102 +oldUrl: /how-to/deploy-local-aspire/ --- # Deploy locally with Aspire @@ -11,7 +11,7 @@ next: { label: "How-to guides", href: "/how-to/" } **Goal:** run your whole NetScript workspace on one machine under .NET Aspire — scaffold the AppHost, bring up the resource graph (Postgres, Redis, every service and background processor), and watch it from the Aspire dashboard. This is the **local** orchestration companion to the -[Deploy](/how-to/deploy/) recipe (which covers shipping to a remote target); for *why* the +[Deploy](/orchestration-runtime/how-to/deploy/) recipe (which covers shipping to a remote target); for *why* the AppHost works the way it does, read [Orchestration with Aspire](/explanation/aspire/). {{ comp callout { type: "important", title: "The order is: scaffold → orchestrate → database" } }} @@ -198,4 +198,4 @@ a Deno workspace problem.</li> - **Exact symbols + full port map:** {{ comp.xref({ key: "ref:aspire", text: "the Aspire reference" }) }} and the {{ comp.xref({ key: "cli:reference", text: "CLI reference" }) }}. -{{ comp.nextPrev({ prev: { label: "Graceful shutdown", href: "/how-to/graceful-shutdown/" }, next: { label: "How-to guides", href: "/how-to/" } }) }} +{{ comp.nextPrev({ prev: { label: "Graceful shutdown", href: "/orchestration-runtime/how-to/graceful-shutdown/" }, next: { label: "How-to guides", href: "/how-to/" } }) }} diff --git a/docs/site/how-to/deploy.md b/docs/site/orchestration-runtime/how-to/deploy.md similarity index 98% rename from docs/site/how-to/deploy.md rename to docs/site/orchestration-runtime/how-to/deploy.md index 0ae0cc4c4..69d4b46e9 100644 --- a/docs/site/how-to/deploy.md +++ b/docs/site/orchestration-runtime/how-to/deploy.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Deploy templateEngine: [vento, md] -prev: { label: "Build a durable chat", href: "/how-to/build-a-durable-chat/" } -next: { label: "Deploy to Deno Deploy", href: "/how-to/deploy-deno-deploy/" } +order: 101 +oldUrl: /how-to/deploy/ --- # Deploy a NetScript workspace @@ -51,7 +51,7 @@ The scaffolded workflows cover the deploy surfaces that ship today: (<code>netscript deploy build</code>). They are intentionally starter pipelines: fill in repository secrets, GitHub environment protection, host credentials, and target-specific configuration before you treat them as production release jobs. See -<a href="/how-to/deploy-deno-deploy/">Deploy to Deno Deploy</a> for the managed-platform command +<a href="/orchestration-runtime/how-to/deploy-deno-deploy/">Deploy to Deno Deploy</a> for the managed-platform command reference. {{ /comp }} @@ -63,7 +63,7 @@ There is now a first-class, runnable managed-platform path: preflight guard and <code>deploy.targets['deno-deploy']</code> config. Aspire-backed targets are also routed: <code>netscript deploy docker|compose|kubernetes|azure-aca|azure-app-service|azure-aks|cloud-run <op></code> delegates to the target adapter. See -<a href="/how-to/deploy-deno-deploy/">Deploy to Deno Deploy</a> for Deno Deploy details. +<a href="/orchestration-runtime/how-to/deploy-deno-deploy/">Deploy to Deno Deploy</a> for Deno Deploy details. {{ /comp }} ## Before you start @@ -181,7 +181,7 @@ Postgres is provisioned <strong>by Aspire</strong>. So the order is always: <code>db seed</code>. Running a DB command with no Postgres reachable — for example in an isolated CI container — fails fast. In production, point the same commands at your managed Postgres via <code>POSTGRES_URI</code> or <code>DATABASE_URL</code> instead of relying on -Aspire. See <a href="/how-to/database-migration/">Database migration</a> for the full sequence. +Aspire. See <a href="/data-persistence/how-to/database-migration/">Database migration</a> for the full sequence. {{ /comp }} ## Step 3 — Provision backing services @@ -419,7 +419,7 @@ deployment is live. { title: "Deploy to Deno Deploy", body: "The one runnable managed-platform path: netscript deploy deno-deploy plan/up/status/logs/down, the unstable-API guard, and deploy.targets['deno-deploy'] config.", - href: "/how-to/deploy-deno-deploy/", + href: "/orchestration-runtime/how-to/deploy-deno-deploy/", icon: "◆" }, { @@ -431,13 +431,13 @@ deployment is live. { title: "Database migration", body: "Run db init → generate → seed → status against Postgres — and why aspire start comes first.", - href: "/how-to/database-migration/", + href: "/data-persistence/how-to/database-migration/", icon: "▣" }, { title: "Add OpenTelemetry", body: "Wire spans and traceparent propagation to your OTLP collector in production.", - href: "/how-to/add-opentelemetry/", + href: "/observability/how-to/add-opentelemetry/", icon: "≋" } ] }) }} diff --git a/docs/site/how-to/graceful-shutdown.md b/docs/site/orchestration-runtime/how-to/graceful-shutdown.md similarity index 97% rename from docs/site/how-to/graceful-shutdown.md rename to docs/site/orchestration-runtime/how-to/graceful-shutdown.md index 7fe144851..6f574d0a5 100644 --- a/docs/site/how-to/graceful-shutdown.md +++ b/docs/site/orchestration-runtime/how-to/graceful-shutdown.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Graceful shutdown templateEngine: [vento, md] -prev: { label: "Run a polyglot task", href: "/how-to/run-a-polyglot-task/" } -next: { label: "Deploy locally with Aspire", href: "/how-to/deploy-local-aspire/" } +order: 105 +oldUrl: /how-to/graceful-shutdown/ --- # Graceful shutdown @@ -241,7 +241,7 @@ entrypoint by hand. { title: "Do — Tune the worker runtime", body: "Recipe: concurrency, retry, and the runtime knobs that decide how much in-flight work a drain has to wait on.", - href: "/how-to/tune-worker-runtime/", + href: "/background-processing/how-to/tune-worker-runtime/", icon: "◆" }, { @@ -252,4 +252,4 @@ entrypoint by hand. } ] }) }} -{{ comp.nextPrev({ prev: { label: "Run a polyglot task", href: "/how-to/run-a-polyglot-task/" }, next: { label: "Deploy locally with Aspire", href: "/how-to/deploy-local-aspire/" } }) }} +{{ comp.nextPrev({ prev: { label: "Run a polyglot task", href: "/background-processing/how-to/run-a-polyglot-task/" }, next: { label: "Deploy locally with Aspire", href: "/orchestration-runtime/how-to/deploy-local-aspire/" } }) }} diff --git a/docs/site/orchestration-runtime/how-to/index.md b/docs/site/orchestration-runtime/how-to/index.md new file mode 100644 index 000000000..4a5b79a08 --- /dev/null +++ b/docs/site/orchestration-runtime/how-to/index.md @@ -0,0 +1,12 @@ +--- +layout: layouts/base.vto +title: Recipes +templateEngine: [vento, md] +order: 100 +--- + +# Orchestration & Runtime — recipes + +Task-oriented recipes for this area. Each one solves a single concrete problem and assumes you know +the basics from the guides above it in the sidebar. The full cross-area catalog lives at +[All how-to recipes]({{ "howto:index" |> xref |> url }}). diff --git a/docs/site/how-to/roll-out-runtime-overrides.md b/docs/site/orchestration-runtime/how-to/roll-out-runtime-overrides.md similarity index 87% rename from docs/site/how-to/roll-out-runtime-overrides.md rename to docs/site/orchestration-runtime/how-to/roll-out-runtime-overrides.md index f1c6fe960..9360ce937 100644 --- a/docs/site/how-to/roll-out-runtime-overrides.md +++ b/docs/site/orchestration-runtime/how-to/roll-out-runtime-overrides.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Roll out runtime overrides templateEngine: [vento, md] -prev: { label: "Deploy locally with Aspire", href: "/how-to/deploy-local-aspire/" } -next: { label: "Add a task runtime adapter", href: "/how-to/add-a-task-runtime-adapter/" } +order: 104 +oldUrl: /how-to/roll-out-runtime-overrides/ --- # Roll out runtime overrides @@ -106,11 +106,11 @@ The next watcher reload sees the previous version and calls `onChange` again. ## Next steps -- Tune worker behavior with [Tune the worker runtime](/how-to/tune-worker-runtime/). -- Deploy the process with [Deploy](/how-to/deploy/). +- Tune worker behavior with [Tune the worker runtime](/background-processing/how-to/tune-worker-runtime/). +- Deploy the process with [Deploy](/orchestration-runtime/how-to/deploy/). - Look up the package surface in [runtime-config reference](/reference/runtime-config/). {{ comp.nextPrev({ - prev: { label: "Deploy locally with Aspire", href: "/how-to/deploy-local-aspire/" }, - next: { label: "Add a task runtime adapter", href: "/how-to/add-a-task-runtime-adapter/" } + prev: { label: "Deploy locally with Aspire", href: "/orchestration-runtime/how-to/deploy-local-aspire/" }, + next: { label: "Add a task runtime adapter", href: "/background-processing/how-to/add-a-task-runtime-adapter/" } }) }} diff --git a/docs/site/orchestration-runtime/index.md b/docs/site/orchestration-runtime/index.md index 50bd8af02..1dfc1bab2 100644 --- a/docs/site/orchestration-runtime/index.md +++ b/docs/site/orchestration-runtime/index.md @@ -26,9 +26,9 @@ workspace up, change runtime overrides, or understand how plugin contributions b { eyebrow: "Story", title: "CLI & scaffold", body: "One command to a complete workspace — and why generated conventions save agent turns.", href: "/orchestration-runtime/cli-scaffold/", icon: "S" }, { eyebrow: "Story", title: "Runtime configuration", body: "Typed project config plus hot-reloadable operator overrides.", href: "/orchestration-runtime/runtime-config/", icon: "S" }, { eyebrow: "Quickstart", title: "Run the workspace", body: "Scaffold and start a workspace from the quickstart.", href: "/quickstart/", icon: "Q" }, - { eyebrow: "How-To", title: "Deploy locally with Aspire", body: "Use Aspire to bring up local resources.", href: "/how-to/deploy-local-aspire/", icon: "H" }, - { eyebrow: "How-To", title: "Runtime overrides", body: "Roll out configuration overrides across resources.", href: "/how-to/roll-out-runtime-overrides/", icon: "H" }, - { eyebrow: "How-To", title: "Plugins", body: "Add or author a plugin contribution.", href: "/how-to/add-a-plugin/", icon: "H" }, + { eyebrow: "How-To", title: "Deploy locally with Aspire", body: "Use Aspire to bring up local resources.", href: "/orchestration-runtime/how-to/deploy-local-aspire/", icon: "H" }, + { eyebrow: "How-To", title: "Runtime overrides", body: "Roll out configuration overrides across resources.", href: "/orchestration-runtime/how-to/roll-out-runtime-overrides/", icon: "H" }, + { eyebrow: "How-To", title: "Plugins", body: "Add or author a plugin contribution.", href: "/orchestration-runtime/how-to/add-a-plugin/", icon: "H" }, { eyebrow: "API Reference", title: "aspire and runtime config", body: "Generated symbols for AppHost, config, runtime-config, plugin, and CLI units.", href: "/reference/aspire/", icon: "R" } ] }) }} @@ -50,3 +50,12 @@ New here, start with the concept, then the practical wiring, then the reference: dashboard side of the same graph — the spans and logs `aspire start` collects. - **Look up exact symbols:** {{ comp.xref({ key: "ref:aspire", text: "the Aspire reference" }) }} and the {{ comp.xref({ key: "cli:reference", text: "CLI reference" }) }}. + +## Learn, do, look up + +{{ comp.cardsGrid({ columns: 4, cards: [ + { eyebrow: "Learn", title: "Quickstart", body: "From `netscript init` to a running, orchestrated workspace.", href: resolveXref("concept:quickstart").href }, + { eyebrow: "Do", title: "Recipes", body: "Task-oriented recipes for this area, one problem each.", href: "/orchestration-runtime/how-to/" }, + { eyebrow: "Look up", title: "`@netscript/aspire` reference", body: "Generated API reference. Related units: `cli`, `config`, `runtime-config`, `plugin`.", href: resolveXref("ref:aspire").href }, + { eyebrow: "Understand", title: "Orchestration with Aspire", body: "The design rationale behind this pillar.", href: resolveXref("explain:aspire").href }, +] }) }} diff --git a/docs/site/orchestration-runtime/runtime-config.md b/docs/site/orchestration-runtime/runtime-config.md index b53e3bb1c..12e6ac060 100644 --- a/docs/site/orchestration-runtime/runtime-config.md +++ b/docs/site/orchestration-runtime/runtime-config.md @@ -4,6 +4,7 @@ title: Runtime configuration templateEngine: [vento, md] prev: { label: "Polyglot tasks", href: "/background-processing/polyglot-tasks/" } next: { label: "Capabilities", href: "/capabilities/" } +order: 2 --- # Runtime configuration @@ -56,7 +57,7 @@ deliberately distinct: `@netscript/config` owns the build-time contract, and { title: "Do — Choose a queue provider", body: "A config-driven decision recipe: pick and wire a queue backend through project config.", - href: "/how-to/choose-a-queue-provider/", + href: "/data-persistence/how-to/choose-a-queue-provider/", icon: "◆" } ] }) }} diff --git a/docs/site/quickstart.vto b/docs/site/quickstart.vto index 47c006c37..524835fc3 100644 --- a/docs/site/quickstart.vto +++ b/docs/site/quickstart.vto @@ -104,7 +104,7 @@ service, and so on) — on the strict happy path above, the four steps are suffi On the default path, <strong>Aspire is step 2 of running your app</strong>: <code>cd aspire && aspire start</code> provisions Postgres and Redis first. Any <code>netscript db</code> command (init, generate, seed) runs <em>after</em> Aspire is up, because it needs that database to exist. See -<a href="/how-to/database-migration/">Database migrations</a> for the full flow. +<a href="/data-persistence/how-to/database-migration/">Database migrations</a> for the full flow. {{ /comp }} ## 3. Start it diff --git a/docs/site/quickstart/aspire.md b/docs/site/quickstart/aspire.md new file mode 100644 index 000000000..07c6d0997 --- /dev/null +++ b/docs/site/quickstart/aspire.md @@ -0,0 +1,85 @@ +--- +layout: layouts/base.vto +title: "Aspire quickstart" +templateEngine: [vento, md] +--- + +# Aspire quickstart + +A NetScript workspace is never one process — it is a Fresh app, oRPC services, plugin APIs, +background processors, a database, and a cache. Aspire is how we make that whole fleet start with +**one command**, wired together, with a real dashboard from the first run. This page is the +shortest path to seeing it; the [main Quickstart](/quickstart/) covers installing the CLI and the +scaffold in more detail. + +{{ comp callout { type: "important", title: "Alpha" } }} +NetScript is alpha software and the API is subject to change. Pin versions in real projects. +{{ /comp }} + +## What you get + +- **One workspace, one command up.** `aspire start` boots the database, the cache, every service, + every plugin API, and every background processor — in dependency order, no docker-compose to + babysit. +- **Multi-resource wiring, resolved for you.** Connection strings and neighbour endpoints are + computed and injected as environment variables before each process starts, so nothing has to + discover anything at runtime. +- **The Aspire dashboard.** Live resource list, per-process console logs, and distributed traces + in one place — `aspire start` prints its URL and a one-time login token. +- **A TypeScript AppHost — not .NET authoring.** The orchestrator entry point is a generated + TypeScript program at `aspire/apphost.mts`, running on an isolated Node runtime inside + `aspire/` so it never leaks into your Deno workspace. You write no C#. + +{{ comp callout { type: "note", title: "Prerequisites" } }} +<strong><a href="https://docs.deno.com">Deno</a> 2.x</strong> and the <code>netscript</code> CLI +(install steps in the <a href="/quickstart/">Quickstart</a>), the external +<strong><a href="https://learn.microsoft.com/dotnet/aspire/">.NET Aspire</a> CLI</strong>, and a +running <strong>Docker</strong> daemon — Aspire provisions Postgres and Redis as local containers. +{{ /comp }} + +## The commands + +Scaffold a workspace, then bring it up. The Aspire layer lives in its own `aspire/` folder; +restore its SDK modules once, then start: + +```bash +netscript init my-app --db postgres + +cd my-app/aspire +aspire restore # one-time: downloads the AppHost SDK modules +aspire start # boots Postgres + Redis + services, prints the dashboard URL +``` + +When boot settles, open the dashboard URL `aspire start` printed (conventionally +`https://localhost:18888`) and paste the login token. Every resource, its logs, and its traces are +one click away. + +{{ comp callout { type: "note", title: "Database commands come after" } }} +<code>netscript db init</code>, <code>db generate</code>, and <code>db seed</code> run from the +<strong>workspace root</strong> only once <code>aspire start</code> is up — they provision the +database <em>through</em> the running AppHost. With no Aspire up there is no Postgres for them to +reach. +{{ /comp }} + +## Prefer no orchestration? + +Aspire is the default, not a requirement. Scaffold with `--no-aspire` to skip the orchestration +layer entirely — no `aspire/` folder, no dashboard — and start the Fresh app directly: + +```bash +netscript init my-app --db postgres --no-aspire +deno task --cwd apps/dashboard dev +``` + +You take over infrastructure and wiring yourself: bring your own Postgres and cache, hand each +process its connection strings. When that trade is the right call — and what exactly you give +up — is covered in [Orchestration with Aspire]({{ "explain:aspire" |> xref |> url }}). + +## Where next + +- **Step-by-step recipe:** [Deploy locally with Aspire]({{ "howto:deploy-local-aspire" |> xref |> url }}) — + the full local flow, including the database sequence and first-run footguns. +- **Why it works this way:** [Orchestration with Aspire]({{ "explain:aspire" |> xref |> url }}) — + the AppHost, plugin contributions, and the resource graph. +- **Exact symbols and the port map:** [the Aspire reference]({{ "ref:aspire" |> xref |> url }}) and + the [CLI reference]({{ "cli:reference" |> xref |> url }}). diff --git a/docs/site/reference/ai/skills.md b/docs/site/reference/ai/skills.md index 6e0c34776..dbce3d327 100644 --- a/docs/site/reference/ai/skills.md +++ b/docs/site/reference/ai/skills.md @@ -26,7 +26,7 @@ of three ready-made skills that teach an agent the NetScript vocabulary. `netscript agent init` installs three first-party skills on the **Claude Code host path** (under `.claude/skills/`, alongside `.mcp.json` and the marked `AGENTS.md` section); the VS Code host path writes `.vscode/mcp.json` only, with no skills. They share one vocabulary with the -`netscript` CLI and the [NetScript MCP tools](/capabilities/agent-tooling/#tool-catalog), +`netscript` CLI and the [NetScript MCP tools](/ai/agent-tooling/#tool-catalog), so an agent routes to a workflow and then reaches for the matching CLI verb or MCP tool. | Skill | Role | @@ -37,7 +37,7 @@ so an agent routes to a workflow and then reaches for the matching CLI verb or M These are the skills named in the bundle manifest and installed as an atomic set; re-running `netscript agent init` is idempotent. See -[Agent tooling](/capabilities/agent-tooling/) for the CLI × skills × MCP combo. +[Agent tooling](/ai/agent-tooling/) for the CLI × skills × MCP combo. ```ts import { diff --git a/docs/site/reference/cli/commands.md b/docs/site/reference/cli/commands.md index 70831baef..cb6765db6 100644 --- a/docs/site/reference/cli/commands.md +++ b/docs/site/reference/cli/commands.md @@ -49,7 +49,7 @@ the [quickstart](/quickstart/); every flag is: ## `agent` — install and run agent tooling `netscript agent` installs and runs the shared CLI × skills × MCP tooling. See -[Agent tooling](/capabilities/agent-tooling/) for the mental model and the +[Agent tooling](/ai/agent-tooling/) for the mental model and the [`@netscript/mcp` reference](/reference/mcp/) for the server's tool surface. | Command | Description | @@ -134,7 +134,7 @@ the [CLI reference](/cli-reference/#plugins). The full group also carries: | `netscript plugin auth session revoke <id>` | Revoke an auth session by id. Flag: `--auth-url <url>`. | The backends selectable here are the same ones read at runtime by -`NETSCRIPT_AUTH_BACKEND` — see [add authentication](/how-to/add-authentication/). +`NETSCRIPT_AUTH_BACKEND` — see [add authentication](/identity-access/how-to/add-authentication/). ## `service` — extended verbs @@ -192,7 +192,7 @@ also lists, updates, and removes copied registry items: Because Fresh UI is copy-source, `ui:update` only touches registry files you have not modified — your edits are never overwritten. See -[customize Fresh UI](/how-to/customize-fresh-ui/). +[customize Fresh UI](/web-layer/how-to/customize-fresh-ui/). ## `deploy` — cloud and container targets diff --git a/docs/site/reference/kv/index.md b/docs/site/reference/kv/index.md index 9c7251443..609def5d7 100644 --- a/docs/site/reference/kv/index.md +++ b/docs/site/reference/kv/index.md @@ -139,7 +139,7 @@ A reusable contract harness for adapter authors. Re-exports the shared `KvStore` ## See it live -- **How-to:** [Queue / KV / cron](/how-to/queue-kv-cron/) — read, write, and watch a KV store with +- **How-to:** [Queue / KV / cron](/data-persistence/how-to/queue-kv-cron/) — read, write, and watch a KV store with these symbols in a running workspace. - **Concept:** [KV, queues & cron](/data-persistence/kv-queues-cron/) — the durable-by-default KV model and how it backs queues and cron. diff --git a/docs/site/reference/mcp/index.md b/docs/site/reference/mcp/index.md index 68e3d9f03..75af9c8a7 100644 --- a/docs/site/reference/mcp/index.md +++ b/docs/site/reference/mcp/index.md @@ -17,7 +17,7 @@ documentation, and trigger allowlisted CLI commands — over newline-delimited J no npm MCP SDK on the dependency graph. Most consumers never import this package: `netscript agent init` installs it as an MCP server and -`netscript agent mcp` runs it. See [Agent tooling](/capabilities/agent-tooling/) for the CLI × +`netscript agent mcp` runs it. See [Agent tooling](/ai/agent-tooling/) for the CLI × skills × MCP combo, and the package README for the mental model and recipes. Two entrypoints carry the surface: diff --git a/docs/site/reference/queue/index.md b/docs/site/reference/queue/index.md index 539f89388..7849137ca 100644 --- a/docs/site/reference/queue/index.md +++ b/docs/site/reference/queue/index.md @@ -125,9 +125,9 @@ documented in the sections above. ## See it live -- **How-to:** [Queue / KV / cron](/how-to/queue-kv-cron/) — enqueue, consume, and schedule against +- **How-to:** [Queue / KV / cron](/data-persistence/how-to/queue-kv-cron/) — enqueue, consume, and schedule against these symbols end to end. -- **How-to:** [Choose a queue provider](/how-to/choose-a-queue-provider/) — pick and pin a backend +- **How-to:** [Choose a queue provider](/data-persistence/how-to/choose-a-queue-provider/) — pick and pin a backend from the `QueueProvider` enum above. - **Concept:** [KV, queues & cron](/data-persistence/kv-queues-cron/) — why a queue is provider-agnostic and how Aspire wires the four backends. diff --git a/docs/site/how-to/add-a-service.md b/docs/site/services-sdk/how-to/add-a-service.md similarity index 98% rename from docs/site/how-to/add-a-service.md rename to docs/site/services-sdk/how-to/add-a-service.md index fab56484e..1794ea07e 100644 --- a/docs/site/how-to/add-a-service.md +++ b/docs/site/services-sdk/how-to/add-a-service.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Add a service templateEngine: [vento, md] -prev: { label: "Add a plugin", href: "/how-to/add-a-plugin/" } -next: { label: "Database & migration", href: "/how-to/database-migration/" } +order: 101 +oldUrl: /how-to/add-a-service/ --- # Add a service @@ -320,7 +320,7 @@ Wire persistence with the database recipe before you depend on durability. { title: "Tutorial: Build a service", body: "The guided, learning-oriented version — contract to typed client to a Fresh island, explained step by step.", href: "/tutorials/storefront/02-catalog-service/", icon: "→" }, { title: "Service API reference", body: "The full generated surface of defineService and createService — every option, builder method, and return type.", href: "/reference/service/", icon: "◆" }, { title: "Contracts, explained", body: "How an oRPC contract flows from service to typed client to UI without a codegen step.", href: "/explanation/contracts/", icon: "◎" }, - { title: "Database & migration", body: "Replace the seeded in-memory records with real Prisma-backed persistence — Postgres is the recommended engine, or mysql / mssql / sqlite via --db — init, generate, seed (Aspire up first).", href: "/how-to/database-migration/", icon: "▣" } + { title: "Database & migration", body: "Replace the seeded in-memory records with real Prisma-backed persistence — Postgres is the recommended engine, or mysql / mssql / sqlite via --db — init, generate, seed (Aspire up first).", href: "/data-persistence/how-to/database-migration/", icon: "▣" } ] }) }} Manage the service over its lifetime by editing its contract under `contracts/versions/` diff --git a/docs/site/how-to/discover-services.md b/docs/site/services-sdk/how-to/discover-services.md similarity index 98% rename from docs/site/how-to/discover-services.md rename to docs/site/services-sdk/how-to/discover-services.md index 7eebe4581..21a309d3e 100644 --- a/docs/site/how-to/discover-services.md +++ b/docs/site/services-sdk/how-to/discover-services.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Discover services templateEngine: [vento, md] -prev: { label: "How-to guides", href: "/how-to/" } -next: { label: "Expose OpenAPI & Scalar", href: "/how-to/expose-openapi-scalar/" } +order: 102 +oldUrl: /how-to/discover-services/ --- # Discover services @@ -275,7 +275,7 @@ var) or the client cannot find the service.</li> ## See also {{ comp.featureGrid({ items: [ - { title: "Do — Add a service", body: "Create the callee first: contract → defineService → an oRPC service answering on its own port.", href: "/how-to/add-a-service/", icon: "◆" }, + { title: "Do — Add a service", body: "Create the callee first: contract → defineService → an oRPC service answering on its own port.", href: "/services-sdk/how-to/add-a-service/", icon: "◆" }, { title: "Capability — Services & contracts", body: "Why the shared contract object is the single source of truth, and how a typed client cannot drift from the service.", href: "/capabilities/services/", icon: "▣" }, { title: "Look up — @netscript/sdk", body: "The full SDK surface: createServiceClient, defineServices, the discovery readers, query factories, and TanStack utils.", href: "/reference/sdk/", icon: "≡" }, { title: "Understand — Orchestration with Aspire", body: "How Aspire wires resources, resolves endpoints, and injects the services__ env vars discovery reads.", href: "/explanation/aspire/", icon: "◎" } @@ -285,4 +285,4 @@ For the concepts behind the typed-client surface see {{ comp.xref({ key: "cap:sd the sibling recipe that builds the callee is {{ comp.xref({ key: "howto:add-a-service" }) }}; and the orchestration that injects the URLs is explained in {{ comp.xref({ key: "explain:aspire" }) }}. -{{ comp.nextPrev({ prev: { label: "How-to guides", href: "/how-to/" }, next: { label: "Expose OpenAPI & Scalar", href: "/how-to/expose-openapi-scalar/" } }) }} +{{ comp.nextPrev({ prev: { label: "How-to guides", href: "/how-to/" }, next: { label: "Expose OpenAPI & Scalar", href: "/services-sdk/how-to/expose-openapi-scalar/" } }) }} diff --git a/docs/site/how-to/expose-openapi-scalar.md b/docs/site/services-sdk/how-to/expose-openapi-scalar.md similarity index 95% rename from docs/site/how-to/expose-openapi-scalar.md rename to docs/site/services-sdk/how-to/expose-openapi-scalar.md index a4c2b2db0..b9f2d90fc 100644 --- a/docs/site/how-to/expose-openapi-scalar.md +++ b/docs/site/services-sdk/how-to/expose-openapi-scalar.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Expose OpenAPI & Scalar templateEngine: [vento, md] -prev: { label: "Discover services", href: "/how-to/discover-services/" } -next: { label: "Use a second database", href: "/how-to/use-a-second-database/" } +order: 103 +oldUrl: /how-to/expose-openapi-scalar/ --- # Expose OpenAPI & Scalar @@ -181,13 +181,13 @@ no zod input/output yields an empty operation schema in the spec. Add schemas (a { title: "Do — Discover services", body: "Recipe: enumerate the services in a workspace and the endpoints each one exposes.", - href: "/how-to/discover-services/", + href: "/services-sdk/how-to/discover-services/", icon: "◆" }, { title: "Do — Graceful shutdown", body: "Recipe: drain in-flight requests and run teardown hooks before the service process exits.", - href: "/how-to/graceful-shutdown/", + href: "/orchestration-runtime/how-to/graceful-shutdown/", icon: "◆" }, { @@ -198,4 +198,4 @@ no zod input/output yields an empty operation schema in the spec. Add schemas (a } ] }) }} -{{ comp.nextPrev({ prev: { label: "Discover services", href: "/how-to/discover-services/" }, next: { label: "Use a second database", href: "/how-to/use-a-second-database/" } }) }} +{{ comp.nextPrev({ prev: { label: "Discover services", href: "/services-sdk/how-to/discover-services/" }, next: { label: "Use a second database", href: "/data-persistence/how-to/use-a-second-database/" } }) }} diff --git a/docs/site/services-sdk/how-to/index.md b/docs/site/services-sdk/how-to/index.md new file mode 100644 index 000000000..fc5f22f4f --- /dev/null +++ b/docs/site/services-sdk/how-to/index.md @@ -0,0 +1,12 @@ +--- +layout: layouts/base.vto +title: Recipes +templateEngine: [vento, md] +order: 100 +--- + +# Services & SDK — recipes + +Task-oriented recipes for this area. Each one solves a single concrete problem and assumes you know +the basics from the guides above it in the sidebar. The full cross-area catalog lives at +[All how-to recipes]({{ "howto:index" |> xref |> url }}). diff --git a/docs/site/services-sdk/index.md b/docs/site/services-sdk/index.md index d35c0d1e0..08c51e1b9 100644 --- a/docs/site/services-sdk/index.md +++ b/docs/site/services-sdk/index.md @@ -37,8 +37,17 @@ via ALPN automatically. See <a href="/capabilities/services/#tls-http-2-opt-in"> {{ comp.cardsGrid({ columns: 3, cards: [ { eyebrow: "Overview & Concepts", title: "Contracts to service to client", body: "The shared model for service handlers, OpenAPI, RPC, and typed clients.", href: "/explanation/contracts/", icon: "O" }, { eyebrow: "Quickstart", title: "Catalog service", body: "Create the first service in the Storefront tutorial.", href: "/tutorials/storefront/02-catalog-service/", icon: "Q" }, - { eyebrow: "How-To", title: "Add a service", body: "Add a new service to a workspace.", href: "/how-to/add-a-service/", icon: "H" }, - { eyebrow: "How-To", title: "Discover services", body: "Resolve service URLs and clients from the generated workspace.", href: "/how-to/discover-services/", icon: "H" }, - { eyebrow: "How-To", title: "OpenAPI and Scalar", body: "Expose the generated OpenAPI document and Scalar UI.", href: "/how-to/expose-openapi-scalar/", icon: "H" }, + { eyebrow: "How-To", title: "Add a service", body: "Add a new service to a workspace.", href: "/services-sdk/how-to/add-a-service/", icon: "H" }, + { eyebrow: "How-To", title: "Discover services", body: "Resolve service URLs and clients from the generated workspace.", href: "/services-sdk/how-to/discover-services/", icon: "H" }, + { eyebrow: "How-To", title: "OpenAPI and Scalar", body: "Expose the generated OpenAPI document and Scalar UI.", href: "/services-sdk/how-to/expose-openapi-scalar/", icon: "H" }, { eyebrow: "API Reference", title: "service and sdk", body: "Generated service, SDK, and contract package symbols.", href: "/reference/service/", icon: "R" } ] }) }} + +## Learn, do, look up + +{{ comp.cardsGrid({ columns: 4, cards: [ + { eyebrow: "Learn", title: "Storefront tutorial", body: "Define contracts and services, then consume them through the typed SDK.", href: resolveXref("tut:storefront").href }, + { eyebrow: "Do", title: "Recipes", body: "Task-oriented recipes for this area, one problem each.", href: "/services-sdk/how-to/" }, + { eyebrow: "Look up", title: "`@netscript/service` reference", body: "Generated API reference. Related units: `sdk`, `contracts`.", href: resolveXref("ref:service").href }, + { eyebrow: "Understand", title: "Contracts & type flow", body: "The design rationale behind this pillar.", href: resolveXref("explain:contracts").href }, +] }) }} diff --git a/docs/site/services-sdk/sdk.md b/docs/site/services-sdk/sdk.md index 780ccf1db..c48f23ee1 100644 --- a/docs/site/services-sdk/sdk.md +++ b/docs/site/services-sdk/sdk.md @@ -4,6 +4,7 @@ title: Typed SDK & client templateEngine: [vento, md] prev: { label: "Fresh meta-framework", href: "/web-layer/" } next: { label: "Polyglot tasks", href: "/background-processing/polyglot-tasks/" } +order: 2 --- # Typed SDK & client @@ -81,7 +82,7 @@ up on the client already typed. The mechanics of each layer are below; the type- { title: "Do — Discover a service", body: "Task recipe: resolve a service URL from Aspire env and stand up a typed client for it.", - href: "/how-to/discover-services/", + href: "/services-sdk/how-to/discover-services/", icon: "◆" } ] }) }} diff --git a/docs/site/services-sdk/services.md b/docs/site/services-sdk/services.md index a6d968fbb..2e9db027f 100644 --- a/docs/site/services-sdk/services.md +++ b/docs/site/services-sdk/services.md @@ -4,6 +4,7 @@ title: Services & contracts templateEngine: [vento, md] prev: { label: "Capabilities", href: "/capabilities/" } next: { label: "Background jobs", href: "/background-processing/workers/" } +order: 1 --- # Services & contracts @@ -85,13 +86,13 @@ type story is in [Contracts](/explanation/contracts/). { title: "Do — Expose OpenAPI & Scalar", body: "Recipe: turn on the generated OpenAPI spec and the Scalar docs UI for an existing service.", - href: "/how-to/expose-openapi-scalar/", + href: "/services-sdk/how-to/expose-openapi-scalar/", icon: "◆" }, { title: "Do — Graceful shutdown", body: "Recipe: drain in-flight requests and run teardown hooks on SIGINT/SIGTERM before the process exits.", - href: "/how-to/graceful-shutdown/", + href: "/orchestration-runtime/how-to/graceful-shutdown/", icon: "◆" } ] }) }} diff --git a/docs/site/styles/docs.css b/docs/site/styles/docs.css index bacedbadf..bcd237638 100644 --- a/docs/site/styles/docs.css +++ b/docs/site/styles/docs.css @@ -1703,3 +1703,68 @@ a.ns-cards-grid__card:focus-visible { color: var(--ns-fg); border-color: var(--ns-border-hover, var(--ns-accent, #5b8def)); } + +/* ── docs-v5 multilevel nav tree (nav plugin, _includes/nav/menu-item.vto) ── */ + +.ns-nav-lane { + display: flex; + flex-direction: column; + gap: 2px; + margin: 0; +} + +.ns-nav-lane__sub { + font-family: var(--ns-font-sans); + font-size: 0.6875rem; + font-weight: 400; + text-transform: none; + letter-spacing: normal; + color: var(--ns-muted-fg); + opacity: 0.8; +} + +.ns-nav-branch__summary { + display: flex; + align-items: center; + list-style: none; + cursor: pointer; +} + +.ns-nav-branch__summary::-webkit-details-marker { + display: none; +} + +.ns-nav-branch__summary .ns-dashboard__nav-item { + flex: 1; + min-width: 0; +} + +.ns-nav-branch__chevron { + display: flex; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + margin-right: var(--ns-space-1-5); + border-radius: var(--ns-radius-sm, 4px); + color: var(--ns-muted-fg); + transition: transform 140ms ease, background-color 140ms ease; +} + +.ns-nav-branch__chevron:hover { + background: color-mix(in srgb, var(--ns-surface-raised) 60%, transparent); + color: var(--ns-fg); +} + +.ns-nav-branch[open] > .ns-nav-branch__summary .ns-nav-branch__chevron { + transform: rotate(90deg); +} + +.ns-nav-branch__children { + display: flex; + flex-direction: column; + gap: 2px; + margin-left: var(--ns-space-3); + padding-left: var(--ns-space-2); + border-left: 1px solid var(--ns-border); +} diff --git a/docs/site/tutorials/chat/03-chat-ui.md b/docs/site/tutorials/chat/03-chat-ui.md index 83a0402fb..94a75c1cc 100644 --- a/docs/site/tutorials/chat/03-chat-ui.md +++ b/docs/site/tutorials/chat/03-chat-ui.md @@ -37,7 +37,7 @@ netscript ui:add ai This copies component files into `apps/dashboard/components/ui/`, the `chat-render` parser into `apps/dashboard/lib/chat/parse-blocks.ts`, and their CSS into `apps/dashboard/assets/ui/`, then wires the styles and merges any required imports. After the copy, that code is yours to -edit — see [Customize Fresh UI](/how-to/customize-fresh-ui/) for the ownership model. +edit — see [Customize Fresh UI](/web-layer/how-to/customize-fresh-ui/) for the ownership model. {{ comp.apiTable({ caption: "The ai collection — the pieces this track uses", @@ -67,7 +67,7 @@ const seed = await resolveChatSnapshot({ target: { sessionId } }); ``` Wire `seed` into your page with the scaffold's page builder the same way the dashboard -passes data to a view — see [Customize Fresh UI](/how-to/customize-fresh-ui/) for the +passes data to a view — see [Customize Fresh UI](/web-layer/how-to/customize-fresh-ui/) for the `definePage` pattern. The important part is that the island receives `seed` as a prop. {{ comp callout { type: "note", title: "The one-projection law, in one sentence" } }} diff --git a/docs/site/tutorials/chat/06-live-streaming.md b/docs/site/tutorials/chat/06-live-streaming.md index 8a53a7351..b51b08fcc 100644 --- a/docs/site/tutorials/chat/06-live-streaming.md +++ b/docs/site/tutorials/chat/06-live-streaming.md @@ -180,11 +180,11 @@ That progression — **one agreed message shape → durable delivery → live st NetScript real-time surface is built from; you just built it in its AI-chat form. Where to go next: - Reach for the durable-chat seams directly with the - [Build a durable chat](/how-to/build-a-durable-chat/) recipe. + [Build a durable chat](/ai/how-to/build-a-durable-chat/) recipe. - Restyle any chat or widget component — you own the copied source: - [Customize Fresh UI](/how-to/customize-fresh-ui/). + [Customize Fresh UI](/web-layer/how-to/customize-fresh-ui/). - Model live list/board/table data (the other real-time plane) with - [Publish a durable stream](/how-to/publish-a-durable-stream/). + [Publish a durable stream](/durable-workflows/how-to/publish-a-durable-stream/). - Compose the provider-neutral engine — model registry, agent loop, tool registry, MCP transports: [AI engine](/ai/engine/). - Look up exact signatures in the [fresh reference](/reference/fresh/). diff --git a/docs/site/tutorials/chat/index.md b/docs/site/tutorials/chat/index.md index 966baa9ad..2c14be20f 100644 --- a/docs/site/tutorials/chat/index.md +++ b/docs/site/tutorials/chat/index.md @@ -4,6 +4,7 @@ title: AI Chat templateEngine: [vento, md] prev: { label: "Tutorials", href: "/tutorials/" } next: { label: "1 · Scaffold", href: "/tutorials/chat/01-scaffold/" } +order: 2 --- # AI Chat @@ -114,3 +115,5 @@ its transcript, how a tool call (yours or a remote MCP server's) lands as a dura and how MCP `ui://` widgets render themed and sandboxed inside the chat. {{ comp.nextPrev({ prev: { label: "Tutorials", href: "/tutorials/" }, next: { label: "1 · Scaffold", href: "/tutorials/chat/01-scaffold/" } }) }} + +After this track, keep building in **Build › [AI & Agents](/ai/)** — the guides and recipes there pick up where these chapters stop. diff --git a/docs/site/tutorials/eis-chat/01-scaffold.md b/docs/site/tutorials/eis-chat/01-scaffold.md index 375824a27..d5c37602c 100644 --- a/docs/site/tutorials/eis-chat/01-scaffold.md +++ b/docs/site/tutorials/eis-chat/01-scaffold.md @@ -1,4 +1,5 @@ --- layout: layouts/redirect.vto redirectTo: /tutorials/chat/01-scaffold/ +nav_hide: true --- diff --git a/docs/site/tutorials/eis-chat/02-message-contract.md b/docs/site/tutorials/eis-chat/02-message-contract.md index db1120645..463408997 100644 --- a/docs/site/tutorials/eis-chat/02-message-contract.md +++ b/docs/site/tutorials/eis-chat/02-message-contract.md @@ -1,4 +1,5 @@ --- layout: layouts/redirect.vto redirectTo: /tutorials/chat/02-durable-chat-route/ +nav_hide: true --- diff --git a/docs/site/tutorials/eis-chat/03-deliver-worker.md b/docs/site/tutorials/eis-chat/03-deliver-worker.md index db1120645..463408997 100644 --- a/docs/site/tutorials/eis-chat/03-deliver-worker.md +++ b/docs/site/tutorials/eis-chat/03-deliver-worker.md @@ -1,4 +1,5 @@ --- layout: layouts/redirect.vto redirectTo: /tutorials/chat/02-durable-chat-route/ +nav_hide: true --- diff --git a/docs/site/tutorials/eis-chat/04-live-stream.md b/docs/site/tutorials/eis-chat/04-live-stream.md index bbfb6ba19..7a9b98233 100644 --- a/docs/site/tutorials/eis-chat/04-live-stream.md +++ b/docs/site/tutorials/eis-chat/04-live-stream.md @@ -1,4 +1,5 @@ --- layout: layouts/redirect.vto redirectTo: /tutorials/chat/06-live-streaming/ +nav_hide: true --- diff --git a/docs/site/tutorials/eis-chat/index.md b/docs/site/tutorials/eis-chat/index.md index 8068d7c65..56898979a 100644 --- a/docs/site/tutorials/eis-chat/index.md +++ b/docs/site/tutorials/eis-chat/index.md @@ -1,4 +1,5 @@ --- layout: layouts/redirect.vto redirectTo: /tutorials/chat/ +nav_hide: true --- diff --git a/docs/site/tutorials/erp-sync/01-scaffold.md b/docs/site/tutorials/erp-sync/01-scaffold.md index 5641b2c7e..2605b4213 100644 --- a/docs/site/tutorials/erp-sync/01-scaffold.md +++ b/docs/site/tutorials/erp-sync/01-scaffold.md @@ -168,7 +168,7 @@ your control plane for the rest of the track. The Postgres container only exists while <code>aspire start</code> is up. So <code>netscript db init</code>, <code>db generate</code>, and <code>db seed</code> must run <strong>after</strong> Aspire has started — never before. There is more on the database sequence in -<a href="/how-to/deploy-local-aspire/">Deploy locally with Aspire</a>. +<a href="/orchestration-runtime/how-to/deploy-local-aspire/">Deploy locally with Aspire</a>. {{ /comp }} ## Verify your progress diff --git a/docs/site/tutorials/erp-sync/03-polyglot-transform.md b/docs/site/tutorials/erp-sync/03-polyglot-transform.md index 9a83b680d..d4d12dff7 100644 --- a/docs/site/tutorials/erp-sync/03-polyglot-transform.md +++ b/docs/site/tutorials/erp-sync/03-polyglot-transform.md @@ -147,7 +147,7 @@ Calling <code>.build()</code> on a <code>deno</code> task <em>without</em> Always pass an explicit, least-privilege set. The framework also ships named <code>permissions</code> presets (<code>minimal</code>, <code>readOnly</code>, <code>network</code>, …) so you do not hand-roll the object for common cases — see -<a href="/how-to/tune-worker-runtime/">Tune the worker runtime</a>. +<a href="/background-processing/how-to/tune-worker-runtime/">Tune the worker runtime</a>. {{ /comp }} ## Step 3 — Run it through the executor @@ -261,7 +261,7 @@ A missing interpreter surfaces as a <em>failed task</em>, not a thrown error: ex <code>127</code> is reported as <em>command not found</em> and <code>126</code> as <em>command not executable</em>. Confirm <code>python3 --version</code> / <code>pwsh --version</code> / <code>dotnet --version</code> on the actual worker host before you ship a task that depends on it. -When yours is ready, <a href="/how-to/run-a-polyglot-task/">Run a polyglot task</a> walks the +When yours is ready, <a href="/background-processing/how-to/run-a-polyglot-task/">Run a polyglot task</a> walks the Python and shell variants end to end, including interpreter pinning. {{ /comp }} @@ -312,11 +312,11 @@ on a schedule. ## Where to go deeper -- **Run the Python/shell variants for real** → [Run a polyglot task](/how-to/run-a-polyglot-task/) +- **Run the Python/shell variants for real** → [Run a polyglot task](/background-processing/how-to/run-a-polyglot-task/) — the hands-on recipe: define, write the script, pin the interpreter, read the result. - **The capability** → [Polyglot tasks](/background-processing/polyglot-tasks/) — the WHY: what a task is, the subprocess seam, the full `TaskResult` shape. -- **Tune the runtime** → [Tune the worker runtime](/how-to/tune-worker-runtime/) — concurrency, +- **Tune the runtime** → [Tune the worker runtime](/background-processing/how-to/tune-worker-runtime/) — concurrency, the permission presets, and the per-task timeout/retry knobs. {{ comp.nextPrev({ prev: { label: "2 · Import job", href: "/tutorials/erp-sync/02-import-job/" }, next: { label: "4 · Queue & cron", href: "/tutorials/erp-sync/04-queue-and-cron/" } }) }} diff --git a/docs/site/tutorials/erp-sync/04-queue-and-cron.md b/docs/site/tutorials/erp-sync/04-queue-and-cron.md index 572df1ad1..95b6dee1f 100644 --- a/docs/site/tutorials/erp-sync/04-queue-and-cron.md +++ b/docs/site/tutorials/erp-sync/04-queue-and-cron.md @@ -67,7 +67,7 @@ Auto-discovery probes <strong>RabbitMQ → Redis → Deno KV only</strong>. It w fall through to PostgreSQL, even when a Postgres connection is present. The SQL-durable queue is opt-in — you must pass <code>provider: QueueProvider.Postgres</code> or you will quietly land on the Deno KV adapter. The full decision guide is in -<a href="/how-to/choose-a-queue-provider/">Choose a queue provider</a>. +<a href="/data-persistence/how-to/choose-a-queue-provider/">Choose a queue provider</a>. {{ /comp }} ## Step 2 — Size worker concurrency in config @@ -105,7 +105,7 @@ export const workers = defineWorkers({ For per-topic control — a hot `imports` topic at concurrency 10 while a heavy `reports` topic stays at 1 — use a `WorkerGroup` with its own `scaling: { mode, concurrency }`. The full per-topic and -runner-mode knobs are in [Tune the worker runtime](/how-to/tune-worker-runtime/). +runner-mode knobs are in [Tune the worker runtime](/background-processing/how-to/tune-worker-runtime/). {{ comp callout { type: "warning", title: "Set scaling.concurrency in config — the Aspire env var is silently ignored" } }} There are <strong>two</strong> concurrency env names in play and they are <em>not</em> the same @@ -229,7 +229,7 @@ schedule, not a file event, enqueued it. Set the cron back to `0 6 * * *` when y Raising <code>concurrency</code> means more jobs run at once, and every queue backend can redeliver a message on retry. An idempotent import — keyed on the file's content hash or name — can re-run without double-importing. Pair retries with an <code>idempotencyKey</code> on enqueue so a redelivery after a -restart does not duplicate rows. See <a href="/how-to/choose-a-queue-provider/">Choose a queue +restart does not duplicate rows. See <a href="/data-persistence/how-to/choose-a-queue-provider/">Choose a queue provider</a> for each backend's delivery semantics. {{ /comp }} diff --git a/docs/site/tutorials/erp-sync/05-deploy.md b/docs/site/tutorials/erp-sync/05-deploy.md index 07579c833..aa99f4e1c 100644 --- a/docs/site/tutorials/erp-sync/05-deploy.md +++ b/docs/site/tutorials/erp-sync/05-deploy.md @@ -41,8 +41,8 @@ stitch a file drop to its job execution. observable, correctly-wired stack on <strong>one machine</strong>. The Postgres and Redis it starts are throwaway Docker containers for dev convenience — <strong>not</strong> your production database or cache. Shipping to a remote target (managed infrastructure, your own process lifecycle) is the -<a href="/how-to/deploy/">Deploy</a> recipe; this chapter is the local companion. The full local -walkthrough lives in <a href="/how-to/deploy-local-aspire/">Deploy locally with Aspire</a>. +<a href="/orchestration-runtime/how-to/deploy/">Deploy</a> recipe; this chapter is the local companion. The full local +walkthrough lives in <a href="/orchestration-runtime/how-to/deploy-local-aspire/">Deploy locally with Aspire</a>. {{ /comp }} ## Before you begin @@ -212,12 +212,12 @@ and you know exactly where the local story ends and a production deployment begi You have finished the ERP Sync track. From here, branch into task-oriented and reference docs: -- **Ship it remotely** → [Deploy](/how-to/deploy/) — the production companion to this local run: +- **Ship it remotely** → [Deploy](/orchestration-runtime/how-to/deploy/) — the production companion to this local run: deployable units, managed backing services, and the `--no-aspire` path. -- **Take the transform polyglot** → [Run a polyglot task](/how-to/run-a-polyglot-task/) — swap +- **Take the transform polyglot** → [Run a polyglot task](/background-processing/how-to/run-a-polyglot-task/) — swap Chapter 3's Deno transform for a Python or shell step on your own host. -- **Tune throughput** → [Choose a queue provider](/how-to/choose-a-queue-provider/) and - [Tune the worker runtime](/how-to/tune-worker-runtime/). +- **Tune throughput** → [Choose a queue provider](/data-persistence/how-to/choose-a-queue-provider/) and + [Tune the worker runtime](/background-processing/how-to/tune-worker-runtime/). - **Understand the orchestrator** → [Orchestration with Aspire](/explanation/aspire/) and the full [How-to guides](/how-to/). diff --git a/docs/site/tutorials/erp-sync/index.md b/docs/site/tutorials/erp-sync/index.md index 29b79aa81..4a5835b22 100644 --- a/docs/site/tutorials/erp-sync/index.md +++ b/docs/site/tutorials/erp-sync/index.md @@ -4,6 +4,7 @@ title: ERP Sync templateEngine: [vento, md] prev: { label: "Tutorials", href: "/tutorials/" } next: { label: "1 · Scaffold", href: "/tutorials/erp-sync/01-scaffold/" } +order: 5 --- # ERP Sync @@ -122,3 +123,5 @@ instead of resetting between chapters. Follow them in order. {{ /comp }} {{ comp.nextPrev({ prev: { label: "Tutorials", href: "/tutorials/" }, next: { label: "1 · Scaffold", href: "/tutorials/erp-sync/01-scaffold/" } }) }} + +After this track, keep building in **Build › [Background jobs](/background-processing/)** — the guides and recipes there pick up where these chapters stop. diff --git a/docs/site/tutorials/live-dashboard/02-contract-to-service.md b/docs/site/tutorials/live-dashboard/02-contract-to-service.md index b7f95f9d0..4f45746fd 100644 --- a/docs/site/tutorials/live-dashboard/02-contract-to-service.md +++ b/docs/site/tutorials/live-dashboard/02-contract-to-service.md @@ -208,7 +208,7 @@ netscript db seed # seed development data (orders, users, products These talk to the Postgres container Aspire provisioned. Run them with no Aspire up and they fail — there is no database to reach. The full sequence is in -[Database & migration](/how-to/database-migration/). +[Database & migration](/data-persistence/how-to/database-migration/). ## Verify your progress diff --git a/docs/site/tutorials/live-dashboard/03-sdk-cache-first-query.md b/docs/site/tutorials/live-dashboard/03-sdk-cache-first-query.md index ebb4f7e2f..f381f5ea0 100644 --- a/docs/site/tutorials/live-dashboard/03-sdk-cache-first-query.md +++ b/docs/site/tutorials/live-dashboard/03-sdk-cache-first-query.md @@ -66,7 +66,7 @@ export const ordersClient = createServiceClient<typeof ordersContract>({ reading a field the output does not have, is a compile error. {{ comp callout { type: "note", title: "How serviceName resolves to a URL" } }} -You never hardcode <code>http://localhost:3002</code>. <code>serviceName: 'orders'</code> is resolved at call time from an Aspire-injected env var — server-side <code>services__orders__http__0</code>, and the browser mirror <code>VITE_services__orders__http__0</code> — via <code>getServiceUrl</code> in <code>@netscript/sdk/discovery</code>. Aspire sets those when you list <code>orders</code> as a reference; the client just reads them. Full mechanics in <a href="/how-to/discover-services/">Discover services</a>. +You never hardcode <code>http://localhost:3002</code>. <code>serviceName: 'orders'</code> is resolved at call time from an Aspire-injected env var — server-side <code>services__orders__http__0</code>, and the browser mirror <code>VITE_services__orders__http__0</code> — via <code>getServiceUrl</code> in <code>@netscript/sdk/discovery</code>. Aspire sets those when you list <code>orders</code> as a reference; the client just reads them. Full mechanics in <a href="/services-sdk/how-to/discover-services/">Discover services</a>. {{ /comp }} ## Step 2 — Add the cache-first query factory diff --git a/docs/site/tutorials/live-dashboard/06-deploy.md b/docs/site/tutorials/live-dashboard/06-deploy.md index 6b4854848..815552572 100644 --- a/docs/site/tutorials/live-dashboard/06-deploy.md +++ b/docs/site/tutorials/live-dashboard/06-deploy.md @@ -148,7 +148,7 @@ Both should return healthy responses, and the dashboard at `:18888` should list - [ ] Creating an order is visible in the dashboard traces and the live monitor. {{ comp callout { type: "warning", title: "Aspire is the LOCAL story — not a production deployer" } }} -<code>aspire start</code> exists to make one command produce a complete, observable, correctly-wired stack on <strong>one machine</strong>. The Postgres and Redis it starts are throwaway Docker containers for dev convenience — <strong>not</strong> your production database or cache. For a remote target you point processes at managed infrastructure and let your platform own lifecycle; that is the <a href="/how-to/deploy/">Deploy</a> recipe, and the <code>--no-aspire</code> path in <a href="/explanation/aspire/">Orchestration with Aspire</a>. +<code>aspire start</code> exists to make one command produce a complete, observable, correctly-wired stack on <strong>one machine</strong>. The Postgres and Redis it starts are throwaway Docker containers for dev convenience — <strong>not</strong> your production database or cache. For a remote target you point processes at managed infrastructure and let your platform own lifecycle; that is the <a href="/orchestration-runtime/how-to/deploy/">Deploy</a> recipe, and the <code>--no-aspire</code> path in <a href="/explanation/aspire/">Orchestration with Aspire</a>. {{ /comp }} {{ comp callout { type: "warning", title: "Footguns when aspire start will not boot" } }} @@ -172,7 +172,7 @@ NetScript spine. - **Task recipes** → the [how-to guides](/how-to/) cover what the tutorials don't: adding plugins, database migrations, queue backends, and production pitfalls. -- **Ship it remotely** → [Deploy](/how-to/deploy/) is the production companion to local Aspire. +- **Ship it remotely** → [Deploy](/orchestration-runtime/how-to/deploy/) is the production companion to local Aspire. - **Go deeper** → [Orchestration with Aspire](/explanation/aspire/) explains the AppHost, plugin contributions, and two-pass reference resolution; {{ comp.xref({ key: "cap:streams" }) }} and {{ comp.xref({ key: "cap:fresh-framework", text: "the Fresh meta-framework" }) }} back chapters 4 diff --git a/docs/site/tutorials/live-dashboard/index.md b/docs/site/tutorials/live-dashboard/index.md index f819d3cb8..1732bc010 100644 --- a/docs/site/tutorials/live-dashboard/index.md +++ b/docs/site/tutorials/live-dashboard/index.md @@ -4,6 +4,7 @@ title: Live Dashboard templateEngine: [vento, md] prev: { label: "Tutorials", href: "/tutorials/" } next: { label: "1 · Scaffold", href: "/tutorials/live-dashboard/01-scaffold/" } +order: 1 --- # Live Dashboard @@ -111,3 +112,5 @@ contract to durable stream. Every hop is typed off the same contract, so the row the row the service wrote. {{ comp.nextPrev({ prev: { label: "Tutorials", href: "/tutorials/" }, next: { label: "1 · Scaffold", href: "/tutorials/live-dashboard/01-scaffold/" } }) }} + +After this track, keep building in **Build › [Web Layer](/web-layer/)** — the guides and recipes there pick up where these chapters stop. diff --git a/docs/site/tutorials/storefront/03-cart-contracts.md b/docs/site/tutorials/storefront/03-cart-contracts.md index 14525c50e..ea16bf540 100644 --- a/docs/site/tutorials/storefront/03-cart-contracts.md +++ b/docs/site/tutorials/storefront/03-cart-contracts.md @@ -57,7 +57,7 @@ it. ## Step 1 — Scaffold the cart contract Let the CLI lay down the contract file and wire it into the version aggregate for you, exactly as the -[Add a service](/how-to/add-a-service/) recipe does. From the workspace root: +[Add a service](/services-sdk/how-to/add-a-service/) recipe does. From the workspace root: ```sh netscript contract add cart diff --git a/docs/site/tutorials/storefront/06-storefront-ui.md b/docs/site/tutorials/storefront/06-storefront-ui.md index 663eb4151..2103f136d 100644 --- a/docs/site/tutorials/storefront/06-storefront-ui.md +++ b/docs/site/tutorials/storefront/06-storefront-ui.md @@ -156,7 +156,7 @@ You never write <code>http://localhost:3001</code>. <code>serviceName: 'products call time from an Aspire-injected env var — server-side <code>services__products__http__0</code> and its browser mirror — so the same client works in a page loader and in a hydrated island. Aspire sets those when the app lists <code>products</code> as a reference; the client just reads them. Full -mechanics in <a href="/how-to/discover-services/">Discover services</a>. +mechanics in <a href="/services-sdk/how-to/discover-services/">Discover services</a>. {{ /comp }} ## Step 3 — Read and mutate in the island diff --git a/docs/site/tutorials/storefront/07-deploy.md b/docs/site/tutorials/storefront/07-deploy.md index d92b71ccb..17452a9ad 100644 --- a/docs/site/tutorials/storefront/07-deploy.md +++ b/docs/site/tutorials/storefront/07-deploy.md @@ -39,7 +39,7 @@ infrastructure, services, plugin APIs, and background processors — then gives whole thing. You will read the live port map from the dashboard and confirm every resource is healthy. {{ comp callout { type: "warning", title: "This is the LOCAL story — not a production deployer" } }} -<code>aspire start</code> exists to make one command produce a complete, correctly-wired stack on <strong>one machine</strong>. The Postgres and Redis it starts are throwaway Docker containers for dev convenience — <strong>not</strong> your production database or cache. NetScript does not ship a cloud deployer for your app; for a remote target you point processes at managed infrastructure yourself. This chapter teaches the local topology. See the <a href="/how-to/deploy/">Deploy</a> and <a href="/how-to/deploy-local-aspire/">Deploy locally with Aspire</a> how-to guides for the production-vs-local split. +<code>aspire start</code> exists to make one command produce a complete, correctly-wired stack on <strong>one machine</strong>. The Postgres and Redis it starts are throwaway Docker containers for dev convenience — <strong>not</strong> your production database or cache. NetScript does not ship a cloud deployer for your app; for a remote target you point processes at managed infrastructure yourself. This chapter teaches the local topology. See the <a href="/orchestration-runtime/how-to/deploy/">Deploy</a> and <a href="/orchestration-runtime/how-to/deploy-local-aspire/">Deploy locally with Aspire</a> how-to guides for the production-vs-local split. {{ /comp }} ## Before you begin @@ -185,9 +185,9 @@ where it counts, and verified at its edges. ## Where to go next -- **Ship it somewhere real** → the [Deploy](/how-to/deploy/) how-to (production targets) and - [Deploy locally with Aspire](/how-to/deploy-local-aspire/) (the full local recipe with every flag). -- **Add observability** → [Add OpenTelemetry](/how-to/add-opentelemetry/) and the +- **Ship it somewhere real** → the [Deploy](/orchestration-runtime/how-to/deploy/) how-to (production targets) and + [Deploy locally with Aspire](/orchestration-runtime/how-to/deploy-local-aspire/) (the full local recipe with every flag). +- **Add observability** → [Add OpenTelemetry](/observability/how-to/add-opentelemetry/) and the [Observability explanation](/explanation/observability/). - **Go deeper on the ideas** → [Durability model](/explanation/durability-model/), [Contracts & type flow](/explanation/contracts/), and [The plugin system](/explanation/plugin-system/). diff --git a/docs/site/tutorials/storefront/index.md b/docs/site/tutorials/storefront/index.md index 98895c2d9..81d1c3ff4 100644 --- a/docs/site/tutorials/storefront/index.md +++ b/docs/site/tutorials/storefront/index.md @@ -4,6 +4,7 @@ title: Build a storefront backend templateEngine: [vento, md] prev: { label: "Tutorials", href: "/tutorials/" } next: { label: "1 · Scaffold", href: "/tutorials/storefront/01-scaffold/" } +order: 4 --- # Build a storefront backend @@ -121,3 +122,5 @@ This is a tutorial track: state compounds. The <code>my-shop/</code> workspace y ## Start the build {{ comp.nextPrev({ prev: { label: "Tutorials", href: "/tutorials/" }, next: { label: "1 · Scaffold", href: "/tutorials/storefront/01-scaffold/" } }) }} + +After this track, keep building in **Build › [Services & SDK](/services-sdk/)** — the guides and recipes there pick up where these chapters stop. diff --git a/docs/site/tutorials/workspace/06-deploy.md b/docs/site/tutorials/workspace/06-deploy.md index 502997dce..6d1915759 100644 --- a/docs/site/tutorials/workspace/06-deploy.md +++ b/docs/site/tutorials/workspace/06-deploy.md @@ -152,7 +152,7 @@ observable, correctly-wired stack on <strong>one machine</strong>. The Postgres are throwaway Docker containers for dev convenience — <strong>not</strong> your production database or cache, and the <code>kv-oauth</code> session store and auth credentials here are local-dev values. For a remote target you point processes at managed infrastructure and let your platform own lifecycle; -that is the <a href="/how-to/deploy/">Deploy</a> recipe. +that is the <a href="/orchestration-runtime/how-to/deploy/">Deploy</a> recipe. {{ /comp }} {{ comp callout { type: "warning", title: "Footguns when aspire start will not boot" } }} @@ -181,7 +181,7 @@ gets a `401`; the engineer you paged gets provisioned without anyone waiting on ## Where to go next -- **Ship it remotely** → [Deploy](/how-to/deploy/) — the production companion: deployable units, +- **Ship it remotely** → [Deploy](/orchestration-runtime/how-to/deploy/) — the production companion: deployable units, managed backing services, and the `--no-aspire` path. - **Go deeper on auth** → [Authentication capability](/capabilities/auth/) and [The authentication model](/explanation/auth-model/). diff --git a/docs/site/tutorials/workspace/index.md b/docs/site/tutorials/workspace/index.md index ff750576f..e7a6a2def 100644 --- a/docs/site/tutorials/workspace/index.md +++ b/docs/site/tutorials/workspace/index.md @@ -4,6 +4,7 @@ title: Team Workspace templateEngine: [vento, md] prev: { label: "Tutorials", href: "/tutorials/" } next: { label: "1 · Scaffold", href: "/tutorials/workspace/01-scaffold/" } +order: 3 --- # Team Workspace @@ -134,3 +135,5 @@ chapter 1 and keep the same `my-workspace/` through to deploy. {{ comp.nextPrev({ prev: { label: "Tutorials", href: "/tutorials/" }, next: { label: "1 · Scaffold", href: "/tutorials/workspace/01-scaffold/" } }) }} </content> </invoke> + +After this track, keep building in **Build › [Identity & Access](/identity-access/)** — the guides and recipes there pick up where these chapters stop. diff --git a/docs/site/web-layer/builders.md b/docs/site/web-layer/builders.md index 94d8519f8..b36138070 100644 --- a/docs/site/web-layer/builders.md +++ b/docs/site/web-layer/builders.md @@ -2,6 +2,7 @@ layout: layouts/base.vto title: Pages and the define-page builder templateEngine: [vento, md] +order: 2 --- # Pages and the define-page builder diff --git a/docs/site/web-layer/defer-streaming-ui.md b/docs/site/web-layer/defer-streaming-ui.md index e1204c9cc..3c136b03a 100644 --- a/docs/site/web-layer/defer-streaming-ui.md +++ b/docs/site/web-layer/defer-streaming-ui.md @@ -2,6 +2,7 @@ layout: layouts/base.vto title: Deferred and streaming UI templateEngine: [vento, md] +order: 6 --- # Deferred and streaming UI diff --git a/docs/site/web-layer/error.md b/docs/site/web-layer/error.md index 76ea988c1..f86c410e2 100644 --- a/docs/site/web-layer/error.md +++ b/docs/site/web-layer/error.md @@ -2,6 +2,7 @@ layout: layouts/base.vto title: Error handling and diagnostics templateEngine: [vento, md] +order: 9 --- # Error handling and diagnostics diff --git a/docs/site/web-layer/examples.md b/docs/site/web-layer/examples.md index 6e3d13b00..69767b391 100644 --- a/docs/site/web-layer/examples.md +++ b/docs/site/web-layer/examples.md @@ -2,6 +2,7 @@ layout: layouts/base.vto title: Examples and sandbox templateEngine: [vento, md] +order: 11 --- # Examples and sandbox diff --git a/docs/site/web-layer/form.md b/docs/site/web-layer/form.md index 523fed433..968cdbd53 100644 --- a/docs/site/web-layer/form.md +++ b/docs/site/web-layer/form.md @@ -2,6 +2,7 @@ layout: layouts/base.vto title: Server-validated forms templateEngine: [vento, md] +order: 5 --- # Server-validated forms diff --git a/docs/site/web-layer/fresh-ui.md b/docs/site/web-layer/fresh-ui.md index 73f15cada..35b86d551 100644 --- a/docs/site/web-layer/fresh-ui.md +++ b/docs/site/web-layer/fresh-ui.md @@ -4,6 +4,7 @@ title: Fresh UI & design templateEngine: [vento, md] prev: { label: "Telemetry & logging", href: "/observability/telemetry/" } next: { label: "Authentication", href: "/identity-access/auth/" } +order: 12 --- # Fresh UI & design @@ -47,7 +48,7 @@ drift from your oRPC contracts. Use <strong><code>@netscript/fresh</code></stron meta-framework) to build pages/islands in any Fresh app; use <strong><code>apps/dashboard</code></strong> (the scaffolded app) as the worked reference to copy patterns from. For the data boundary the UI renders see <a href="/services-sdk/services/">Services & contracts</a>; to add or regenerate UI -pieces see <a href="/how-to/customize-fresh-ui/">Customize the Fresh UI</a>. +pieces see <a href="/web-layer/how-to/customize-fresh-ui/">Customize the Fresh UI</a>. {{ /comp }} ## Two layers, one design grammar @@ -287,7 +288,7 @@ matches what you're doing. { title: "Do — Customize the Fresh UI", body: "Task recipe: ui:init / ui:add, the copy-source ownership model, and editing the design tokens.", - href: "/how-to/customize-fresh-ui/", + href: "/web-layer/how-to/customize-fresh-ui/", icon: "◆" }, { diff --git a/docs/site/how-to/build-a-desktop-frontend.md b/docs/site/web-layer/how-to/build-a-desktop-frontend.md similarity index 96% rename from docs/site/how-to/build-a-desktop-frontend.md rename to docs/site/web-layer/how-to/build-a-desktop-frontend.md index 0115e120a..065f269db 100644 --- a/docs/site/how-to/build-a-desktop-frontend.md +++ b/docs/site/web-layer/how-to/build-a-desktop-frontend.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Building a desktop frontend the NetScript way templateEngine: [vento, md] -prev: { 'label': 'Customize Fresh UI', 'href': '/how-to/customize-fresh-ui/' } -next: { 'label': 'Build a server-validated form', 'href': '/how-to/build-a-server-validated-form/' } +order: 102 +oldUrl: /how-to/build-a-desktop-frontend/ --- # Building a desktop frontend the NetScript way @@ -153,7 +153,7 @@ packaged-runtime check. {{ /comp }} ## See also -- [Customize Fresh UI](/how-to/customize-fresh-ui/) +- [Customize Fresh UI](/web-layer/how-to/customize-fresh-ui/) - [`@netscript/fresh` reference](/reference/fresh/) - [`@netscript/sdk` reference](/reference/sdk/) - [`@netscript/fresh-ui` reference](/reference/fresh-ui/) diff --git a/docs/site/how-to/build-a-server-validated-form.md b/docs/site/web-layer/how-to/build-a-server-validated-form.md similarity index 88% rename from docs/site/how-to/build-a-server-validated-form.md rename to docs/site/web-layer/how-to/build-a-server-validated-form.md index 1ca63294d..b0e32ea6f 100644 --- a/docs/site/how-to/build-a-server-validated-form.md +++ b/docs/site/web-layer/how-to/build-a-server-validated-form.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Build a server-validated form templateEngine: [vento, md] -prev: { label: "Add a task runtime adapter", href: "/how-to/add-a-task-runtime-adapter/" } -next: { label: "Build a validated ingestion queue", href: "/how-to/build-a-validated-ingestion-queue/" } +order: 103 +oldUrl: /how-to/build-a-server-validated-form/ --- # Build a server-validated form @@ -102,6 +102,6 @@ Deno.test('contact page registers the route pattern', () => { - Look up the builder surface in [fresh reference](/reference/fresh/). {{ comp.nextPrev({ - prev: { label: "Add a task runtime adapter", href: "/how-to/add-a-task-runtime-adapter/" }, - next: { label: "Build a validated ingestion queue", href: "/how-to/build-a-validated-ingestion-queue/" } + prev: { label: "Add a task runtime adapter", href: "/background-processing/how-to/add-a-task-runtime-adapter/" }, + next: { label: "Build a validated ingestion queue", href: "/durable-workflows/how-to/build-a-validated-ingestion-queue/" } }) }} diff --git a/docs/site/how-to/customize-fresh-ui.md b/docs/site/web-layer/how-to/customize-fresh-ui.md similarity index 97% rename from docs/site/how-to/customize-fresh-ui.md rename to docs/site/web-layer/how-to/customize-fresh-ui.md index 08a325adc..8be2b2a0c 100644 --- a/docs/site/how-to/customize-fresh-ui.md +++ b/docs/site/web-layer/how-to/customize-fresh-ui.md @@ -2,8 +2,8 @@ layout: layouts/base.vto title: Customize Fresh UI templateEngine: [vento, md] -prev: { "label": "Add OpenTelemetry", "href": "/how-to/add-opentelemetry/" } -next: { "label": "Build a durable chat", "href": "/how-to/build-a-durable-chat/" } +order: 101 +oldUrl: /how-to/customize-fresh-ui/ --- # Customize Fresh UI @@ -204,7 +204,7 @@ When an island or route needs server data, prefer the runtime's typed query laye (<code>@netscript/fresh/query</code>) over hand-rolled <code>fetch</code> calls — it carries the oRPC contract types through to the client so a renamed service field surfaces as a type error. See <a href="/reference/fresh/"><code>@netscript/fresh</code></a> -for the query builders, and <a href="/how-to/add-a-service/">Add a service</a> for +for the query builders, and <a href="/services-sdk/how-to/add-a-service/">Add a service</a> for the backend half of that contract. {{ /comp }} @@ -389,9 +389,9 @@ type. [`@netscript/fresh`](/reference/fresh/). These are the authority for every export (the `/server`, `/query`, and sibling subpaths included); this guide never duplicates them. -- Related recipes: [Add a service](/how-to/add-a-service/) to give your UI a typed - oRPC backend, and [Add OpenTelemetry](/how-to/add-opentelemetry/) to trace it. +- Related recipes: [Add a service](/services-sdk/how-to/add-a-service/) to give your UI a typed + oRPC backend, and [Add OpenTelemetry](/observability/how-to/add-opentelemetry/) to trace it. - Concepts: the [contracts](/explanation/contracts/) explanation shows how a typed contract flows from service to client to island. -{{ comp.nextPrev({ prev: { label: "Add OpenTelemetry", href: "/how-to/add-opentelemetry/" }, next: { label: "Build a durable chat", href: "/how-to/build-a-durable-chat/" } }) }} +{{ comp.nextPrev({ prev: { label: "Add OpenTelemetry", href: "/observability/how-to/add-opentelemetry/" }, next: { label: "Build a durable chat", href: "/ai/how-to/build-a-durable-chat/" } }) }} diff --git a/docs/site/web-layer/how-to/index.md b/docs/site/web-layer/how-to/index.md new file mode 100644 index 000000000..67c4b251c --- /dev/null +++ b/docs/site/web-layer/how-to/index.md @@ -0,0 +1,12 @@ +--- +layout: layouts/base.vto +title: Recipes +templateEngine: [vento, md] +order: 100 +--- + +# Web Layer — recipes + +Task-oriented recipes for this area. Each one solves a single concrete problem and assumes you know +the basics from the guides above it in the sidebar. The full cross-area catalog lives at +[All how-to recipes]({{ "howto:index" |> xref |> url }}). diff --git a/docs/site/web-layer/index.md b/docs/site/web-layer/index.md index a7a1b03af..51414ad50 100644 --- a/docs/site/web-layer/index.md +++ b/docs/site/web-layer/index.md @@ -72,8 +72,17 @@ scaffolded dashboard app — is [Fresh UI & design](/web-layer/fresh-ui/), which { eyebrow: "Overview & Concepts", title: "Fresh page model", body: "Server rendering, islands, route contracts, layers, partials, and shared query cache.", href: "/web-layer/server/", icon: "O" }, { eyebrow: "Overview & Concepts", title: "Fresh UI & design", body: "The copy-source component registry, design tokens, and the scaffolded dashboard app.", href: "/web-layer/fresh-ui/", icon: "O" }, { eyebrow: "Quickstart", title: "Live dashboard", body: "Build a Fresh page backed by a typed SDK client and a cache-first QueryIsland.", href: "/tutorials/live-dashboard/", icon: "Q" }, - { eyebrow: "How-To", title: "Customize Fresh UI", body: "Adjust the generated UI layer and design-system surface.", href: "/how-to/customize-fresh-ui/", icon: "H" }, - { eyebrow: "How-To", title: "Server-validated form", body: "Build a form that validates and mutates on the server.", href: "/how-to/build-a-server-validated-form/", icon: "H" }, + { eyebrow: "How-To", title: "Customize Fresh UI", body: "Adjust the generated UI layer and design-system surface.", href: "/web-layer/how-to/customize-fresh-ui/", icon: "H" }, + { eyebrow: "How-To", title: "Server-validated form", body: "Build a form that validates and mutates on the server.", href: "/web-layer/how-to/build-a-server-validated-form/", icon: "H" }, { eyebrow: "API Reference", title: "@netscript/fresh", body: "Generated symbols for the Fresh framework package.", href: "/reference/fresh/", icon: "R" }, { eyebrow: "API Reference", title: "@netscript/fresh-ui", body: "Generated symbols for the companion UI package.", href: "/reference/fresh-ui/", icon: "R" } ] }) }} + +## Learn, do, look up + +{{ comp.cardsGrid({ columns: 4, cards: [ + { eyebrow: "Learn", title: "Live dashboard tutorial", body: "Contract to page to live stream — the web layer end to end.", href: resolveXref("tut:live-dashboard").href }, + { eyebrow: "Do", title: "Recipes", body: "Task-oriented recipes for this area, one problem each.", href: "/web-layer/how-to/" }, + { eyebrow: "Look up", title: "`@netscript/fresh` reference", body: "Generated API reference. Related units: `fresh-ui`.", href: resolveXref("ref:fresh").href }, + { eyebrow: "Understand", title: "Contracts & type flow", body: "The design rationale behind this pillar.", href: resolveXref("explain:contracts").href }, +] }) }} diff --git a/docs/site/web-layer/interactive.md b/docs/site/web-layer/interactive.md index f1c484588..0039c9032 100644 --- a/docs/site/web-layer/interactive.md +++ b/docs/site/web-layer/interactive.md @@ -2,6 +2,7 @@ layout: layouts/base.vto title: Interactive islands templateEngine: [vento, md] +order: 7 --- # Interactive islands diff --git a/docs/site/web-layer/query.md b/docs/site/web-layer/query.md index dfbbf3436..eee644158 100644 --- a/docs/site/web-layer/query.md +++ b/docs/site/web-layer/query.md @@ -2,6 +2,7 @@ layout: layouts/base.vto title: Data loading and the query cache templateEngine: [vento, md] +order: 4 --- # Data loading and the query cache diff --git a/docs/site/web-layer/route.md b/docs/site/web-layer/route.md index 74ced47d9..18c43b6f4 100644 --- a/docs/site/web-layer/route.md +++ b/docs/site/web-layer/route.md @@ -2,6 +2,7 @@ layout: layouts/base.vto title: Routing and route contracts templateEngine: [vento, md] +order: 3 --- # Routing and route contracts diff --git a/docs/site/web-layer/server.md b/docs/site/web-layer/server.md index 9e74a359f..6d7112e63 100644 --- a/docs/site/web-layer/server.md +++ b/docs/site/web-layer/server.md @@ -2,6 +2,7 @@ layout: layouts/base.vto title: The Fresh page model templateEngine: [vento, md] +order: 1 --- # The Fresh page model diff --git a/docs/site/web-layer/testing.md b/docs/site/web-layer/testing.md index 6142543fe..21e70a797 100644 --- a/docs/site/web-layer/testing.md +++ b/docs/site/web-layer/testing.md @@ -2,6 +2,7 @@ layout: layouts/base.vto title: Testing Fresh pages templateEngine: [vento, md] +order: 10 --- # Testing Fresh pages diff --git a/docs/site/web-layer/vite.md b/docs/site/web-layer/vite.md index 2e35bb6ce..21bf97e3e 100644 --- a/docs/site/web-layer/vite.md +++ b/docs/site/web-layer/vite.md @@ -2,6 +2,7 @@ layout: layouts/base.vto title: Build and Vite integration templateEngine: [vento, md] +order: 8 --- # Build and Vite integration