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
43 changes: 43 additions & 0 deletions .llm/runs/beta6-nondash--supervisor/context-pack-258.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Slice FB5 / Issue #258 Context Pack

Status: A1 FAIL_FIX implemented, awaiting Tier-A re-review, adversarial review, and IMPL-EVAL.

Branch: `feat/258-fresh-ui-genui-renderer`

Baseline: `origin/main` at `b5d09693`.

Implemented:

- `packages/fresh-ui/src/ai/render-ui.tsx` safe recursive renderer.
- `packages/fresh-ui/tests/ai/render-ui.test.tsx` depth, nested-array depth, whitelist, and
happy-path regressions.
- `packages/fresh-ui/deno.json` subpath export `./ai/render-ui`.
- `packages/fresh-ui/registry.manifest.ts` item `render-ui`, appended to existing `ai` collection.
- `packages/fresh-ui/registry.generated.ts` regenerated registry content.

Key constraints honored:

- Consumes `RenderUiToolInput` from `@netscript/ai/tools`; does not redefine the E4 input contract.
- Root `mod.ts` unchanged.
- No new `as` casts in the new renderer or test.
- No raw HTML / `dangerouslySetInnerHTML` path.
- No `deno.lock` drift.
- A1 nested-array bypass fixed: array recursion now advances to `depth + 1`, so nested arrays in
unvalidated `props` interiors are bounded by `RENDER_UI_MAX_DEPTH`.

Validation summary:

- Wrapper check/lint/fmt for `packages/fresh-ui`: green.
- Focused renderer guard tests: green, 4 passed.
- `deno test --allow-all packages/fresh-ui/tests/`: green, 133 passed.
- `deno publish --dry-run --allow-dirty` from `packages/fresh-ui`: green without
`--allow-slow-types`.
- Touched renderer doc-lint: totalErrors=0.
- Full fresh-ui doc-lint still reports existing interactive runtime debt; renderer entrypoint is clean.
- `ui:add ai` scratch smoke: installs and copies renderer; copied-file type-check blocked by
unpublished `@netscript/ai@^0.0.1-beta.5` JSR availability.

Deferred:

- Full `scaffold.runtime` E2E coverage for generated-project render assertions. Follow-up issue
filed as #564: `test(cli-e2e): scaffold.runtime coverage for fresh-ui ai generative-ui renderer`.
22 changes: 22 additions & 0 deletions .llm/runs/beta6-nondash--supervisor/drift-258.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Slice FB5 / Issue #258 Drift

## 2026-07-07

- Severity: minor.
- The run directory on `origin/main` contains prior evaluator files only, not the full supervisor
artifact set. This slice records per-slice evidence in `worklog-258.md` and `context-pack-258.md`
rather than inventing a full supervisor plan artifact.

## 2026-07-07

- Severity: minor.
- `deno task doc:lint --root packages/fresh-ui --pretty` still reports existing public-surface debt
from `interactive.ts` (123 total errors). The new renderer entrypoint is clean under the wrapper's
explicit entrypoint mode (`totalErrors=0`), so the existing debt is not expanded in this slice.

## 2026-07-07

- Severity: minor.
- `ui:add ai` installs the renderer in a scratch project, but `deno check` of the copied file cannot
resolve `jsr:@netscript/ai@^0.0.1-beta.5` because that version is unavailable from JSR in this
environment. The copy path itself succeeded and copied `lib/ai/render-ui.tsx`.
88 changes: 88 additions & 0 deletions .llm/runs/beta6-nondash--supervisor/evaluate-258.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# IMPL-EVAL: FB5 render_ui generative-UI renderer (issue #258)

**Branch:** `feat/258-fresh-ui-genui-renderer`
**Base:** `b5d09693`
**Slice commits:** `92465bfa` (renderer) + `fe05c814` (depth-guard fix)
**Evaluator role:** IMPL-EVAL (independent re-attack)

---

## Decisive Checks

### 1. Security — Depth Guard (ADVERSARIAL) ✅ PASS

**File:** `packages/fresh-ui/src/ai/render-ui.tsx`
**Constant:** `RENDER_UI_MAX_DEPTH = 6` (line 13)

**All recursive descent paths verified:**

| Descent Path | Depth Increment | Line |
|-------------------------------|----------------:|-----:|
| `renderChildren` → `renderNode` children | `depth + 1` | 344 |
| `renderDataBlock` list items | `depth + 1` | 290 |
| `renderNode` **array branch** | `depth + 1` | **163 (A1 fix)** |
| Object branch → `renderBlock` | same depth | 174 (guarded at 184) |

**Adversarial payload trace (50 array nestings):**
- `renderBlock` depth=0 → `renderChildren` → `renderNode` depth=1
- Array branch fires 6×: depth 1→2→3→4→5→6→7
- At depth=7 > maxDepth=6 → `renderFallback('max-depth')` ✅ **Bounded**
- **No descent path can recurse without incrementing depth.**

### 2. Security — Whitelist / XSS ✅ PASS

- `BLOCK_TYPES` hardcoded set: `stack, grid, section, chart, metric, table, list, card`
- Unknown types → `renderFallback('unknown-type')` (static div, no raw markup)
- **No `dangerouslySetInnerHTML`, no `__html`, no raw markup path**
- All user strings rendered as JSX text children (auto-escaped by Preact)
- Test 4 confirms `<script>alert("owned")</script><img src=x onerror=alert(1)>` NOT in output.

### 3. Contract Seam ✅ PASS

- **Line 7:** `import type { RenderUiToolInput } from '@netscript/ai/tools'` ✅
- Registry manifest has `render-ui` in `'ai'` collection ✅
- `registry.generated.ts` includes `RenderUiSurface` ✅

### 4. Tests + Gates + Hygiene ✅ PASS

| Gate | Result |
|-------------------------------|---------------------------------------------|
| `deno test` (4 cases) | ✅ 4 passed / 0 failed |
| `run-deno-doc-lint` (renderer)| ✅ totalErrors=0 for touched exports |
| `deno publish --dry-run` | ✅ exit 0, no `--allow-slow-types` |
| `deno task arch:check` | ✅ exit 0, warnings only, no FAIL |
| `git diff deno.lock` | ✅ empty (no lock churn) |
| `as` casts | ✅ 0 casts (well under 2-accepted budget) |

### 5. Optional: Pre-fix Array Branch Regression Confirmed ✅ PASS

| Commit | Array Branch Code | Depth Guard? |
|----------|----------------------------------|-------------:|
| `92465bfa` (pre-fix) | `renderNode(child, depth, context)` | ❌ **Bypass** (same depth) |
| `fe05c814` (fix) | `renderNode(child, depth + 1, context)` | ✅ Incremented |

The fix correctly increments depth on array descent, closing the bypass.

---

## PR Body Close-Gate Compliance

- ✅ `Closes #258` in body (closing keyword present)
- ✅ `gate:jsr` label present (doc-lint + publish dry-run evidence provided)
- ✅ `gate:e2e` **intentionally unchecked** (deferred to #564, noted in drift)
- ✅ Labels correct: `status:impl`, `wave:v1`, `area:fresh-ui`, `type:feat`, `epic:ai-stack`, `priority:p1`
- ✅ Milestone: `0.0.1-beta.6`

---

## Verdict

All decisive checks pass. The renderer is a robust security boundary:

1. **Depth guard is complete** — all three recursive paths increment depth.
2. **Whitelist is closed** — no arbitrary element creation, no raw HTML path.
3. **Contract seam is clean** — consumes E4 input, extends existing registry.
4. **Gates are green** — tests, doc-lint, publish, architecture, lock hygiene.
5. **A1 fix verified** — pre-fix bypass confirmed, post-fix guard confirmed.

OPENHANDS_VERDICT: PASS
114 changes: 114 additions & 0 deletions .llm/runs/beta6-nondash--supervisor/worklog-258.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Slice FB5 / Issue #258 Worklog

## Design

Public surface:

- `@netscript/fresh-ui/ai/render-ui` subpath exporting `renderUiPayload`, `RenderUiSurface`,
`RENDER_UI_MAX_DEPTH`, the curated block vocabulary types, and the consumed
`RenderUiToolInput` type from `@netscript/ai/tools`.
- Root `mod.ts` remains unchanged to avoid expanding the existing root export count.

Domain vocabulary:

- Input contract is `RenderUiToolInput` from `@netscript/ai/tools`: `{ component, props?, title? }`.
- Recursive payload nodes are read from `props.children` / `props.items` / `props.content` and must
carry `type` or `component`.
- Exactly three categories are whitelisted: `layout`, `viz`, and `data`.
- Whitelisted block types are `stack`, `grid`, `section`, `chart`, `metric`, `table`, `list`, and
`card`.

Ports:

- None. The renderer is deterministic Preact DOM projection and has no adapter or IO dependency.

Constants:

- `RENDER_UI_MAX_DEPTH = 6`.
- `RENDER_UI_BLOCK_CATEGORIES` is the only accepted vocabulary table.

Commit slices:

- FB5: safe renderer + registry entry + guard tests + harness evidence.

Deferred scope:

- Provider-specific tool-call wiring stays out of scope.
- Full `scaffold.runtime` E2E coverage is deferred to follow-up issue #564 because it requires a
generated-project scenario beyond this unit renderer slice.

Contributor path:

- Add a new curated block by editing `RENDER_UI_BLOCK_CATEGORIES`, adding one renderer branch in
`src/ai/render-ui.tsx`, and extending `tests/ai/render-ui.test.tsx`.

## Implementation Notes

- Added `packages/fresh-ui/src/ai/render-ui.tsx`.
- Added `packages/fresh-ui/tests/ai/render-ui.test.tsx`.
- Added `./ai/render-ui` to `packages/fresh-ui/deno.json`.
- Added `render-ui` registry item and appended it to the existing `ai` collection.
- Regenerated `packages/fresh-ui/registry.generated.ts`.

Security guard regressions:

- Depth guard: payloads beyond `RENDER_UI_MAX_DEPTH` render
`data-render-ui-fallback="max-depth"`.
- Whitelist guard: unknown block types render `data-render-ui-fallback="unknown-type"` and the
fallback carries no raw payload markup.
- Raw HTML path: tests assert no `dangerouslySetInnerHTML` or `__html` appears in the renderer
source.

## Gate Evidence

| Gate | Command | Raw result |
| --- | --- | --- |
| E4 surface read | `deno doc --filter RenderUiToolInput packages/ai/tools.ts` | exit 0; `RenderUiToolInput` has `component`, `props?`, `title?` |
| E4 tool read | `deno doc --filter renderUiTool packages/ai/tools.ts` | exit 0; tool is client-deferred `render_ui` |
| Focused check | `deno check --unstable-kv packages/fresh-ui/src/ai/render-ui.tsx packages/fresh-ui/tests/ai/render-ui.test.tsx` | exit 0 |
| Guard tests | `deno test --allow-read --unstable-kv packages/fresh-ui/tests/ai/render-ui.test.tsx` | exit 0; 3 passed / 0 failed |
| Wrapper check | `deno run --allow-read --allow-run .llm/tools/run-deno-check.ts --root packages/fresh-ui --ext ts,tsx` | exit 0; filesSelected=128; failedBatches=0; totalOccurrences=0 |
| Wrapper lint | `deno run --allow-read --allow-run .llm/tools/run-deno-lint.ts --root packages/fresh-ui --ext ts,tsx` | exit 0; filesSelected=128; totalOccurrences=0 |
| Wrapper fmt | `deno run --allow-read --allow-run .llm/tools/run-deno-fmt.ts --root packages/fresh-ui --ext ts,tsx --ignore-line-endings` | exit 0; filesSelected=128; failedBatches=0 |
| Fresh UI tests | `deno test --allow-all packages/fresh-ui/tests/` | exit 0; 132 passed / 0 failed |
| F-3 | `rtk proxy deno task arch:check` | exit 0; warnings only, no FAIL rows |
| Touched doc-lint | `deno run --allow-read --allow-run .llm/tools/run-deno-doc-lint.ts --root packages/fresh-ui --entrypoints ./src/ai/render-ui.tsx --pretty` | exit 0; totalErrors=0 |
| Full fresh-ui doc-lint | `deno task doc:lint --root packages/fresh-ui --pretty` | exit 0 wrapper; existing `interactive.ts` debt remains 123 total; new renderer entrypoint total=0 |
| Publish dry-run | `deno publish --dry-run --allow-dirty` from `packages/fresh-ui` | exit 0; no `--allow-slow-types`; dry run complete |
| Registry smoke | `deno run -A packages/cli/bin/netscript-dev.ts ui:add ai --project-root .llm/tmp/fresh-ui-render-ui-smoke --registry-root packages/fresh-ui --force` | exit 0; installed 25 items; copied 47 files; renderer copied to `lib/ai/render-ui.tsx` |
| Copied renderer check | `deno check --unstable-kv lib/ai/render-ui.tsx` from scratch smoke root | exit 1; blocked by unpublished `@netscript/ai@^0.0.1-beta.5` on JSR, not by copy rewrite |

## Reconcile

- Issue #258 should be closed by this PR once Tier-A review, adversarial review, and IMPL-EVAL pass.
- Epic #238 is referenced only as `Part of #238`; no closing keyword.
- `gate:e2e` remains deferred to #564 and must not be checked on #258 in this slice.

## A1 FAIL_FIX — Nested Array Depth Bypass

Finding:

- A1 found that `renderNode()` rendered arrays at the same depth, so nested arrays inside the
unvalidated `props` interior could bypass `RENDER_UI_MAX_DEPTH` and overflow the JS call stack.

Fix:

- Array children now recurse with `depth + 1`.
- The first guard in `renderNode()` still short-circuits to `renderFallback('max-depth')` once the
bound is crossed.
- Added a non-deferrable regression that feeds a 50-deep nested array through `props.children` and
asserts the serialized tree contains `data-render-ui-fallback="max-depth"`.

### A1 FAIL_FIX Gate Evidence

| Gate | Command | Raw result |
| --- | --- | --- |
| Focused check | `deno check --unstable-kv packages/fresh-ui/src/ai/render-ui.tsx packages/fresh-ui/tests/ai/render-ui.test.tsx` | exit 0 |
| Guard tests | `deno test --allow-read --unstable-kv packages/fresh-ui/tests/ai/render-ui.test.tsx` | exit 0; 4 passed / 0 failed |
| Wrapper check | `deno run --allow-read --allow-run .llm/tools/run-deno-check.ts --root packages/fresh-ui --ext ts,tsx` | exit 0; filesSelected=128; failedBatches=0; totalOccurrences=0 |
| Wrapper lint | `deno run --allow-read --allow-run .llm/tools/run-deno-lint.ts --root packages/fresh-ui --ext ts,tsx` | exit 0; filesSelected=128; totalOccurrences=0 |
| Wrapper fmt | `deno run --allow-read --allow-run .llm/tools/run-deno-fmt.ts --root packages/fresh-ui --ext ts,tsx --ignore-line-endings` | exit 0; filesSelected=128; failedBatches=0 |
| Touched doc-lint | `deno run --allow-read --allow-run .llm/tools/run-deno-doc-lint.ts --root packages/fresh-ui --entrypoints ./src/ai/render-ui.tsx --pretty` | exit 0; totalErrors=0 |
| Publish dry-run | `deno publish --dry-run --allow-dirty` from `packages/fresh-ui` | exit 0; no `--allow-slow-types`; dry run complete |
| F-3 | `rtk proxy deno task arch:check` | exit 0; warnings only, no FAIL rows |
| Fresh UI tests | `deno test --allow-all packages/fresh-ui/tests/` | exit 0; 133 passed / 0 failed |
1 change: 1 addition & 0 deletions packages/fresh-ui/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"exclude": [],
"exports": {
".": "./mod.ts",
"./ai/render-ui": "./src/ai/render-ui.tsx",
"./interactive": "./interactive.ts",
"./primitives": "./primitives.tsx",
"./registry": "./registry.ts"
Expand Down
5 changes: 4 additions & 1 deletion packages/fresh-ui/registry.generated.ts

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions packages/fresh-ui/registry.manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,29 @@ export const freshUiRegistryManifest: RegistryManifest = {
registryDependencies: ['theme-seed'],
css: [{ layer: 'components', content: "@import './ui/mcp-ui-widget.css';" }],
},
{
name: 'render-ui',
kind: 'lib',
layer: 2,
description:
'Safe recursive render_ui payload renderer with depth and block-vocabulary guards for Fresh UI generative surfaces.',
copyOwnership: 'app-owned-after-copy',
author: 'NetScript',
tags: ['ai', 'generative-ui', 'renderer', 'security'],
files: [{
source: 'src/ai/render-ui.tsx',
target: '@lib/ai/render-ui.tsx',
}],
registryDependencies: [
'theme-seed',
'layout-objects',
'chart-block',
'stats-grid',
'data-table',
'card',
],
dependencies: ['jsr:@netscript/ai@^0.0.1-beta.5', 'npm:preact@^10.29.2'],
},
],
collections: [
{
Expand Down Expand Up @@ -1261,6 +1284,7 @@ export const freshUiRegistryManifest: RegistryManifest = {
'markdown',
'chat-render',
'mcp-ui-widget',
'render-ui',
],
},
{
Expand Down
Loading
Loading