Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/fern-docs-preview-comment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ jobs:
HEAD_REF: ${{ steps.metadata.outputs.head_ref }}
working-directory: ./docs/fern
run: |
node scripts/filter-public-openapi.mjs
OUTPUT=$(npx -y fern-api@latest generate --docs --preview --id "$HEAD_REF" 2>&1)
echo "$OUTPUT"
URL=$(echo "$OUTPUT" | grep -oP 'Published docs to \K.*(?= \()')
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,6 @@ docker/logs/*
# Linting artifact
python-sdk-lint/
site/

# Generated Fern-only public API reference spec
docs/fern/openapi/openapi.public.yaml
2 changes: 1 addition & 1 deletion docs/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ These are generated; edit the source and regenerate:

## API reference

The REST API reference is rendered natively by Fern from the OpenAPI spec — `docs/fern/generators.yml` points at `docs/fern/openapi/openapi.yaml` (a symlink to the generated repo-root `openapi/openapi.yaml`), surfaced by the `- api: API Reference` nav node. No `<swagger-ui>` embed.
The REST API reference is rendered natively by Fern from a docs-only filtered OpenAPI spec. `docs/fern/openapi/openapi.yaml` is a symlink to the generated repo-root `openapi/openapi.yaml`, and `npm run prepare:openapi` writes `docs/fern/openapi/openapi.public.yaml` for Fern. `docs/fern/generators.yml` must point at the public filtered spec, surfaced by the `- api: API Reference` nav node. No `<swagger-ui>` embed.
4 changes: 2 additions & 2 deletions docs/agents/plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: ""
---
<a id="agents-plugins"></a>

NeMo Platform 0.1.0 is extended through Python plugins and
NeMo Platform 0.2.0 is extended through Python plugins and
coding-agent skills. Plugins add runtime capabilities to the local platform.
Skills teach your coding agent how to operate those capabilities on your behalf.

Expand Down Expand Up @@ -103,5 +103,5 @@ Plugin authors use the `nemo-platform-plugin` package. A minimal plugin declares
points in `pyproject.toml`, implements the matching class, and restarts
`nemo services run` so the local platform discovers it.

Keep plugins local-first for OSS 0.1.0: avoid requiring a cluster,
Keep plugins local-first for OSS 0.2.0: avoid requiring a cluster,
container runtime, or external control plane for the documented launch path.
2 changes: 1 addition & 1 deletion docs/auth/authorization/api-scopes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Each API has a read and write scope. A token with an API-specific scope can only

| API | Read Scope | Write Scope | Endpoints |
|------|-----------|-------------|------|
| Audit | `audit:read` | `audit:write` | `/apis/audit/` |
| Audit | `audit:read` | `audit:write` | `/apis/auditor/` |
| Auth | `auth:read` | `auth:write` | `/apis/auth/` |
| Data Designer | `data-designer:read` | `data-designer:write` | `/apis/data-designer/` |
| Entities | `entities:read` | `entities:write` | `/apis/entities/` (internal — see note) |
Expand Down
2 changes: 1 addition & 1 deletion docs/auth/authorization/managing-access.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Add users to workspaces, assign roles, and control who can access your resources

<Note>

The SDK examples on this page use `NeMoPlatform()` with no arguments so that the client reads your active CLI context (set by `nemo auth login`). That is the right pattern for authorization workflows: you act as your logged-in identity and pass the workspace explicitly in each API call. For the standard local initialization pattern, see [CLI and SDK initialization](/documentation/get-started#setup-init).
The SDK examples on this page use `NeMoPlatform()` with no arguments so that the client reads your active CLI context (set by `nemo auth login`). That is the right pattern for authorization workflows: you act as your logged-in identity and pass the workspace explicitly in each API call. For the standard local initialization pattern, see [CLI and SDK initialization](/documentation/get-started#nemo-setup).

</Note>
## Creating Workspaces
Expand Down
16 changes: 0 additions & 16 deletions docs/auth/authorization/permissions-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,6 @@ PlatformAdmin is omitted — it bypasses permission checks entirely at the polic
| <code>inference.virtual-models.(read &#124; list)</code> | Read, list inference virtual-models | ✓ | ✓ | ✓ |
| <code>inference.virtual-models.(create &#124; update &#124; delete)</code> | Create, update, delete inference virtual-models | | ✓ | ✓ |

## Intake API

| Permission | Description | Viewer | Editor | Admin |
|------------|-------------|:------:|:------:|:-----:|
| <code>intake.annotations.(read &#124; list)</code> | Read, list intake annotations | ✓ | ✓ | ✓ |
| <code>intake.annotations.(create &#124; delete)</code> | Create, delete intake annotations | | ✓ | ✓ |
| <code>intake.evaluator-results.(read &#124; list)</code> | Read, list intake evaluator-results | ✓ | ✓ | ✓ |
| `intake.evaluator-results.create` | Create intake evaluator results | | ✓ | ✓ |
| `intake.experiment-groups.read` | Read intake experiment groups | ✓ | ✓ | ✓ |
| <code>intake.experiment-groups.(create &#124; update &#124; delete)</code> | Create, update, delete intake experiment-groups | | ✓ | ✓ |
| `intake.experiments.read` | Read intake experiments | ✓ | ✓ | ✓ |
| <code>intake.experiments.(create &#124; update &#124; delete)</code> | Create, update, delete intake experiments | | ✓ | ✓ |
| `intake.ingest.create` | Ingest traces into intake | | ✓ | ✓ |
| <code>intake.spans.(read &#124; list)</code> | Read, list intake spans | ✓ | ✓ | ✓ |
| `intake.traces.read` | Read intake traces | ✓ | ✓ | ✓ |

## Jobs API

| Permission | Description | Viewer | Editor | Admin |
Expand Down
1 change: 0 additions & 1 deletion docs/contributing/skills-spec.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
title: "NeMo Platform Skills Spec"
description: ""
---
**Status:** Draft.

This document defines the conventions for skills shipped with NeMo Platform: how they're structured, what their frontmatter must contain, how they get tested, and where they live in the repo.

Expand Down
2 changes: 1 addition & 1 deletion docs/customizer/tutorials/format-training-dataset.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ For illustrative purposes only, we show an example entry as multi-line JSON.
</Accordion>
##### Shared Tools

When your dataset uses the same set of tools across all examples, include the shared tool definitions in **each training row** (Automodel jobs do not accept a separate `dataset_parameters` block). See the [Tool Calling tutorial](/documentation/example-applications/tool-calling) for a full workflow.
When your dataset uses the same set of tools across all examples, include the shared tool definitions in **each training row** (Automodel jobs do not accept a separate `dataset_parameters` block).

```python
import os
Expand Down
2 changes: 1 addition & 1 deletion docs/evaluator/tutorials/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ Learn how to write a domain-specific Python metric, test it locally, and run it

## How It Works

For the conceptual overview of how Evaluator separates definition (library) from execution (platform), see [About Evaluating → How It Works](/documentation/evaluate-models#how-it-works-library-platform). For runnable SDK examples, see [SDK Resources](/documentation/evaluate-models/sdk-resources).
For the conceptual overview of how Evaluator separates definition (library) from execution (platform), see [About Evaluating → How It Works](/documentation/evaluate-models#how-it-works-library--platform). For runnable SDK examples, see [SDK Resources](/documentation/evaluate-models/sdk-resources).
Comment thread
mckornfield marked this conversation as resolved.
5 changes: 3 additions & 2 deletions docs/fern/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ docs/ # page content (.mdx), one tree per product area
├── docs.yml # Site config, theme, css, redirects, versions
├── generators.yml # OpenAPI spec wiring for the API reference
├── openapi/openapi.yaml # symlink -> repo-root openapi/openapi.yaml (generated)
├── openapi/openapi.public.yaml # generated Fern-only filtered spec (gitignored)
├── styles/ # CSS (notebook-viewer.css, button.css)
├── assets/ # logos, shared images
├── components/ # custom TSX MDX components
Expand Down Expand Up @@ -81,10 +82,10 @@ Avoid source-path links such as `/get-started/concepts/workspaces`, `/latest/get

The REST API reference is generated natively by Fern from the OpenAPI spec — no `<swagger-ui>` embed. Two pieces wire it up:

- `generators.yml` declares the spec: `api.specs[].openapi: ./openapi/openapi.yaml`.
- `generators.yml` declares the docs-only public spec: `api.specs[].openapi: ./openapi/openapi.public.yaml`.
- `versions/latest.yml` surfaces it with an `- api: API Reference` navigation node (under the **Reference** section).

`openapi/openapi.yaml` is a symlink to the repo-root `openapi/openapi.yaml` (the generated source of truth), so the reference tracks the Platform API automatically. Regenerate the spec with `make refresh-openapi` from the repo root, then run `npm run check`.
`openapi/openapi.yaml` is a symlink to the repo-root `openapi/openapi.yaml` (the generated source of truth). Fern reads `openapi/openapi.public.yaml`, which is generated by `scripts/filter-public-openapi.mjs` and excludes services that should stay out of public docs while remaining in the root spec for SDK generation and API validation. Regenerate the root spec with `make refresh-openapi` from the repo root, then run `npm run check`.

Fern groups endpoints by their OpenAPI tag in the sidebar (Customizer, Evaluator, Guardrails, …), which replaces the old per-service filter chips. Link to it from other pages with the nav URL `/documentation/reference/api-reference`.

Expand Down
2 changes: 1 addition & 1 deletion docs/fern/generators.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
api:
specs:
- openapi: ./openapi/openapi.yaml
- openapi: ./openapi/openapi.public.yaml
13 changes: 7 additions & 6 deletions docs/fern/package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
{
"private": true,
"scripts": {
"check": "npx -y fern-api@latest check && npm run validate-mdx && npm run check:gated-links",
"check:fern": "npx -y fern-api@latest check",
"prepare:openapi": "node scripts/filter-public-openapi.mjs",
"check": "npm run prepare:openapi && npx -y fern-api@latest check && npm run validate-mdx && npm run check:gated-links",
"check:fern": "npm run prepare:openapi && npx -y fern-api@latest check",
"validate-mdx": "node scripts/validate-mdx.mjs",
"check:gated-links": "node scripts/delink-gated.mjs",
"fix:gated-links": "node scripts/delink-gated.mjs --fix",
"broken-links": "npx -y fern-api@latest docs broken-links",
"dev": "npx -y fern-api@latest docs dev",
"broken-links": "npm run prepare:openapi && npx -y fern-api@latest docs broken-links",
"dev": "npm run prepare:openapi && npx -y fern-api@latest docs dev",
"watch": "node scripts/docs-watch.mjs",
"generate": "npx -y fern-api@latest generate --docs",
"preview": "npx -y fern-api@latest generate --docs --preview"
"generate": "npm run prepare:openapi && npx -y fern-api@latest generate --docs",
"preview": "npm run prepare:openapi && npx -y fern-api@latest generate --docs --preview"
},
"devDependencies": {
"@mdx-js/mdx": "^3.1.0"
Expand Down
72 changes: 58 additions & 14 deletions docs/fern/scripts/docs-watch.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { spawn } from "node:child_process";
import { spawn, spawnSync } from "node:child_process";
import { watch } from "node:fs";
import { utimes } from "node:fs/promises";
import { constants } from "node:os";
Expand All @@ -14,8 +14,12 @@ const docsRoot = path.resolve(fernDir, "..");
const reloadTrigger = path.join(fernDir, "docs.yml");
const reloadDebounceMs = 150;
const ignoredPrefix = "fern/";
const openapiInputPrefix = "fern/openapi/";
const publicOpenapiPath = "fern/openapi/openapi.public.yaml";
const filterOpenapiScriptPath = "fern/scripts/filter-public-openapi.mjs";

let debounceTimer = null;
let pendingReloadRequiresOpenapi = false;
let shuttingDown = false;

function log(message) {
Expand All @@ -36,36 +40,76 @@ function clearPendingReload() {
debounceTimer = null;
}

function scheduleReload(relativePath) {
function scheduleReload(relativePath, requiresOpenapiPrepare = false) {
clearPendingReload();
pendingReloadRequiresOpenapi = pendingReloadRequiresOpenapi || requiresOpenapiPrepare;

debounceTimer = setTimeout(() => {
debounceTimer = null;
touchReloadTrigger(relativePath).catch((error) => {
log(`failed to trigger reload: ${error.message}`);
});
const shouldRunOpenapiPrepare = pendingReloadRequiresOpenapi;
pendingReloadRequiresOpenapi = false;
Promise.resolve()
.then(() => {
if (shouldRunOpenapiPrepare) {
prepareOpenapi();
}
})
.then(() => touchReloadTrigger(relativePath))
.catch((error) => {
log(`failed to trigger reload: ${error.message}`);
});
}, reloadDebounceMs);
}

function shouldIgnore(relativePath) {
const normalized = path.posix.normalize(relativePath.split(path.sep).join("/"));
return normalized.startsWith(ignoredPrefix);
function normalizeWatchedPath(relativePath) {
return path.posix.normalize(relativePath.split(path.sep).join("/"));
}

const fern = spawn("npx", ["-y", "fern-api@latest", "docs", "dev"], {
cwd: fernDir,
stdio: "inherit",
});
function shouldPrepareOpenapi(normalizedPath) {
return (
normalizedPath === filterOpenapiScriptPath ||
(normalizedPath.startsWith(openapiInputPrefix) && normalizedPath !== publicOpenapiPath)
);
}

function shouldIgnore(normalizedPath) {
return normalizedPath.startsWith(ignoredPrefix) && !shouldPrepareOpenapi(normalizedPath);
}

function prepareOpenapi() {
const result = spawnSync("node", ["scripts/filter-public-openapi.mjs"], {
cwd: fernDir,
stdio: "inherit",
});

if (result.error) {
throw result.error;
}
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
}

function spawnFernDev() {
prepareOpenapi();
return spawn("npx", ["-y", "fern-api@latest", "docs", "dev"], {
cwd: fernDir,
stdio: "inherit",
});
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
const fern = spawnFernDev();

const watcher = watch(
docsRoot,
{ recursive: true },
(_eventType, filename) => {
const relativePath = filename ? filename.toString() : "";
if (shouldIgnore(relativePath)) {
const normalizedPath = normalizeWatchedPath(relativePath);
if (shouldIgnore(normalizedPath)) {
return;
}
scheduleReload(relativePath);
scheduleReload(relativePath, shouldPrepareOpenapi(normalizedPath));
},
);

Expand Down
Loading
Loading