From 9c2c49fcdc52fbe23a3fa523068f6c23b56aa5fc Mon Sep 17 00:00:00 2001 From: Rickylabs Date: Sun, 12 Jul 2026 00:03:32 +0200 Subject: [PATCH 1/5] docs: clarify PORT/typed-config relationship across 8 pages (#660 #7) Aspire injects PORT at runtime; netscript.config.ts services..port (apps..port for Fresh apps) is the typed source of truth the scaffold wires as the fallback default. One shared sentence at each first parseInt occurrence so the raw-env read no longer reads as the canonical pattern. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01VifzAytcxBk5odEMaCXihM --- docs/site/explanation/contracts.md | 4 ++++ docs/site/how-to/add-a-service.md | 4 ++++ docs/site/how-to/add-opentelemetry.md | 4 ++++ docs/site/how-to/customize-fresh-ui.md | 4 ++++ docs/site/how-to/expose-openapi-scalar.md | 4 ++++ docs/site/services-sdk/services.md | 4 ++++ docs/site/tutorials/live-dashboard/02-contract-to-service.md | 4 ++++ 7 files changed, 28 insertions(+) diff --git a/docs/site/explanation/contracts.md b/docs/site/explanation/contracts.md index c07ac3af4..b2d0a03b1 100644 --- a/docs/site/explanation/contracts.md +++ b/docs/site/explanation/contracts.md @@ -126,6 +126,10 @@ two artifacts. } ] }) }} +Aspire injects `PORT` at runtime, so the entrypoint reads it from the environment; the typed source +of truth is your `netscript.config.ts` `services..port` field, which the scaffold wires as the +fallback default — set the port there rather than editing this line. + The `users` service listens on **port 3001** and exposes two parallel transports from the same contract: REST-shaped routes under `/api/v1/users/*` (driven by `oc.route({ method, path })` and surfaced in OpenAPI) and a typed RPC channel under `/api/rpc/v1/...` that the derived client diff --git a/docs/site/how-to/add-a-service.md b/docs/site/how-to/add-a-service.md index 1dd0c9b67..ddda94ccd 100644 --- a/docs/site/how-to/add-a-service.md +++ b/docs/site/how-to/add-a-service.md @@ -207,6 +207,10 @@ await defineService(router, { }); ``` +Aspire injects `PORT` at runtime, so the entrypoint reads it from the environment; the typed source +of truth is your `netscript.config.ts` `services..port` field, which the scaffold wires as the +fallback default — set the port there rather than editing this line. + `defineService` stands up the Hono + oRPC runtime, mounting your router under both an OpenAPI surface (`/api/v1/users/*`) and the RPC surface (`/api/rpc/v1/...`). The default RPC mount point is `/api/rpc` and the OpenAPI mount point is `/api`; both are overridable diff --git a/docs/site/how-to/add-opentelemetry.md b/docs/site/how-to/add-opentelemetry.md index 8b454b430..b64c60ac4 100644 --- a/docs/site/how-to/add-opentelemetry.md +++ b/docs/site/how-to/add-opentelemetry.md @@ -195,6 +195,10 @@ app with the fluent builder and opts the RPC layer into trace context explicitly } ] }) }} +Aspire injects `PORT` at runtime, so the entrypoint reads it from the environment; the typed source +of truth is your `netscript.config.ts` `services..port` field, which the scaffold wires as the +fallback default — set the port there rather than editing this line. + {{ comp callout { type: "note", title: "traceparent is the join key" } }} NetScript follows the W3C Trace Context standard. When an inbound request carries a traceparent header, the service continues that trace instead of starting a new diff --git a/docs/site/how-to/customize-fresh-ui.md b/docs/site/how-to/customize-fresh-ui.md index fac0255be..51dfa340d 100644 --- a/docs/site/how-to/customize-fresh-ui.md +++ b/docs/site/how-to/customize-fresh-ui.md @@ -98,6 +98,10 @@ and components: } ] }) }} +Aspire injects `PORT` at runtime, so the entrypoint reads it from the environment; the typed source +of truth is your `netscript.config.ts` `apps..port` field, which the scaffold wires as the +fallback default — set the port there rather than editing this line. + {{ comp callout { type: "note", title: "Two import roots, two jobs" } }} @netscript/fresh is the runtime meta-framework: it ships the app bootstrap (/server), route and page builders, and a typed diff --git a/docs/site/how-to/expose-openapi-scalar.md b/docs/site/how-to/expose-openapi-scalar.md index 1d44a8b6d..a4c2b2db0 100644 --- a/docs/site/how-to/expose-openapi-scalar.md +++ b/docs/site/how-to/expose-openapi-scalar.md @@ -55,6 +55,10 @@ await defineService(router, { }); ``` +Aspire injects `PORT` at runtime, so the entrypoint reads it from the environment; the typed source +of truth is your `netscript.config.ts` `services..port` field, which the scaffold wires as the +fallback default — set the port there rather than editing this line. + This wires three routes onto the service: {{ comp.apiTable({ diff --git a/docs/site/services-sdk/services.md b/docs/site/services-sdk/services.md index 98c899b79..a6d968fbb 100644 --- a/docs/site/services-sdk/services.md +++ b/docs/site/services-sdk/services.md @@ -148,6 +148,10 @@ the same Hono + oRPC runtime; `defineService` is a curated preset over the same } ] }) }} +Aspire injects `PORT` at runtime, so the entrypoint reads it from the environment; the typed source +of truth is your `netscript.config.ts` `services..port` field, which the scaffold wires as the +fallback default — set the port there rather than editing this line. + ### `defineService(router, options)` — the preset options `DefineServiceOptions` extends the base `ServiceConfig` (`name`, `version`, `port`) and adds 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 1767d09c7..b7f95f9d0 100644 --- a/docs/site/tutorials/live-dashboard/02-contract-to-service.md +++ b/docs/site/tutorials/live-dashboard/02-contract-to-service.md @@ -175,6 +175,10 @@ await defineService(router, { }); ``` +Aspire injects `PORT` at runtime, so the entrypoint reads it from the environment; the typed source +of truth is your `netscript.config.ts` `services..port` field, which the scaffold wires as the +fallback default — set the port there rather than editing this line. + The service binds the router and exposes the same surface on port **3002**: {{ comp.apiTable({ From 8bb1ab58078d4c8099e28ad5bd7a8877300bfe98 Mon Sep 17 00:00:00 2001 From: Rickylabs Date: Sun, 12 Jul 2026 00:03:39 +0200 Subject: [PATCH 2/5] docs: reference showcase ledes, see-it-live links, streams framing (#660 #8 #9 #10 #15) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - reference/ai: explicit note that AI provider keys have no typed config surface; raw Deno.env.get is the supported path (#8). - reference/queue + reference/kv: See-it-live link blocks to the queue-kv-cron how-to and data-persistence concept page (#9). - reference/contracts + reference/sdk: showcase sentence per lede — contracts obviate manual req.json() validation; defineServices() wires the whole typed stack in one call (#10). - durable-workflows/streams: one sentence reframing no in-process subscribe() as an intentional single HTTP/SSE surface (#15). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01VifzAytcxBk5odEMaCXihM --- docs/site/durable-workflows/streams.md | 5 ++++- docs/site/reference/ai/index.md | 6 ++++++ docs/site/reference/contracts/index.md | 7 +++++-- docs/site/reference/kv/index.md | 7 +++++++ docs/site/reference/queue/index.md | 9 +++++++++ docs/site/reference/sdk/index.md | 7 +++++-- 6 files changed, 36 insertions(+), 5 deletions(-) diff --git a/docs/site/durable-workflows/streams.md b/docs/site/durable-workflows/streams.md index 0eb66fb3e..354de3fc7 100644 --- a/docs/site/durable-workflows/streams.md +++ b/docs/site/durable-workflows/streams.md @@ -109,7 +109,10 @@ The producer side is two calls: freeze a typed schema, open a stream, write entity state. The consumer side is an HTTP/SSE read of the same `:4437` stream path — there is no in-process `subscribe()` handle, so a Fresh island (or any SSE client) reads the durable log directly and materializes the latest value per -key. +key. This is deliberate: exposing one HTTP/SSE surface instead of an in-process +`subscribe()` means a browser island and a server-side consumer read the durable +log through the exact same path and wire format, with no separate in-VM +subscription API to keep in sync. {{ comp.tabbedCode({ tabs: [ { diff --git a/docs/site/reference/ai/index.md b/docs/site/reference/ai/index.md index 3cb3c4195..e62fcaaf4 100644 --- a/docs/site/reference/ai/index.md +++ b/docs/site/reference/ai/index.md @@ -330,6 +330,12 @@ and providers through their config bags. Two adapters resolve an API key from th > `Deno.env.get(...)` in your composition code. Reading a key from the environment requires > `--allow-env` for the variable in question. +> **Provider keys have no typed config surface — by design.** Unlike service topology (typed +> through `@netscript/config`), AI provider credentials are **not** modelled in +> `netscript.config.ts`. Reading the key with raw `Deno.env.get(...)` (or letting the Anthropic / +> OpenRouter adapters resolve their env var) is the **supported** path — secrets stay out of the +> committed config and are injected by the runtime environment instead. + ## Examples ### Compose a runtime diff --git a/docs/site/reference/contracts/index.md b/docs/site/reference/contracts/index.md index 42ba4b000..26cbead52 100644 --- a/docs/site/reference/contracts/index.md +++ b/docs/site/reference/contracts/index.md @@ -7,8 +7,11 @@ title: "@netscript/contracts" Contract vocabulary shared across NetScript package and plugin boundaries: the oRPC base contract, common error data, pagination schemas, result types, and schema helper factories. -This page is generated from the package's public surface with `deno doc` (US-2). For the full -index of packages and plugins return to the [reference overview](/reference/). +Because a contract's input schema is enforced at the transport boundary, a service handler never +hand-parses or validates `req.json()` — a request that doesn't match the schema is rejected before +your code runs, and the same contract types the client that calls it. This page is generated from +the package's public surface with `deno doc` (US-2). For the full index of packages and plugins +return to the [reference overview](/reference/). The root entrypoint (`@netscript/contracts`) carries the core contract primitives. Three sub-path exports add higher-level builders: diff --git a/docs/site/reference/kv/index.md b/docs/site/reference/kv/index.md index 312598651..9c7251443 100644 --- a/docs/site/reference/kv/index.md +++ b/docs/site/reference/kv/index.md @@ -137,6 +137,13 @@ A reusable contract harness for adapter authors. Re-exports the shared `KvStore` | `createMemoryKvAdapter` | function | `function createMemoryKvAdapter(): MemoryKvAdapter` | Factory for a clean in-memory KV adapter for downstream tests. | | `MemoryKvAdapter` | class | `class MemoryKvAdapter implements WatchableKv` | Volatile in-memory adapter (re-exported from the root). | +## See it live + +- **How-to:** [Queue / KV / cron](/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. + --- Back to the [reference overview](/reference/). diff --git a/docs/site/reference/queue/index.md b/docs/site/reference/queue/index.md index 78c75d078..539f89388 100644 --- a/docs/site/reference/queue/index.md +++ b/docs/site/reference/queue/index.md @@ -123,6 +123,15 @@ documented in the sections above. | `@netscript/queue/adapters/amqp` | `./adapters/amqp.adapter.ts` | RabbitMQ (AMQP) adapter. | | `@netscript/queue/adapters/kv-polling` | `./adapters/kv-polling.adapter.ts` | KV-polling adapter for KV Connect. | +## See it live + +- **How-to:** [Queue / KV / cron](/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 + 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. + --- Back to the [reference overview](/reference/). diff --git a/docs/site/reference/sdk/index.md b/docs/site/reference/sdk/index.md index a849a6fbf..34d2feb74 100644 --- a/docs/site/reference/sdk/index.md +++ b/docs/site/reference/sdk/index.md @@ -11,8 +11,11 @@ packages and plugins return to the [reference overview](/reference/). The root entrypoint (`@netscript/sdk`) is the high-level composition preset for service-aware applications: it re-exports the client, query, query-client, discovery, cache, telemetry, and -OpenAPI surfaces and adds the `defineServices()` preset. Focused sub-path exports carry the -same values for narrow imports - see [Sub-path exports](#sub-path-exports). +OpenAPI surfaces and adds the `defineServices()` preset. A single `defineServices()` call wires the +whole typed stack — service clients, cache-aware query factories, and query utils — from one service +map, so a component imports ready-to-use `queryOptions()` / `mutationOptions()` instead of +hand-writing fetch wrappers. Focused sub-path exports carry the same values for narrow imports - see +[Sub-path exports](#sub-path-exports). ## Composition preset (`defineServices`) From 6b2bcfe6de431e4947a51eb3e1bf8230df63951f Mon Sep 17 00:00:00 2001 From: Rickylabs Date: Sun, 12 Jul 2026 00:04:39 +0200 Subject: [PATCH 3/5] docs: add runnable end-to-end blocks to three decision how-tos (#660 #11) discover-services, choose-a-queue-provider, and add-a-task-runtime-adapter each gain one copy-pasteable end-to-end block (resulting file + full command sequence) at deploy-local-aspire density, demonstrating the pluggability the pages previously only asserted. All commands/APIs grounded in package source. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01VifzAytcxBk5odEMaCXihM --- .../site/how-to/add-a-task-runtime-adapter.md | 48 +++++++++++++++++++ docs/site/how-to/choose-a-queue-provider.md | 43 +++++++++++++++++ docs/site/how-to/discover-services.md | 46 ++++++++++++++++++ 3 files changed, 137 insertions(+) diff --git a/docs/site/how-to/add-a-task-runtime-adapter.md b/docs/site/how-to/add-a-task-runtime-adapter.md index 16e0494e4..16e969d01 100644 --- a/docs/site/how-to/add-a-task-runtime-adapter.md +++ b/docs/site/how-to/add-a-task-runtime-adapter.md @@ -120,6 +120,54 @@ if (!result.success) { } ``` +## Run it end to end + +The adapter and its registration above, run against a real task file. Copy the two files, then run +the command sequence from the workspace root: + +```js +// tasks/render-invoice.mjs — the external Node task the adapter spawns +const idx = process.argv.indexOf('--invoice'); +const invoice = idx >= 0 ? process.argv[idx + 1] : 'unknown'; +console.log(`rendered ${invoice}`); +``` + +```ts +// run-invoice.ts — register the adapter and execute one task +import { createDefaultTaskExecutor } from '@netscript/plugin-workers-core/executor'; +import { nodeAdapter } from './node-adapter.ts'; + +const executor = createDefaultTaskExecutor({ + customAdapters: { node: nodeAdapter }, + defaults: { cwd: Deno.cwd(), timeout: 300_000 }, +}); + +const result = await executor.execute( + { + id: 'render-invoice', + type: 'node', + entrypoint: './tasks/render-invoice.mjs', + args: ['--invoice', 'inv_123'], + }, + { correlationId: 'invoice.inv_123', onStdout: (line) => console.log(line) }, +); + +if (!result.success) throw new Error(result.error ?? 'task failed'); +console.log('status', result.status, 'exit', result.exitCode); +``` + +```bash +# From the workspace root. --allow-run=node lets the adapter spawn the Node +# binary; --allow-read lets it read the task file. Node must be on PATH. +deno run --allow-run=node --allow-read run-invoice.ts +# rendered inv_123 +# status completed exit 0 +``` + +`supports(task)` matches `task.type === 'node'`, so the executor routes `render-invoice` to your +adapter, which spawns `node ./tasks/render-invoice.mjs --invoice inv_123`, streams stdout through +`onStdout`, and returns a `TaskResult` with `status: 'completed'` and `exitCode: 0`. + ## Failure modes - `supports(task)` returns `false`: the executor returns a failed `TaskResult` for unsupported diff --git a/docs/site/how-to/choose-a-queue-provider.md b/docs/site/how-to/choose-a-queue-provider.md index d3b4e9228..abafebc0c 100644 --- a/docs/site/how-to/choose-a-queue-provider.md +++ b/docs/site/how-to/choose-a-queue-provider.md @@ -179,6 +179,49 @@ 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/). +## Prove it end to end — same call site, two backends + +The pluggability claim is only convincing if you can watch one file run unchanged on two backends. +Write the producer/consumer against the factory (no `provider`), then run it first on the Deno KV +fallback locally and again on Redis under Aspire — the file never changes. + +```ts +// jobs.ts — the resulting file: provider-neutral producer + consumer +import { createTypedQueue } from '@netscript/queue'; +import { z } from 'zod'; + +const JobSchema = z.object({ id: z.string(), kind: z.string() }); + +// No provider → auto-discovery. Probe order: RabbitMQ → Redis → Deno KV. +export const jobs = createTypedQueue('jobs', JobSchema); + +if (import.meta.main) { + await jobs.enqueue({ id: 'job-1', kind: 'reindex' }); + await jobs.listen(async (message) => { + console.log('processed', message.id, message.kind); + }); +} +``` + +```bash +# From the workspace root, in order: + +# 1. Local run — no Aspire, no broker. Auto-discovery falls through to Deno KV. +# The scaffold's deno.json already sets unstable: ['raw-imports', 'kv']; +# add --unstable-kv to any ad-hoc run that touches the queue. +deno run --unstable-kv -A jobs.ts +# -> processed job-1 reindex (on the Deno KV fallback) + +# 2. Provision the real backend, then run the SAME file untouched. +cd aspire && aspire start && cd .. # brings up Redis/Garnet +deno run --unstable-kv -A jobs.ts +# -> processed job-1 reindex (now auto-discovered onto Redis) +``` + +The call site — `createTypedQueue('jobs', JobSchema)`, `enqueue`, `listen` — is identical in both +runs; only the resolved backend changed. Pin a specific provider with `provider: QueueProvider.*` +(Step 2) when you want to take auto-discovery out of the loop. + ## In-production pitfalls {{ comp callout { type: "warning", title: "Choosing a provider — read before you ship" } }} diff --git a/docs/site/how-to/discover-services.md b/docs/site/how-to/discover-services.md index 228df67b9..7eebe4581 100644 --- a/docs/site/how-to/discover-services.md +++ b/docs/site/how-to/discover-services.md @@ -202,6 +202,52 @@ to point a client at a fixed URL with no orchestrator: services__users__http__0=http://localhost:3001 deno task --cwd web dev ``` +## End to end in one pass + +The four steps above, run start to finish against the example `users` service. Copy the file, then +run the command sequence from the workspace root: + +```ts +// web/src/clients/users.ts — the resulting file (contract in, typed client out) +import { createServiceClient, isDefinedError, safe } from '@netscript/sdk/client'; +import { UsersContractV1 } from '@my-app/contracts'; + +// serviceName is the discovery key — resolves services__users__http__0 at call time. +export const usersClient = createServiceClient({ + contract: UsersContractV1, + serviceName: 'users', +}); + +// A typed call with safe-style error handling — no hardcoded URL anywhere. +export async function firstUsers() { + const [error, result] = await safe(usersClient.list({ limit: 20 })); + if (error && isDefinedError(error)) { + throw new Error(`users.list failed: ${error.code}`); + } + return result.items; +} +``` + +```bash +# From the workspace root, in order: + +# 1. Declare the dependency — add "users" to the caller's ServiceReferences +# array in aspire/appsettings.json (see Step 1 above). + +# 2. Regenerate the Aspire helpers so the reference is wired into the AppHost. +netscript service generate + +# 3. Bring the graph up — Aspire injects services__users__http__0 into the caller. +cd aspire && aspire start && cd .. + +# 4. Run the caller; the typed client resolves the URL lazily on the first call. +deno task --cwd web dev +``` + +With `aspire start` up, `usersClient.list({ limit: 20 })` resolves +`services__users__http__0`, issues the oRPC call to `/api/rpc/v1/users`, and returns +the contract's typed `items`. No registry, no `localhost:`, no generated client file. + ## Production pitfalls {{ comp callout { type: "warning", title: "Footguns before you ship" } }} From f846b973f00b2bcd3d9dab89a261091d826babd7 Mon Sep 17 00:00:00 2001 From: Rickylabs Date: Sun, 12 Jul 2026 00:05:02 +0200 Subject: [PATCH 4/5] docs: add complete route-protection example to better-auth-plugins (#660 #12) A real Fresh routes/*.tsx handler gated on an active session and an admin role, at sibling auth.md's depth: resolves the session via backend.sessions.getSession, maps to a Principal with principalMapper, and fails closed on both checks. Grounded in packages/auth-better-auth + @netscript/service/auth exports. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01VifzAytcxBk5odEMaCXihM --- .../identity-access/better-auth-plugins.md | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/docs/site/identity-access/better-auth-plugins.md b/docs/site/identity-access/better-auth-plugins.md index c756cbf13..ddcd86495 100644 --- a/docs/site/identity-access/better-auth-plugins.md +++ b/docs/site/identity-access/better-auth-plugins.md @@ -82,6 +82,82 @@ interactive-flow seam for the better-auth backend is tracked on the roadmap. {{ /comp }} +## Protect a route with the resolved session + +`createBetterAuthBackend` returns an `AuthBackendPort`, and the piece a page or API route needs is +`backend.sessions.getSession({ request })`: it validates the request's better-auth session cookie +through `auth.api.getSession` and returns the normalized `AuthSession` (or `undefined`). Map that +session to a `Principal` with `backend.principalMapper.mapSessionToPrincipal(session)` and you have +the scopes and roles to authorize on. The example below gates a Fresh route on an **active** session +and an `admin` role, failing closed on both. + +```ts +// server/auth-backend.ts — one shared backend instance for the app +import { auth } from './better-auth.ts'; // your createNetscriptBetterAuth(...) instance +import { createBetterAuthBackend } from '@netscript/auth-better-auth'; + +export const backend = createBetterAuthBackend({ + auth, + sessionTokenSecret: Deno.env.get('BETTER_AUTH_SECRET')!, +}); +``` + +```tsx +// routes/admin/index.tsx — a page that only an authenticated admin can load +import { HttpError } from 'fresh'; +import type { AuthnRequest } from '@netscript/service/auth'; +import { define } from '@/utils/state.ts'; +import { backend } from '../../server/auth-backend.ts'; + +// Adapt the Fresh Request into the AuthnRequest the backend port reads. +function toAuthnRequest(req: Request): AuthnRequest { + const url = new URL(req.url); + return { + header: (name) => req.headers.get(name) ?? undefined, + headers: () => req.headers, + cookie: (name) => + req.headers.get('cookie') + ?.split('; ') + .find((c) => c.startsWith(`${name}=`)) + ?.slice(name.length + 1), + method: req.method, + path: url.pathname, + }; +} + +export const handler = define.handlers(async (ctx) => { + // Resolve the session from the request's better-auth cookie. + const session = await backend.sessions.getSession({ + request: toAuthnRequest(ctx.req), + }); + + // Fail closed: no active session → redirect to sign-in, never render anonymously. + if (!session || session.state !== 'active') { + return ctx.redirect('/api/v1/auth/signin'); + } + + // Map to a NetScript Principal and authorize on its roles. + const { principal } = backend.principalMapper.mapSessionToPrincipal(session); + if (!principal.roles.includes('admin')) { + throw new HttpError(403); + } + + return { data: { subject: principal.subject, roles: principal.roles } }; +}); + +export default define.page(({ data }) => ( +
+

Admin dashboard

+

Signed in as {data.subject} — roles: {data.roles.join(', ')}

+
+)); +``` + +Because `getSession` returns a typed `AuthSession | undefined` and the state check is explicit, the +route cannot silently degrade to an anonymous render — the same fail-loud discipline the +[authentication](/identity-access/auth/) page describes for the backend port. A machine-to-machine +API route follows the identical shape: swap the redirect for `throw new HttpError(401)`. + ## Where to go next - {{ comp.xref({ key: "explain:auth-model" }) }} — how Principals, sessions, and backends fit From e3553e451d0648c938001a284a73ac21529dc291 Mon Sep 17 00:00:00 2001 From: Rickylabs Date: Sun, 12 Jul 2026 00:06:00 +0200 Subject: [PATCH 5/5] chore(run): docs-660b sweep worklog (#660) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01VifzAytcxBk5odEMaCXihM --- .llm/runs/docs-660b-sweep--opus/worklog.md | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .llm/runs/docs-660b-sweep--opus/worklog.md diff --git a/.llm/runs/docs-660b-sweep--opus/worklog.md b/.llm/runs/docs-660b-sweep--opus/worklog.md new file mode 100644 index 000000000..f038f7675 --- /dev/null +++ b/.llm/runs/docs-660b-sweep--opus/worklog.md @@ -0,0 +1,58 @@ +# Worklog — docs-660b how-to + reference + pillar sweep (Opus 4.8) + +Issue #660 slice B. Proposals #7, #8, #9, #10, #11, #12, #15 from `.llm/runs/docs-audit-662--sonnet/report.md`. + +## Grounding / evidence + +- **PORT/config (#7).** `packages/config/src/domain/schemas/service-schema.ts:11` types `port: z.number()`; `netscript-config-schema.ts:142,144` expose `services`/`apps` records. Scaffold entrypoints read `parseInt(Deno.env.get('PORT') || '')` (`packages/cli/src/kernel/assets/embedded.generated.ts`), and the scaffolder writes the same value into config (`adapters/service/scaffolder.ts:115 port: options.servicePort`). Claim verified: Aspire injects PORT at runtime; `services..port` (apps: `apps..port`) is the typed source of truth. +- **#11 queue APIs.** `packages/queue/factory/{create-typed-queue,create-queue,create-parallel-queue}.ts`; `QueueProvider` enum `ports/options.ts:14`; `enqueue`/`listen` on `ports/message-queue.ts`. All cited symbols exist. +- **#11 workers executor.** `createDefaultTaskExecutor` + `TaskRuntimeAdapterLike` in `packages/plugin-workers-core/src/executor/` (import path `@netscript/plugin-workers-core/executor`). +- **#12 better-auth route guard.** `createBetterAuthBackend` returns `AuthBackendPort` with `sessions.getSession({ request })` and `principalMapper.mapSessionToPrincipal` (`packages/auth-better-auth/src/better-auth-backend.ts`). `AuthnRequest` exported from `@netscript/service/auth` (`packages/service/src/auth/mod.ts:53`); `AuthSession.state`/`Principal.roles` per auth.md tables. + +## Per-page changes + +(appended as slices land) + +### #7 — PORT / typed-config consistency (8 pages) +One shared sentence at the first `parseInt(Deno.env.get('PORT')...)` occurrence per page: +`how-to/expose-openapi-scalar.md`, `how-to/add-a-service.md`, `tutorials/storefront/02-catalog-service.md`, +`tutorials/live-dashboard/02-contract-to-service.md`, `services-sdk/services.md`, `explanation/contracts.md`, +`how-to/add-opentelemetry.md` — all use `services..port`; `how-to/customize-fresh-ui.md` uses +`apps..port` (Fresh app). Commit 1. + +### #8 — reference/ai/index.md +Added a block-quote note: AI provider keys have no typed config surface (by design); raw +`Deno.env.get` is the supported path, secrets injected by the runtime env. Commit 2. + +### #9 — reference/queue/index.md + reference/kv/index.md +"See it live" link blocks (how-to/queue-kv-cron, how-to/choose-a-queue-provider, data-persistence +concept) before the reference-overview footer. Commit 2. + +### #10 — reference/contracts/index.md + reference/sdk/index.md +Contracts lede: contracts obviate manual `req.json()` validation (rejected before handler runs). +SDK lede: `defineServices()` wires clients + query factories + query utils in one call. Commit 2. + +### #15 — durable-workflows/streams.md +One sentence reframing "no in-process subscribe()" as an intentional single HTTP/SSE surface shared +by browser and server consumers. Commit 2. + +### #11 — three decision how-tos +Runnable end-to-end block (resulting file + full command sequence) per page at deploy-local-aspire +density: `discover-services.md` (declare ref → service generate → aspire start → typed client), +`choose-a-queue-provider.md` (same call site on Deno KV then Redis), `add-a-task-runtime-adapter.md` +(node task file + runner + `deno run` + expected output). Commit 3. + +### #12 — identity-access/better-auth-plugins.md +Complete `routes/admin/index.tsx` handler gated on active session + admin role via +`backend.sessions.getSession` / `principalMapper.mapSessionToPrincipal`, fail-closed. Commit 4. + +## Validation +- Grep gate (public-docs law) on all 18 touched files: 0 hits. +- `deno task verify` from docs/site: build 512 files; check:links 23456 links across **169 pages** + all resolve; check:caveats 27 markers across 22 pages all resolve. 169 ≈ expected ~169 — base not stale. + +## Commits +1. PORT/typed-config sweep (#7) — 8 pages +2. reference showcase/links + streams (#8 #9 #10 #15) — 6 pages +3. end-to-end how-to blocks (#11) — 3 pages +4. better-auth route guard (#12) — 1 page