diff --git a/.llm/2026-08-08-fable5-remediation-seed-run.md b/.llm/2026-08-08-fable5-remediation-seed-run.md new file mode 100644 index 0000000000..8b81a1d5c2 --- /dev/null +++ b/.llm/2026-08-08-fable5-remediation-seed-run.md @@ -0,0 +1,16 @@ +# 2026-08-08 — Fable 5 remediation roadmap seed run (session record) + +Run: `.llm/runs/plan-fable5-remediation-roadmap--seed/` · PR #1347 (draft, never merges itself) +· branch `plan/fable5-remediation-roadmap` · baseline `fac9e339042c` (unchanged at lock). + +One-session planning-only seed run (Fable 5 · high supervisor; PLAN-EVAL/IMPL-EVAL owner-waived; +Claude Workflows with 25 Opus 5 research/drafting subagents across 3 pre-committed workflow +scripts, 0 errors, ~3.8M subagent tokens). Produced the complete long-range remediation plan +under `fable-5-remediation-plan/`: 19-artifact cited corpus, synthesis, master plan with a +12-fork owner sweep, milestone train (two inserted milestones via house rename), 41 issue +drafts in three milestone directories, RFC-A/RFC-B drafts, 16 amendment blocks, Wave-7 design, +implementation handoff. Zero GitHub board mutation; owner ratifies filing later. + +Lessons worth promoting (candidates, not promoted here): (1) drafting agents that re-verify +corpus claims at source caught six corrections a prose-only pass would have shipped; (2) the +"commit workflow scripts before execution" rule made a 25-agent run auditable from the PR alone. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/context-pack.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/context-pack.md new file mode 100644 index 0000000000..deddeea9ae --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/context-pack.md @@ -0,0 +1,32 @@ +# Context Pack — plan-fable5-remediation-roadmap--seed + +## Status: COMPLETE (Stage H owner-ratified filing, 2026-08-08). + +Planning-only seed run; all charter deliverables landed under +`.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/`: + +- `MASTER-PLAN.md` — product bar, principles, target architecture, DAG, sequencing, **12-fork + owner sweep (§7)**, risks, exclusions. +- `ISSUE-DEDUP-AND-SUPERSESSION.md` — dispositions for every relevant existing issue; 41 new + drafts tabulated; Stage-E ledger (§3) with six corpus corrections + ownerless items. +- `MILESTONE-TRAIN.md` — two-milestone insertion via house rename; complete move ledger. +- `milestones/{0.0.6,0.0.7,0.0.8}-*/` — 41 complete issue drafts (all DRAFT-marked). +- `rfcs/RFC-A-sdk-client-composition.md`, `rfcs/RFC-B-command-composition-kit.md`. +- `EXISTING-ISSUE-AMENDMENTS.md` — 16 quotable amendment blocks. +- `WAVE7-AND-AGENT-ADOPTION.md` — A/B/C measured smoke design; T7-01 exit-gate draft. +- `IMPLEMENTATION-HANDOFF.md` — filing gate, first PR groups A–D, harness profiles, brief + skeleton. +- `SYNTHESIS.md` + `research/` — 19-artifact cited corpus (waves, board, repo, external). + +## Key facts a resuming session must not re-derive + +- Draft PR **#1347** is the commit trail; labels moved research→plan at lock. Never merge it. +- PLAN-EVAL + IMPL-EVAL **owner-waived** (drift D-2); no evaluator was launched; the waiver does + NOT extend to future implementation runs. +- **GitHub filing completed after owner ratification.** The live board is authoritative; see + `fable-5-remediation-plan/FILING-LOG.md` for milestone and issue receipts. +- Baseline `fac9e339042c` verified unchanged at lock. Workflows (committed pre-execution): + `wf_e2194004-808`, `wf_03b88126-e7e`, `wf_ebfe8327-306` — 25 Opus 5 subagents total, 0 errors. +- Six corpus corrections (dedup §3.1) override the raw research files where they conflict. +- 41 live issues: #1348–#1388. RFC entry points: #1348 and #1361. Implementation sequencing + remains forks F1–F12 in `MASTER-PLAN.md` §7. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/drift.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/drift.md new file mode 100644 index 0000000000..26a217a8bd --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/drift.md @@ -0,0 +1,39 @@ +# Drift Log — plan-fable5-remediation-roadmap--seed + +Append-only. Newest last. + +## D-1 (2026-08-08, minor, owner directive) + +Owner overrides `planning_decisions` effort from lane-policy default **low** to **high** for this +run (long-range meta-framework roadmap). Recorded in `supervisor.md` § overrides. + +## D-2 (2026-08-08, significant, owner directive) + +Owner explicitly **waives PLAN-EVAL and IMPL-EVAL** for this planning-only seed run. The seed-run +stage G hard stop (`plan-eval.md = PASS` before any board mutation) is vacuously safe because the +run performs **zero board mutation** by charter; the owner personally reviews the plan and decides +on later adversarial passes and filing. No formal evaluator, OpenHands, OpenRouter, or substitute +evaluator session is launched. Recorded in `supervisor.md` and `worklog.md`. + +## D-3 (2026-08-08, minor, owner directive) + +Claude Workflows for research/synthesis fan-out run **Opus 5** subagents (owner directive), +overriding the `claude_workflow` lane default (Opus 4.8 · low). Contributors only, never +evaluators; supervisor reviews all output before commit. This also invokes the CLAUDE.md +documentation-authoring-adjacent exception: the workflow lane touches **no `packages/`/`plugins/` +source** — output is run-dir planning artifacts only. + +## D-4 (2026-08-08, minor, charter deviation from seed-run exemplar) + +Stage F (adversarial) and stages G–H are not executed in this run by owner direction. The run ends +at plan-lock + handoff artifacts (stages A–E + I equivalent). The deliverable set is the +`fable-5-remediation-plan/` subtree requested in the charter, which supersedes the exemplar's +folder taxonomy (permitted — seed-run.md fixes stage contracts, not folder names). + +## D-5 (2026-08-08, significant, owner ratification) + +The owner subsequently ratified the locked plan in-turn and explicitly instructed Stage-H board +filing. This supersedes D-4 only for Stage H: `FILING-MANIFEST.md` was committed before mutation, +the milestone train was executed, 41 issues were filed, and existing-owner amendments were added. +PLAN-EVAL and IMPL-EVAL remain owner-waived for this planning/filing run; the waiver does not carry +into implementation. See `fable-5-remediation-plan/FILING-LOG.md`. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/CROSS-RFC-REVIEW.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/CROSS-RFC-REVIEW.md new file mode 100644 index 0000000000..255fb7587c --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/CROSS-RFC-REVIEW.md @@ -0,0 +1,114 @@ +# Cross-RFC PLAN-EVAL review — RFC-A (#1390) × RFC-B (#1389) + +## CYCLE 2 OUTCOME (2026-08-08): **BOTH APPROVED / PASS** — handoff to root for the Qwen pass + +Same evaluator session re-evaluated every cycle-1 finding mechanically against the remediated +texts and worktree source; independent evidence executed (RFC-A fixture `deno check` exit 0; +section-level anchor verification for all 17 findings). + +| RFC | Accepted content SHA | Branch HEAD at verdict | Verdict artifact | +| --- | --- | --- | --- | +| RFC-A (#1390) | **`78a7cecd1d5eaafa7a65bc25a21af497567128dc`** | `14b5c858c` (atop author hygiene `9f45404ac` — run artifacts only, verified) | `plan-eval.md` @ `14b5c858c` | +| RFC-B (#1389) | **`c98c08adabbd992a557ff7c596deae68b9c9cd62`** | `57b51128f` (atop author handoff `6c2043d91`) | `plan-eval.md` @ `57b51128f` | + +All F-A1–F-A10 and F-B1–F-B7 resolved (several beyond the asked bar: RFC-A's epoch/reconnect law +with a preparation-count-2 fixture; RFC-B's per-provider claim-algorithm table). Lane audit: +`type:test` added to #1390 (compile-only fixture in `packages/sdk/tests/`); `ci:skip-*` ruled +still valid on both. Both PRs moved `status:plan-eval` → `status:augment-review`. + +**Qwen 3.8 Max adversarial acceptance brief (root-owned, separately authorized):** evaluate both +RFCs at the accepted content SHAs above, unoriented; the two `plan-eval.md` files carry the full +cycle-1 findings + cycle-2 resolution evidence; the remaining decision surface is the FCP +question sets (8 in RFC-A, 4 in RFC-B — all adjudicated policy-safe) plus the cross-RFC +obligations below (§2 amendments, §3 v2-migration epic). Unresolved-by-design: RFC-A FCP Q5 +wrapper-vs-memo, Q6 GET preserve-vs-retire (v2 RFC), metadata Stage 1b ownership; RFC-B FCP +Q1–Q4. One cosmetic cross-RFC inconsistency remains (frontmatter `target-milestone` convention — +harmonize at numbering). No board object was created/mutated by this evaluator in either cycle. + +The cycle-1 record below is retained for provenance; its "stop" handoff is superseded. + +Evaluator: Claude Fable 5 · high, owner-designated cross-family PLAN-EVAL authority +(2026-08-08). Verdicts of record: `plan-eval.md` in each RFC run dir — **both +CHANGES_REQUESTED (FAIL_PLAN cycle 1)**: RFC-A @ `f1a29fe1a` (F-A1–F-A10), RFC-B @ `122301d25` +(F-B1–F-B7). Deep-dive delegations: workflow `wf_b3416478-edf` (3× Opus 5 · xhigh, read-only, +script committed pre-execution in this run's `workflows/`). This artifact records the +cross-RFC obligations; it performs **no board mutation**. + +## 1. Composition verdict: the two RFCs compose cleanly + +- **No circular dependency.** Shared prerequisite #1350 (0.0.7) is one-way: RFC-A stage 1 and + RFC-B stage 0 both consume it; neither RFC depends on the other's implementation. RFC-B's + service→database edge is new but acyclic (F-B2 requires it be *declared*). +- **No duplicated error policy.** RFC-A's preparation failures are local, pre-dispatch, never + contract errors; RFC-B's command errors are route-opt-in contract errors. Both defer + client-visible typing to #1350's literal-preserving spelling. Coherent — with one shared + obligation: whichever lands first establishes that spelling; the other must reuse it (F-B7b). +- **No duplicated context/telemetry policy.** RFC-A reserves trace headers to the transport; + RFC-B persists validated W3C context in rows and uses producer/consumer spans — disjoint + layers, consistent with the existing telemetry vocabulary. Both correctly avoid inventing a + second correlation scheme; RFC-B's stricter-than-existing attribute redaction (F-B7h) is a + vocabulary-cleanup question, not a conflict. +- **Shared `(family, major)` protocol vocabulary** (RFC-A ↔ #928) does not leak into RFC-B. +- **One latent intersection to watch at implementation:** RFC-A's `idempotency-key` header + allowance and RFC-B's envelope `idempotencyKey` are different layers (transport header vs + command input). No conflict today; the future HTTP-idempotency recipe should name which one is + authoritative for services that accept both. + +## 2. Sequencing vs the filed board (#1348–#1388, PR #1347) + +Coherent as filed: RFC trackers #1348/#1361 in 0.0.6 (ratification); RFC-A implementation +children #1349–#1353 in 0.0.7; RFC-B children #1362–#1364 (+#1363 umbrella) in 0.0.8 with #1350 +(0.0.7) as stage 0. The milestone descriptions carry the authority banner. Frontmatter +`target-milestone` semantics need one clarifying line in each RFC (RFC-A says 0.0.7 = impl, +RFC-B says 0.0.6 = ratification — pick one convention; F-B7d). + +**Existing children are sufficient — no duplicates needed.** Required amendments (owner-ratified, +not executed by this evaluator): + +- **#1351** — add: `@orpc/opentelemetry` rename decision (already available on v1, F-A8d); + exact-pin vs lock-only-pin policy for the family move (F-A8, caret manifests); the GET-dedupe + no-op trap as an acceptance row (F-A8c). +- **#1349** — add: prepared-header channel statement, private-port location + `deno doc`-absence + gate, server key-algebra surfaces (F-A2/A6/A7) once the amended RFC lands. +- **#1350** — stage-0 body reconciliation (metadata initialization + literal-preserving spelling + shared by both RFCs) — already planned by RFC-A stage 0; keep. +- **#1362/#1363** — inherit F-B2's relay-ownership decision and F-B1's claim algorithm once + amended; #1363's child table gains the `PrismaTransactionClient` generator deliverable (F-B6). +- **New reconciliation candidate (owner decision):** `@netscript/queue`'s runtime `ensureSchema` + DDL vs RFC-B's no-hidden-migrations doctrine (F-B3) — file only if the owner adopts the rule + repo-wide rather than kit-scoped. + +## 3. oRPC v2 migration: separate RFC epic — recommended, with exact scope + +**Recommendation: yes, one new RFC tracking issue (rfc-form, 0.0.6-adjacent ratification, +implementation unscheduled)** after RFC-A acceptance — not filed by this evaluator. Verified +facts anchoring it: v1.15.0 is latest stable (shipped *after* beta.26 — v1 actively maintained); +v2 wire protocol incompatible; 74 non-test files reference `@orpc/*`. + +Scope (RFC-A's gate list + the four evaluator additions): keep-or-drop **GET** decision +(`allowMethods` + Sec-Fetch-Mode CSRF story — direction corrected per F-A8a); re-implement +`inferRPCMethodFromContractRouter` (removed in v2); dedupe-effectiveness gate (GET-only filter); +`defineMeta` migration for `NetScriptProcedureMeta`; `errorStatusMap` split vs #1350 spelling; +middleware-dedup removal audit across the 74 files; OTel span-topology/double-span proof +(package rename excluded — that is #1351, v1); serializer/streaming/Fresh/desktop parity; +TanStack key re-verification; coordinated-rollout vs parallel-endpoints owner decision (RFC-A +Q9); exact-family pinning discipline; full conformance-suite re-run on the v2 adapter. +Dependencies: RFC-A accepted + stages 2–3 landed (the ports are the migration boundary); owner +beta-risk decision; #1351 complete. + +## 4. Handoff state (root orchestrator) + +- **Both verdicts request changes → per the evaluator contract, this session stops here.** The + Qwen 3.8 Max adversarial pass waits until both RFCs reach APPROVED. +- Resume the **RFC-A Codex generator** (thread `019fe242-2bd9-7ff3-8044-bd9d09585397`) with PR + #1390's finding list (F-A1–F-A8 required; F-A9/A10 may ride along). Branch + `docs/rfc-sdk-client-contribution`, HEAD `f1a29fe1a` (evaluator commit atop `7be129d80`). +- Resume the **RFC-B Codex generator** (thread `019fe242-2c45-7e03-a428-eebfb968eda0`) with PR + #1389's list (F-B1–F-B4 required; F-B5–F-B7 as batch edits). Branch + `docs/rfc-command-composition-kit`, HEAD `122301d25` (evaluator commit atop `62304176f`). +- Both PRs moved to `status:plan`; restore `status:plan-eval` with the amended handoffs. Cycle 2 + is the last before escalation (two-FAIL_PLAN limit). +- Evaluator wrote only: the two `plan-eval.md` files (pushed with explicit refspecs to their own + branches), the two PR comments + label moves, this artifact, and the seed-run worklog note. No + RFC text, no product code, no issues/epics/milestones touched. RFC-B's pre-existing dirty + `codex-thread-ids.md` was left untouched. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/EXISTING-ISSUE-AMENDMENTS.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/EXISTING-ISSUE-AMENDMENTS.md new file mode 100644 index 0000000000..89e4c71c91 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/EXISTING-ISSUE-AMENDMENTS.md @@ -0,0 +1,929 @@ +# Existing-issue amendments — quotable text for owner ratification — DRAFT (no GitHub mutation; owner ratification pending) + +Pack T6 deliverable. Every block below is **proposed text the owner can paste verbatim**. Nothing +here has been posted, edited, closed, labelled, or milestoned on GitHub. Each entry states: target +issue, amendment type (comment vs body edit), the full text, the rationale, and what it prevents. + +**Rules honoured by every amendment.** + +- Comments are additive. Where a body edit is proposed it is marked **BODY EDIT** and states which + section is replaced, so nothing is silently deleted. +- No amendment ticks an existing acceptance checkbox. Close-gate reads checkboxes under + `## Acceptance` / `## Acceptance criteria` / `gate:`-prefixed boxes + (`github-conventions.md` §4.3); a comment cannot and must not discharge one. +- No amendment adds a closing keyword to an epic (`AGENTS.md`; conventions §4.7). +- Proposed acceptance boxes are written short and stable so they can be copied verbatim into a + ```acceptance-evidence``` mapping. +- Draft-IDs (`T3-02`, `T4-06`, …) refer to sibling drafts in this seed run. When those are filed the + owner substitutes the real `#N`; until then the draft-ID is a placeholder, not a link. +- Facts marked **re-measured** were executed in this worktree at baseline `fac9e339042c` on + 2026-08-08 and supersede corpus figures dated 2026-08-04. + +Contents: [#1278](#1278) · [#1276](#1276) · [#1279](#1279) · [#1275](#1275) · [#1245](#1245) · +[#1333](#1333) · [#1335](#1335) · [#1210](#1210) · [#1208](#1208) · [#922](#922) · [#301](#301) · +[#1126](#1126) · [#1325/#1326/#1329](#1325-1326-1329) · [#979](#979) · [#1090/#1197](#1090-1197) · +[board hygiene](#board-hygiene) + +--- + + + +## 1. #1278 — Type soundness ratification (0.0.6, `type:umbrella`) + +**Amendment type:** comment (additive). No body rewrite — the inventory A/B/C/D structure stays. + +**Proposed comment text:** + +> **Epic-of-record consolidation + re-measurement (2026-08-08).** +> +> This issue is the epic of record for type soundness. #1276 covers the same 2026-08-04 owner +> directive with a different organisation and is being closed as superseded; its unique content is +> folded in below so nothing is lost. +> +> **Measured numbers folded in from #1276** (as recorded there, 2026-08-04): 56 `as unknown as` +> occurrences across `packages/` + `plugins/`; 8 `deno-lint-ignore no-explicit-any` suppressions; +> 7 ratified `quality:scan` allowances (6 in `packages/cli`, 1 in +> `plugins/workers/streams/producer.ts`); doc line references `web-layer/query-bridge.md:259` +> (compiler refusal printed at 276-277), `reference/contracts/index.md:32`, +> `reference/triggers/index.md:310`. +> +> **Tranches folded in from #1276**, retained as phases of this epic so deferral stays visible: +> T1 public surface first (eliminate `any` from exported types); T2 the documented workarounds +> (#1245 + #1249, docs update lands with the fix); T3 the ratified `quality:scan` allowances +> ("pending package-boundary unification is a plan, not a resting state"); T4 production +> `as unknown as`; T5 test-side casts; T6 keep it fixed (extend `quality:scan`). +> #1276's constraints carry over: framework source runs as WSL Codex slices per `CLAUDE.md`, docs +> updates follow each fix on the docs lane, and "no suppression-as-fix" — a new +> `deno-lint-ignore` / `as unknown as` introduced to green a gate is a review-blocking finding. +> #1276 also names #1255 (`page.layer.delivery` span attribute misreport) as subsumed, alongside +> #1245 and #1249. +> +> **Re-measurement at `fac9e339042c` (2026-08-08).** Four inventory items have moved since the +> 2026-08-04 measurement and this epic should be re-scoped before it is scheduled: +> +> - Inventory A: `web-layer/query-bridge.md` no longer contains `as unknown as`, and +> `BaseContractProcedure = Readonly<{ ~orpc: any }>` no longer appears in +> `reference/contracts/index.md` — grep for `as unknown as` and for `~orpc` returns nothing at +> this baseline. The only remaining docs hit is `reference/triggers/index.md:310` +> `const observedEvents: any[] = [];` and its executable twin +> `docs/site/reference/triggers/examples_test.ts:65`. +> - Inventory B: `packages/fresh/src/application/form/_internal/runtime-types.ts` has **zero** +> matches for `as unknown as` / `as any` / `quality-allow` today; the "2 casts" line item is +> stale. The only remaining cast in the whole web layer is +> `packages/fresh/src/application/builders/define-page/builder/route-support.ts:96`. +> - Inventory C: the allowance count is **7** under the default `quality:scan` roots but **10** +> under `--root packages --root plugins`, so three allowances already sit outside the ratified +> set with nothing reporting the delta. +> - Inventory D: there are **6** soundness test files, not ~19 — +> `packages/plugin-{workers,sagas,triggers,auth,ai}-core/tests/contracts/*-contract-soundness_test.ts` +> and `plugins/workers/services/src/routers/health-soundness_test.ts`. They are already exempt by +> construction: `.llm/tools/quality/scan-code-quality.ts:87` excludes `_test.ts`. +> +> **Correction to a shared assumption.** #1276 T6 states that `quality:scan` "covers +> `packages/cli/src` + `plugins` only". That is true of the default task +> (`scan-code-quality.ts:18`), but `quality:scan:repo` already covers all of `packages/` + +> `plugins/` and runs on push-to-main and a Monday 07:17 UTC cron +> (`.github/workflows/code-quality.yml:50-59`). Root scope is not the gap. The gaps are: the +> `explicit-any` rule cannot tell an exported type from a local one +> (`scan-code-quality.ts:51`); `// quality-allow:` accepts any free-text reason with no issue id +> (`:136`); `--max-allow` exists (`:173-181`) but is passed by no task and no workflow, so the +> allowance budget is unbounded; and no Markdown file is ever opened (`:87`). +> +> **Inventory C is being filed as its own trackable child** so the guard rail can land and be +> gated independently of the burn-down. That child carries `Part of #1278`. + +**Rationale.** #1278 holds the milestone (0.0.6) and the richer acceptance shape; #1276 holds the +only measured numbers and the tranche decomposition. Folding rather than choosing keeps both. The +re-measurement is not optional: three of #1278's own inventory line items no longer describe the +repository, and scheduling against them would produce slices with nothing to fix. + +**What it prevents.** (a) Closing #1276 losing the 56/8/7 counts and the T1–T6 decomposition; +(b) a remediation slice opening `runtime-types.ts` or `query-bridge.md` and finding the work already +done — the #1245 failure mode repeating at epic scale; (c) an implementer building the Inventory C +gate on the false premise that widening roots is the fix. + +--- + + + +## 2. #1276 — epic(quality): ratify and eliminate unsound types (Backlog / Triage) + +**Amendment type:** comment, then owner closes as **not planned** (duplicate). Per +`.github/labels.yml` header rules, a not-planned closure **removes** the `status:` label rather than +setting `status:shipped`. + +**Proposed comment text:** + +> **Closing as superseded by #1278.** +> +> #1278 (`Type soundness ratification`, milestone 0.0.6) and this issue record the same 2026-08-04 +> owner directive over the same evidence set. #1278 is the epic of record because it carries the +> release milestone and the inventory A/B/C/D acceptance shape. +> +> Nothing from this issue is discarded. Its unique content — the measured counts (56 +> `as unknown as`, 8 `deno-lint-ignore no-explicit-any`, 7 ratified `quality:scan` allowances with +> their rationale strings), the exact doc line references, the T1–T6 tranche decomposition, the +> "no suppression-as-fix" constraint, the WSL-Codex lane constraint, and the subsumption of #1245 / +> #1249 / #1255 — has been folded into #1278 verbatim in a comment dated 2026-08-08. The T1–T6 +> tranches survive there as phases so each remains independently deferrable. +> +> Superseded by #1278. No work is lost; track type soundness on #1278. + +**Rationale.** `github-board-open.md` §4.2 confirms the duplication by reading both bodies. Two +umbrellas over one directive means two planning surfaces and two chances to schedule the same slice. + +**What it prevents.** Two parallel type-soundness programs; a slice filed against #1276's T-numbers +that duplicates a slice filed against #1278's letter-numbers. + +--- + + + +## 3. #1279 — docs: migration chapter (0.0.6, `type:umbrella`) + +**Amendment type:** comment (additive), plus a **proposed milestone move** the owner executes +separately. + +**Proposed comment text:** + +> **Epic-of-record consolidation (2026-08-08).** +> +> This issue is the epic of record for the migration chapter. #1275 +> (`epic(docs): migration chapter — migrate-from guides, capability equivalence matrix, and +> end-to-end migration recipes`, Backlog / Triage) is the same scope under different wording and is +> being closed as superseded. Its framing is folded in here so nothing is lost: **migrate-from +> guides**, a **capability equivalence matrix**, and **end-to-end migration recipes** are the same +> three deliverables as this issue's per-framework guides / compatibility matrix / e2e recipes. +> Where the two bodies differ in emphasis, treat #1275's "capability equivalence matrix" as the +> normative name — it states what the matrix is for. +> +> **Proposed milestone change, for owner decision.** Migration documentation is post-remediation +> marketing surface, not remediation. Recommendation: move this issue out of 0.0.6 to the late +> train, so 0.0.6 stays the verification + docs-accuracy + soundness cut. This is a scheduling +> proposal only; no scope changes. + +**Rationale.** `github-board-open.md` §4.2 lists #1275/#1279 as the second confirmed duplicate pair. +The SYNTHESIS milestone-train direction keeps 0.0.6 as the verification/docs/soundness cut; a +migration chapter cannot be written truthfully until the seams it documents stop moving. + +**What it prevents.** A migration guide authored against seams that the typed-seam and generation +milestones are about to change — the most expensive class of docs rework. + +--- + + + +## 4. #1275 — epic(docs): migration chapter (Backlog / Triage) + +**Amendment type:** comment, then owner closes as **not planned** (duplicate); remove the `status:` +label on closure. + +**Proposed comment text:** + +> **Closing as superseded by #1279.** +> +> #1279 (`docs: migration chapter — per-framework guides, compatibility matrix, and e2e migration +> recipes`) is the same scope and carries a release milestone. This issue's three deliverables — +> migrate-from guides, capability equivalence matrix, end-to-end migration recipes — have been +> folded into #1279 in a comment dated 2026-08-08, with "capability equivalence matrix" adopted as +> the normative name for the matrix. +> +> Superseded by #1279. Track the migration chapter there. + +**Rationale + prevention.** Same as #1276/#1278: one prose-only umbrella per topic, or the board +grows two plans for one chapter. + +--- + + + +## 5. #1245 — fix(fresh/query): island query types reject the package's own documented patterns + +**Amendment type:** **BODY EDIT** — replace the three-boundary scope with the remnant — plus a +comment recording why. The body edit is required here rather than a comment because the current body +*describes work that is merged*, and a comment does not stop a reader from implementing it. + +**Proposed comment text (post first, then edit the body):** + +> **Rescope: ~75% of this issue landed in #1265 (2026-08-04, `77c034c33`, closing #1252).** +> +> Re-verified against `main` at `fac9e339042c` on 2026-08-08. Of the three boundaries this issue +> was filed on, only fragments remain: +> +> - Boundary 1 (`initialDataUpdatedAt` absent from `IslandQueryOptions`, TS2353) — addressed by +> #1265. +> - Boundary 2 (`createNetScriptQueryClient()` returning a `QueryClient` typed as the narrower +> `QueryClientPort`, TS2551 + TS2345) — addressed by #1265; +> `docs/site/web-layer/query-bridge.md` no longer carries the `as unknown as IslandQueryClient` +> cast. +> - Boundary 3 (`IslandQueryResult` missing `isRefetching` / `isFetching`, TS2339) — addressed by +> #1265. +> +> Leaving this issue open at its filed scope would send a remediation slice to re-implement merged +> work. The body is being edited down to the remnant; nothing is being ticked or claimed complete. +> +> **Remnant scope (what this issue now owns):** +> +> 1. `getIslandQueryClient()`'s `@throws` JSDoc documents a guard the implementation does not have +> (`packages/fresh/src/application/query/…/query-client.ts:26-27`). This is published through +> `deno doc`, so the wrong contract ships to consumers. +> 2. The `clientKey` falsy-input asymmetry between +> `packages/sdk/src/query/query-factory.ts:174` and `packages/sdk/src/ports/query-factory.ts:98`. +> 3. Regression tests for both, so the JSDoc and the body cannot diverge again. +> 4. A consumer migration note: `rickylabs/eis-chat` carries six copied casts written against the +> pre-#1265 types. The note must say which casts to delete and against which version, so +> eis-chat-class apps can drop them rather than carry them forward. + +**Proposed replacement `## Acceptance` section (BODY EDIT):** + +> ## Acceptance +> +> - [ ] `getIslandQueryClient()`'s `@throws` matches the implementation, or the guard is added. +> - [ ] The `clientKey` falsy-input asymmetry between the SDK query factory and its port is resolved. +> - [ ] Regression tests cover the documented throw contract and the falsy `clientKey` case. +> - [ ] `deno doc` output for `getIslandQueryClient` states no guard the body lacks. +> - [ ] A consumer migration note names the casts an eis-chat-class app can now delete and the +> version they became unnecessary in. +> - [ ] `docs/site/web-layer/query-bridge.md` is simplified where it documented a boundary that +> #1265 removed. +> +> Related: #1265 (merged 2026-08-04, `77c034c33`), #1252 (closed by it), #1210 (discovery source), +> #1278. + +**Rationale.** `research/repo-audit/web-layer.md` §13 verified the merge by running the checker +against current source, not by reading the issue. `github-board-open.md` §7 lists #1245 as the owner +for island query type gaps, so the issue must stay open — but at its true remaining size. + +**What it prevents.** The single highest-probability waste in this roadmap: a slice re-implementing +#1265. It also prevents the opposite error — closing #1245 outright and losing the `@throws` defect +and the consumer migration note, which nothing else owns. + +--- + + + +## 6. #1333 — fix(scaffold/frontend): make the default app an idiomatic eis-chat-grade reference (0.0.5, p0) + +**Amendment type:** comment (additive). **Not a body rewrite** — the ten existing acceptance boxes +stay exactly as written; this comment proposes additional boxes for the owner to append if accepted. + +**Proposed comment text:** + +> **Acceptance detail expansion (2026-08-08, seed-run proposal — additive).** +> +> The ten acceptance boxes here are correct but under-specified in the places where measured agent +> runs actually drifted. The pre-plan's expansion list, grounded against current source, proposes +> these as *additional* boxes. Existing boxes are unchanged and none is ticked. +> +> - [ ] The default app's primary resource route is implemented contract-first via +> `withRouteContract`, not a hand-written handler. +> - [ ] Data reaches the page through the typed SDK on a cache-first path, not a direct service +> call from the route. +> - [ ] `withResource` appears in the default app with a shared resource refined per layer. +> - [ ] Route params and search params are typed (`withPathParams` / `withSearchParams`), not +> parsed from strings. +> - [ ] Route-local groups are demonstrated: `(_components)`, `(_islands)`, **`(_shared)`** and +> **`(_lib)`**. +> - [ ] The generated app's own quality gate fails on `any` in app code, so a consumer inherits the +> no-`any` rule rather than reading about it. +> - [ ] The four seams are visibly distinct in the generated tree — DB model, API contract, route +> contract, view model — with the narrowing between them shown, not implied. +> - [ ] Loading, error, empty and success states are executable code in the default route, not +> prose. +> - [ ] The default app links to `/design` and `/design/composition` from a place a reader reaches +> without being told to. +> +> **Why these and not others.** Every one of them names a surface a measured Wave-6 run failed to +> reach: R2 (`workflow-builder-kimi-k3-max`, canary.13) shipped a **676-line `LoomCanvas.tsx`** +> with product `QueryIsland` / `withForm` / Fresh-UI feature-loop adoption at **0 / 0 / 0**; +> `definePage` and fresh-ui appear only in scaffold examples, never in Loom's product routes. +> The repo audit confirms the mechanism: `withResource`, `withForm`, `withRouteContract`, +> `withSearchParams`/`withPathParams`, `withStreaming` and `definePartial` have **zero** examples +> in the generated app — which is the surface `agent-conventions.ts` designates as canonical for +> agents, and `agent-conventions.ts:133` instructs agents to use `withForm` while listing no local +> reference for it. +> +> **Discharged dependency.** This issue's `Related:` line names #1328, which closed 2026-08-07 +> (`status:shipped`, `canary:0.0.5-canary.15`). That dependency no longer blocks. +> +> **Boundary.** The mobile-action-loss observation from the Wave-6 review belongs to this issue's +> acceptance surface unless it reproduces as a `@netscript/fresh-ui` defect on the current canary; +> it should not be filed separately without that repro. + +**Rationale.** The pre-plan's explicit instruction is "expand #1333, do not file new issues" for the +canonical scaffolded frontend. #1333 is p0 in the active milestone, so an additive comment is the +only safe amendment shape — a body rewrite of a p0 mid-milestone risks losing agreed text. + +**What it prevents.** The T2 pack filing a "canonical vertical slice" issue that duplicates #1333; +and #1333 shipping against ten boxes that a compliant implementation can satisfy while still +producing an app with no `withResource`, no typed params, and no `(_shared)`/`(_lib)`. + +--- + + + +## 7. #1335 — Epic: Scaffold conformance (Backlog / Triage, `type:umbrella`) + +**Amendment type:** comment (additive) + **BODY EDIT** limited to the `## Sub-issues` list. The +`## Sub-issues` checklist is deliberately *not* close-gated (conventions §4.4), so editing it does +not touch the merge gate. + +**Proposed comment text:** + +> **Sub-issue list refresh (2026-08-08).** +> +> The only listed sub-issue, **#1328**, closed on 2026-08-07 (`status:shipped`, +> `canary:0.0.5-canary.15`), so this epic currently reads as 0% complete when its first child has +> shipped. The `## Sub-issues` list is being refreshed to tick #1328 and to link the children that +> exist in prose today. +> +> **#1333 is this epic's frontend row.** The body names "Frontend scaffold modernization and dynamic +> app naming" as plain text; that is #1333 (0.0.5, p0). Linking it here prevents a dedup pass from +> re-filing it. This epic does not own #1333's implementation and must not close it. +> +> **Proposed additional children** (seed-run drafts; the owner substitutes filed numbers): +> +> - Service-layout child — collapsible `domain` / `application` / `ports` / `adapters` / `routers` / +> `auth` slice vocabulary, `service add-handler` placement, and a decision table for when a +> generated service collapses to a single file (seed draft-ID **T3-02**). +> - Generated-surface conformance rows this epic's inventory must produce a child for, each already +> evidenced at source: `/design/components` lists 50 of the registry's 66 items with no sync gate; +> `ui:add page --island` emits a `useSignal(0)` counter and an empty `queryLoaders = {}` instead +> of the advertised data-screen triad; `resolveProjectRoot` returns the workspace root while the +> app lives at `apps//`; `appRoutes.crudExample` aliases `serviceExample` so +> `/examples/crud` is unreachable and a test asserts the alias; the canonical island never passes +> `initialDataUpdatedAt: props.cachedAt` although the loader computes it; the generated +> quality-runner's source list is a literal allow-list decoupled from the workspace generator. +> +> This is an umbrella. No implementation PR should close it directly. + +**Proposed `## Sub-issues` replacement (BODY EDIT):** + +> ## Sub-issues +> +> - [x] #1328 — generated check misses TSX/plugin runtimes; scaffold-owned quality findings +> - [ ] #1333 — frontend scaffold modernization and dynamic app naming +> - [ ] Service-layout child — slice vocabulary + `service add-handler` placement + decision table +> - [ ] `/design/components` registry sync gate (50 of 66 items listed) +> - [ ] `ui:add page --island` emits the advertised data-screen triad +> - [ ] `resolveProjectRoot` writes into the app tree, not the workspace root +> - [ ] `appRoutes.crudExample` points at `/examples/crud` +> - [ ] Generated quality-runner source selection derives from the workspace generator + +**Rationale.** `github-board-open.md` §4.1 flags #1335's stale checkbox explicitly, and §4.2 warns +that #1335 names #1333's work as plain text rather than a link — the exact condition that produces a +duplicate filing. + +**What it prevents.** Re-filing #1333 during a dedup pass; an epic that reads as untouched while its +first child shipped; and the T2/T3 packs filing conformance children with no umbrella linkage. + +--- + + + +## 8. #1210 — docs(web-layer): differentiator deep-dives + competitive tutorial benchmark (0.0.6) + +**Amendment type:** comment (additive). No body edit — the per-API sub-page structure and the +competitive benchmark stay as written. + +**Proposed comment text:** + +> **Cross-capability golden recipes — proposed addition to this issue's scope (2026-08-08).** +> +> The per-API deep-dives teach one API at a time. Every measured agent failure so far happened at a +> *crossing* — the point where two capabilities meet and neither API page owns the seam. Proposal: +> add a "golden recipes" set to this issue, each recipe end-to-end, type-checked against published +> entrypoints, and each naming the seams it crosses. +> +> - **Contract-first resource screen.** route contract → typed SDK client → cache-first query +> factory → layered `definePage` → `QueryIsland` hydration → optimistic mutation with rollback. +> - **DB-model-first product.** generated `@database/zod` model → narrowed/extended versioned API +> contract → handler → OpenAPI → SDK → page. (Coordinate with #1332, which owns the docs +> statement of the DB-first predecessor path.) +> - **Live data screen.** durable stream producer → SSE consumer → island live query, including +> the event envelope, replay offsets and `traceparent` propagation. (Coordinate with #1329, which +> owns the envelope definition; this recipe consumes it and must not redefine it.) +> - **Background work with a receipt.** worker job → saga publish → compensation → one correlated +> trace, showing that a discarded publish result is a failure and not a success. +> - **Trigger to service.** webhook or scheduled trigger → worker → service call, including the KV +> adapter registration and the service-reference injection a generated background runtime needs. +> - **Protected screen.** auth boundary → protected route → typed principal available in the page +> loader, with the unauthenticated path shown. +> - **Second service.** `service add` → generated client/query module → a page that composes two +> services without hand-written fetch. +> - **Validated form.** Zod contract → `withForm` → server-side validation → partial navigation +> → error state, with the native-constraint attributes the browser receives. +> - **Add a capability the framework does not ship.** third-party plugin → discovery → generated +> registry → `plugin doctor` → healthy Aspire resource. +> - **Webhook delivery.** an outbound-delivery recipe over the worker template (retries, backoff, +> dead-letter, signature) — recorded as a recipe, not a promised primitive. +> +> Each recipe should end with the falsifiable check the deep-dives already use: remove the seam and +> the example must fail, so the recipe proves a mechanism rather than illustrating one. +> +> **Provenance of this list.** The pre-plan directs "expand #1210 with cross-capability golden +> recipes" without enumerating them. The ten above were derived by the 2026-08-08 seed run from the +> capability crossings that measured Wave-6 runs actually failed at; treat the list as a proposal +> to be trimmed, not a specification. + +**Rationale.** The pre-plan's dedup matrix says "expand instead of new" for #1210. Enumerating the +recipes inside #1210 is what makes that instruction actionable; a bare "add golden recipes" line +would be re-interpreted by every implementer. + +**What it prevents.** A separate "cookbook" or "recipes" issue duplicating #1210; and deep-dive +pages that each document their own API correctly while no page documents any crossing. + +--- + + + +## 9. #1208 — docs(tutorials): no tutorial demonstrates the page builder (0.0.5, p0, `status:plan`) + +**Amendment type:** comment (additive). Records the phase-2 obligation so it cannot evaporate. + +**Proposed comment text:** + +> **Phase-2 filing obligation — recorded so it cannot be lost (2026-08-08).** +> +> This issue's body defines a phase 2 (the full inconsistency-and-underleverage sweep across all +> tutorials) and says it will be "tracked as a checklist comment on #1208 when phase 1 lands". +> As of 2026-08-08 **no phase-2 issue exists on the board**, and a checklist comment on a p0 issue +> that is itself about to close is not a durable owner. +> +> Obligation, stated here so the next reader inherits it: **when the phase-1 PR merges, phase 2 is +> filed as its own issue** in the same milestone family, titled in house shape, carrying +> `Refs #1208` (not a closing keyword), with the tutorial inventory as its acceptance checklist. +> If phase 2 is instead decided to be unnecessary, that decision is recorded as a comment here and +> the row is struck — but it is not left implicit. +> +> Note for roadmap authors: because phase 2 has no issue number, it is one of the two highest +> duplicate-filing risks on this board. Do not file it before phase 1 merges, and do not file it +> twice after. + +**Rationale.** `github-board-open.md` §6.3 and §7 both flag the promised-but-nonexistent phase-2 +issue as a dedup trap. SYNTHESIS §7 ranks it alongside the prose-only umbrellas as a top risk. + +**What it prevents.** Both failure modes at once: phase 2 vanishing when #1208 closes, and phase 2 +being pre-emptively filed by a roadmap pass as a new issue that then collides with the real filing. + +--- + + + +## 10. #922 — Epic: Frontend contribution layer (0.0.7, `type:umbrella`, 24 open children) + +**Amendment type:** comment (additive clarification). **No body rewrite** — the wave narrative and +the RFC #890 design record stay verbatim. + +**Proposed comment text:** + +> **Wave-label vs milestone clarification (2026-08-08) — additive, no scope change.** +> +> This epic's body sequences its waves as `beta.13` / `beta.15` / `beta.17`. Those milestone titles +> no longer exist: the `0.0.1-beta.N` line was renamed to `0.0.N` around 2026-08-01, and milestones +> have since been renamed in place several times. The body text is a historical record and is being +> left as written; this comment supplies the current mapping so no one reads a dead title as a +> schedule. +> +> Measured milestone placement of this epic's children as of 2026-08-08: +> +> | Body wave | Children | Actual milestone | +> | --- | --- | --- | +> | Wave 0 proofs (beta.13) | #923–#927 | 0.0.7 | +> | Wave 1 contracts + spine (beta.13) | #928–#933 | 0.0.7 | +> | Wave 1b gateway | #934 | 0.0.7 | +> | Wave 2 DX/lifecycle (beta.13) | #935–#938, #940 | 0.0.7 | +> | Wave 2 DX/lifecycle | #944 | 0.0.9 | +> | Wave 3 consumers (beta.15) | #939, #941 | 0.0.7 | +> | Wave 3 consumers (beta.15) | #942, #943 | 0.0.11 | +> | Completion (beta.17) | #945, #946 | 0.0.13 | +> +> The epic itself is 0.0.7. Note **#944 is a Wave-2 item milestoned later (0.0.9) than Wave-3 +> siblings still in 0.0.7** — that inversion is a real ordering question for whoever schedules this +> epic, not a labelling artifact. +> +> **Re-baseline reminder.** This epic's body says "Refs #427, #432 — both KEEP-and-re-baseline per +> the RFC's supersession map; no issues closed by this epic's filing." As of 2026-08-08 **the +> re-baseline has not happened**: #427 and #432 are still open verbatim under `epic:dev-dashboard` +> (#400), and #400's 29 open children overlap this epic's consumer wave. The re-baseline is +> outstanding work owned by whoever schedules #922's Wave 3, and it should be done before #400's +> dashboard panels are planned against the old text. +> +> **If milestones shift again**, note the house pattern: milestones are renamed in place +> highest-to-lowest and the freed title is created afterwards, so children do not move and this +> table stays valid under the old numbers — only the titles change. + +**Rationale.** `github-board-open.md` §6.5 measured the drift; `github-board-history.md` §5 documents +the rename mechanism that caused it. The re-baseline omission is measured in §4.2. + +**What it prevents.** Someone "fixing" #922 by moving children to match dead beta titles; and #400's +dashboard work being planned against superseded #427/#432 text. Also prevents an epic body rewrite +that would destroy the RFC #890 review trail. + +--- + + + +## 11. #301 — epic: Road to 0.0.1-stable (Backlog / Triage, `type:umbrella`) + +**Amendment type:** comment (additive) + optional **BODY EDIT** limited to ticking the five closed +rows. The child checklist is not close-gated, so ticking it is safe. + +**Proposed comment text:** + +> **Stale checkbox audit (2026-08-08).** +> +> Five children listed here as unchecked are already CLOSED: **#305, #306, #391, #399, #401**. +> Remaining genuinely open: #302, #303, #307, #309, #313, #327, #400. This epic therefore reads far +> less complete than it is, which distorts any milestone reforecast that reads it. +> +> No checked box on this epic points at a still-open issue, so there are no false-complete rows — +> the error is one-directional and safe to correct by ticking. +> +> Note for planners: three of the remaining rows (#313, #327, #400) are themselves umbrellas with +> their own child sets, so this epic's true remaining scope is much larger than its row count +> suggests. + +**Rationale.** `github-board-open.md` §4.1 verified each of the five against the closed set. + +**What it prevents.** A roadmap reading #301 as ~8% complete and re-planning finished work; and a +"road to stable" status report built on a checklist nobody has reconciled. + +--- + + + +## 12. #1126 — Epic: OpenAPI→MCP service introspection (0.0.5, `type:umbrella`) + +**Amendment type:** comment (additive) + optional **BODY EDIT** ticking the nine closed rows. + +**Proposed comment text:** + +> **Stale checkbox audit (2026-08-08).** +> +> Nine children listed here as unchecked are already CLOSED: **#1128, #1129, #1130, #1131, #1132, +> #1133, #1134, #1135, #1136**. Only **#1137, #1138, #1139, #1140** remain open (all four carry +> `epic:openapi-mcp`). +> +> This epic is materially near completion and currently reads as barely started. Correcting the +> checklist matters for 0.0.5 scoping specifically: #1126 is one of the 0.0.5 umbrellas, and an +> uncorrected checklist inflates the apparent remaining 0.0.5 surface. +> +> This is an umbrella. No implementation PR should close it directly. + +**Rationale + prevention.** Same as #301. Additional stake: #1126 sits in the *active* milestone, so +its stale checklist directly distorts the "can 0.0.5 close" decision. + +--- + + + +## 13. #1325, #1326, #1329 — evidence attachment (no scope change) + +**Amendment type:** comment on each. Evidence pointers only — no acceptance box is added, changed or +ticked, and no scope is touched. These three are already well-specified; the amendment exists so the +Wave-6 measurements and the 2026-08-08 source audit are discoverable from the issue. + +**Proposed comment on #1325** (`fix(triggers): generated background runtime omits the Redis adapter`): + +> **Additional evidence (2026-08-08) — no scope change.** +> +> Independently reproduced at source: `plugins/triggers/src/adapter/resources/glue/runtime.stub.ts` +> emits the generated `triggers/runtime.ts` with no `@netscript/kv/redis` import, so the generated +> runtime carries no adapter for the default Aspire Redis/Garnet cache. +> +> Two measured agent runs hit this independently, which is why the generalisation requirement in +> this issue's acceptance is the real deliverable rather than the one-line import: +> +> - **Wave-6 R2** (`workflow-builder-kimi-k3-max/`, Kimi K3 Max, `0.0.5-canary.13`) — crash-loop +> `KvConnectionError`; the builder added a one-line side-effect import to a scaffold-owned trigger +> entrypoint, i.e. hand-edited a generated file. +> - **Wave-6 R3** (`billing-run-grok-4.5-high-canary.16/`, Grok 4.5 high, `0.0.5-canary.16`) — +> repair commit `8b86649` adds the `@netscript/kv/redis` triggers import; recorded as "hard to +> diagnose". +> +> **Classification conflict worth recording:** R2 classifies this as a framework defect (D-class); +> R3 classifies the same surface as a docs/MCP discoverability gap with "no framework defect +> established". Both runs had to add the same glue by hand on two different canaries. The +> source-level evidence above resolves the conflict in favour of the defect classification. + +**Proposed comment on #1326** (`fix(streams): DurableStreamProducer permanently drops writes`): + +> **Additional evidence (2026-08-08) — no scope change.** +> +> Mechanism re-confirmed at source in +> `packages/plugin-streams-core/src/application/create-durable-stream.ts`: `#connect` runs once, on +> failure sets `#connectError` and returns, and `#appendEvent` drops every event while that field is +> set; no timer, retry policy or state transition ever clears it — so the operator log line +> promising "until reconnect" names a transition the implementation cannot perform. +> +> **Wave-6 R2** recorded the consequence class independently: "durable-stream producer silently +> drops writes forever after a startup-order race". R3 ran a single clean graph and plausibly +> avoided the race, so R3's silence is not evidence against this — it is a coverage gap. +> +> **Pairing reminder:** this issue's acceptance depends on the standardized stream event envelope +> defined by **#1329**. The two must be scheduled as a pair; landing reconnect telemetry against an +> undefined envelope produces spans that #1329 will then redefine. + +**Proposed comment on #1329** (`fix(streams): documented SSE consumer shape differs from the wire`): + +> **Additional evidence (2026-08-08) — no scope change.** +> +> Consumer-side confirmation from measured runs, both of which reverse-engineered the wire because +> the documented shape does not receive anything: +> +> - **Wave-6 R2** (`workflow-builder-kimi-k3-max/`) — `apps/dashboard/islands/LoomCanvas.tsx` uses +> named `data` events with array payloads, arrived at by runtime inspection. +> - **Wave-6 R3** (`billing-run-grok-4.5-high-canary.16/`) — used the wrong durable-stream path +> (`/v1/streams/billing/run-events` → 404) before finding the correct +> `/v1/stream/netscript/billing/run-events` → 200. R3's own audit classifies this as a docs/MCP +> discoverability gap; it maps onto this issue's documented-shape defect. +> +> Two independent consumers reconstructing the same undocumented envelope is the strongest available +> argument for this issue's first acceptance box (one exported versioned schema defining every SSE +> event name and payload) over a documentation-only fix. + +**Rationale.** These three are the 0.0.5 streams/triggers core and are already correctly scoped; +what they lack is the cross-run evidence that makes their *generalisation* requirements defensible +during implementation review. + +**What it prevents.** An implementer satisfying #1325 with a one-line import (R2's own workaround) +rather than the enumerated invariant; #1326 and #1329 being scheduled apart; and #1329 being closed +with a docs edit because R3's classification was read without R2's. + +--- + + + +## 14. #979 — fix(aspire): plugin API resources still pin host ports 8091–8094 (no milestone) + +**Amendment type:** comment (additive evidence). Also appears in the board-hygiene batch below for +its missing milestone. + +**Proposed comment text:** + +> **Additional evidence — the stub-port surface is wider than the Aspire entries (2026-08-08).** +> +> This issue owns the plugin API resources' pinned host ports. A 2026-08-08 source audit found the +> same hardcoded ports in three further layers, which matters because dropping `Port` from the +> scaffolder's plugin entries will not by itself remove the pinning — these paths bypass the +> allocator entirely: +> +> - **Contribution env/health literals** — each contribution allocates through +> `ctx.port(name, DEFAULT)` and then publishes a literal: +> `plugins/sagas/src/aspire/sagas-contribution.ts:135` (`SAGAS_API_URL: +> http://localhost:8092`) and `:146` (health URL, `_ctx` unused); +> `plugins/triggers/src/aspire/triggers-contribution.ts:139` and `:149` (same pattern). +> `plugins/workers/src/aspire/workers-contribution.ts:71,81` is the correct counter-example — it +> uses the allocated port. +> - **Fixed-port fallbacks in runtime clients** — `SAGAS_API_DEFAULT_PORT = 8092` +> (`plugins/sagas/src/constants.ts:11`), `plugins/sagas/src/cli/adapters/runtime-api-client.ts:27` +> (`http://127.0.0.1:8092/api/v1/sagas`), +> `plugins/workers/src/cli/adapters/runtime-api-client.ts:27` (`…:8091/api/v1/workers`). +> - **E2E probe contexts** — `plugins/workers/src/e2e/probes/probe-context.ts:5` +> (`http://localhost:8091`), `plugins/sagas/src/e2e/probes/probe-context.ts:3` +> (`http://127.0.0.1:8092`). +> +> Commit `0b11ca47a` (#1211, randomize default listener ports) is what turned each of these from a +> working default into latent breakage: once ports are randomized, a literal fallback silently +> targets a port nothing is listening on. +> +> **Why this belongs here and not in a new issue:** this issue already owns the prerequisite chain +> (resolve endpoints from the Aspire resource service instead of hardcoding, including the +> `--allow-net` grant handed to the generated project, plus the ~20 `docs/site/**` passages that +> `curl` those ports). The literals above are the same defect at a different layer and should be +> enumerated in the same sweep. A separate seed-run draft (**T4-06**) tracks the stub-port +> hardcodes if the owner prefers them split; if so, it carries `Refs #979` and this comment is its +> evidence base. +> +> **Sibling:** #980 (`netscript service add` pins an Aspire host port) is the same defect on a +> different command and is likewise unmilestoned. + +**Rationale.** `research/repo-audit/runtime-plugins.md` §1.3-1.4 enumerated these at source; the +`github-board-open.md` §7 dedup checklist names #979 as the owner for Aspire plugin port pinning, so +this is evidence attachment, not a new filing. + +**What it prevents.** A fix that removes `Port` from the scaffolder's entries, passes the E2E suite +(which probes the literals), and leaves two workspaces still colliding — plus the silent +`127.0.0.1:8092` fallback that the saga publish-receipt defect rides on. + +--- + + + +## 15. #1090 and #1197 — measured adoption evidence (Wave 6) + +**Amendment type:** comment on each. Evidence only; neither comment ticks or proposes an acceptance +box, because #1090's boxes are explicitly observational and #1197's demand a *future* re-measurement. + +**Proposed comment on #1197** (`agentic: the agent-init harness had zero adoption on 0.0.4`): + +> **Wave-6 measurements — the seventh and eighth data points (2026-08-08).** +> +> This issue records six consecutive measured runs with zero docs-MCP calls. Wave 6 adds two more, +> and the second one breaks the streak in a way that is directly relevant to this issue's +> acceptance: +> +> - **R2** (`workflow-builder-kimi-k3-max/`, Kimi K3 Max, `0.0.5-canary.13`): **live NetScript MCP +> calls 0** for the whole run. `netscript agent init` was skipped naturally until a 07:53:21Z +> supervisor correction; the builder's own explanation was that it "misread 'agent tooling' as +> optional editor garnish instead of the framework discovery path". `plugin doctor` and Aspire +> OTEL unused. **Even after `agent init` ran, MCP calls stayed 0** — `agent init` could not attach +> the generated `.mcp.json` to an already-running OpenCode host. Tool mix: 378 tool parts +> (bash 263, edit 48, write 44, read 14, webfetch 7, todowrite 1, skill 1). +> - **R3** (`billing-run-grok-4.5-high-canary.16/`, Grok 4.5 high, `0.0.5-canary.16`): **MCP calls +> non-zero** — export 6, docs search 3, doctor 2, doc 2, operation schema 2, API-service listing +> 2, plus package/service discovery; Aspire MCP list-apphosts 1, list-resources 1. 514 tool calls +> total. This followed a **supervisor-enforced preflight**: `agent init`, local docs, reload, MCP +> attachment and a harmless docs-lookup proof, all in the same session **before implementation**. +> +> **What this adds to this issue.** The zero is not purely a discovery-motivation problem. R2 gives +> a concrete mechanical cause — a running host cannot pick up a newly written `.mcp.json` — which is +> a fixable defect distinct from "agents do not know to look". And R3 shows the number moves when +> attachment is enforced at session start, which is evidence for this issue's "routing at the moment +> of failure" requirement being necessary but not sufficient: attachment has to work first. +> +> No acceptance box is proposed here; this issue's acceptance correctly demands a *re-measured* +> future run, and R2/R3 are prior runs, not that measurement. + +**Proposed comment on #1090** (`verify(wave-five): does the shipped agent surface change behaviour?`): + +> **Wave-6 observations relevant to this issue's four criteria (2026-08-08) — evidence, not +> discharge.** +> +> None of the four criteria here is satisfied by Wave 6, and this comment does not tick anything. +> Recording what Wave 6 observed, so the eventual verification run has a baseline: +> +> - *Non-zero MCP diagnostic usage:* R2 = 0 across the run; R3 = non-zero (export 6, docs 3, +> doctor 2) but only after a supervisor-enforced preflight, so it is not an unassisted +> observation. +> - *An agent building a data screen runs `ui:add` or records why not:* R2 did neither — +> product `QueryIsland` / `withForm` / Fresh-UI feature-loop adoption **0 / 0 / 0**, and a +> **676-line `LoomCanvas.tsx`** shipped instead. `definePage` and fresh-ui appear only in the +> scaffold examples, never in the product routes. +> - *The #1071 falsifiable check (blind, six agents per arm, varying only the app-scoped conventions +> file):* not run in Wave 6. Wave 6 was a natural experiment with a deliberate +> no-contamination rule — suggestions such as naming the plugins or banning hand-rolled SSE were +> explicitly **rejected as builder prompt contamination**. That discipline is worth preserving in +> the eventual arm design, but it means Wave 6 cannot substitute for the controlled check. +> - *An agent asked to build a service-backed UI reaches a Web Layer page before writing a route:* +> not observed in either run. +> +> **Thesis reinforcement.** Wave 6 restates this issue's own lesson with a second mechanism: R2 +> adopted the framework and immediately surfaced five-plus D-class seams, so non-adoption is not +> inevitable — the shipped surface has to be *reachable at the moment of need*, and R3 shows the +> number moves when it is enforced. +> +> Reminder for planners: #1090, #1102, #1197 and #1201 are one measurement chain. Planning them +> separately builds the extraction harness three times. + +**Rationale.** SYNTHESIS §1 and §6 treat the measurement chain as owned and untouchable; the value +this run can add is evidence, not scope. R2/R3 are the first measurements taken *after* the runs +#1197 describes. + +**What it prevents.** A remediation slice claiming #1197's acceptance on the strength of R3's +non-zero MCP count — which was supervisor-enforced, not spontaneous; and the T7 pack re-deriving the +Wave-6 numbers instead of citing them. + +--- + + + +## 16. Board-hygiene batch + +One batch, executed by the owner in a single pass. Every item is label/milestone metadata only — no +issue body changes, no closures, no scope decisions. All facts from `github-board-open.md` §5.1, +§4.1 and `github-conventions.md` §2.2, measured 2026-08-08. + +### 16.1 Issues missing required metadata + +The minimum contract is: ≥1 `type:`, ≥1 `area:`, exactly one `status:`, a `priority:`, and a +milestone (`github-conventions.md` §2.3). + +| Issue | Missing | Proposed action | +| --- | --- | --- | +| **#175** | **all labels** (zero labels), no `priority:`, no `status:` | Triage: add `type:`, `area:`, `priority:`, `status:triage`. It sits in milestone `0.0.2`, which is stable-released — so also decide: still wanted, or close as not planned. | +| **#950** | no `priority:`, no `status:` | Add `priority:` + `status:triage`. It is an `epic:ai-stack` member in milestone `0.0.8`. | +| **#1000** | no `priority:`, no milestone | Add `priority:` + a milestone. Also carries the legacy `documentation` label (see 16.3). | +| **#979** | no milestone | Assign a milestone. It has a real dependency chain (E2E port probes + ~20 docs passages) so it should not sit unmilestoned; see §14 above. | +| **#980** | no milestone | Assign the same milestone as #979 — same defect, different command. | + +**Proposed comment for #175** (it is the only zero-label issue and needs a human decision): + +> **Board hygiene (2026-08-08).** This issue carries **no labels at all** — the only such issue on +> the open board — and no `priority:`. It is assigned to milestone `0.0.2`, which was released as +> `v0.0.2` on 2026-08-01 and still holds 5 open issues (#175, #767, #768, #863, #864). Requesting +> triage: apply the minimum taxonomy (`type:` + `area:` + `priority:` + `status:triage`), and either +> re-milestone it to a live cut or close it as not planned. No scope judgement is being made here. + +### 16.2 `.github/labels.yml` parity + +The file is materially out of date in both directions. Per its own header rule — *"Add new labels +here first, then create them; do NOT delete existing labels (that strips them off live issues) — +deprecate in this file and propose removal to the maintainer"* — the fix is **declare and create, +never delete**. + +**(a) Declared in `labels.yml` but NOT live (2) — create them:** + +- `status:close-gate-override` (`b60205`, "Audited exception to the closing-keyword acceptance + gate") +- `docs-eval:skip` + +**Consequence if not fixed:** the audited close-gate escape hatch documented in +`netscript-pr/SKILL.md` **cannot be applied today** — the label does not exist, so an audited +exception has no way to be recorded. This is the highest-value item in the whole hygiene batch +because it silently removes a documented process option. + +**(b) Live but NOT declared in `labels.yml` (33, excluding the machine-generated `canary:*`) — add +them to the file so the declared taxonomy matches reality:** + +- `area:` — `agentic`, `ai`, `contracts`, `db`, `packages`, `queue`, `release`, `runtime-config`, + `sagas`, `services`, `streams`, `triggers`, `workers` (several in active use: `area:agentic` on + #1330/#1331/#1343, `area:contracts` on #1332/#1263, `area:release`). +- `epic:` — `deploy-plugin`, `desktop-frontend`, `enterprise-auth`, `road-to-stable`, + `unified-runtime`. +- `status:` — `blocked` (in live use on #1320 and #1280), `in-progress`, `in-review`, `review`. +- `type:` — `feature`, `release`. +- gates/flags — `gate:ci`, `e2e-cli-gate`, `priority:high`, `codex`, `dx`, `prime-time`, `sagas`, + `service`, `question`, `invalid`. + +**(c) Duplicate pairs — deprecate in the file with a note, do not delete.** Record the preferred +member so new issues stop splitting: prefer `type:feat` over `type:feature`; `priority:p1` over +`priority:high`; `area:database` over `area:db`; `area:plugins` over the per-plugin +`area:sagas`/`area:streams`/`area:triggers`/`area:workers` (the per-plugin ones are what #1325, +#1326 and #1329 actually carry, so if the split is intentional, say so in the file rather than +leaving it ambiguous). + +**(d) The `status:` single-label hazard.** `labels.yml` says exactly one `status:` per open issue, +but four undeclared `status:` values are live (`blocked`, `in-progress`, `in-review`, `review`), +two of which duplicate declared columns. Either declare `status:blocked` (it is genuinely in use and +has no declared equivalent) and deprecate the other three, or map them onto declared columns. +Whichever, record it in the file. + +**(e) `wave:*` labels.** `wave:v1`, `wave:v1-min` and `wave:defer` are declared and live but appear +on none of the recently filed issues. Mark them deprecated in `labels.yml` unless the plan revives +the band, so new filings stop being asked to consider them. + +### 16.3 Legacy non-namespaced labels still in use + +- `rfc` on **#234, #313, #510, #820** — all open, all `Backlog / Triage`. Decision needed: keep + `rfc` as the flag it is declared to be, or introduce a namespaced equivalent. Do not strip it from + live issues without a replacement. +- `documentation` on **#1000** only — the last user of the GitHub-standard label where + `type:docs` + `area:docs` is the house taxonomy. Proposed: add `type:docs` + `area:docs` to #1000 + and leave `documentation` in place (deletion strips it from the issue). + +### 16.4 Orphaned `epic:` labels (label group with no umbrella issue) + +- `epic:desktop-frontend` — sole member #859. +- `epic:docs-cut` — sole member #695. +- `epic:telemetry-revamp` — sole member #248 (which is also `epic:ai-stack`). + +Proposed: for each, either file/point to an umbrella, fold the member into an existing epic, or +deprecate the label in `labels.yml`. A single-member epic label is indistinguishable from a typo at +read time. + +### 16.5 Cross-epic double membership + +- **#451, #453, #454, #455** carry both `epic:deployment` and `epic:unified-runtime`. This is not + itself an error, but it means **#823**'s entire open membership sits inside **#327**'s unchecked + child list — three umbrellas (#327, #823, #830) over one child set. +- **#830** is an umbrella *and* a member of `epic:deployment` (i.e. a child of #327). +- **#248** sits in both `epic:ai-stack` and `epic:telemetry-revamp`. + +Proposed: record the intended containment in each umbrella body (a single line naming the parent) so +the double membership reads as deliberate. Do not remove labels — the label group is the machine +truth for membership and stripping it loses the relationship. + +**Proposed comment for #823** (the clearest case): + +> **Board hygiene (2026-08-08).** This epic's entire open membership (#451, #453, #454, #455) also +> carries `epic:deployment` and appears in #327's unchecked child list; #830 is a third umbrella +> over the same deployment surface. No labels are being changed. Requesting one line in each of +> #327 / #823 / #830 stating the intended containment, so a planner reading any one of them knows +> whether the four shared children are owned here, there, or jointly. Planning these three +> independently will produce duplicate slices over one child set. + +**Rationale for the whole batch.** `github-board-history.md` §6.7: label hygiene in this repo is +real and enforced — every closed 0.0.5 row carries exactly one `status:`, a `canary:` label and its +milestone, and the close-gate verifies GraphQL `closingIssuesReferences` against body keywords. +Drafts produced by this run will be rejected by those same gates if the taxonomy they are filed +against is itself inconsistent. + +**What it prevents.** (a) An audited close-gate exception being impossible to record because +`status:close-gate-override` does not exist; (b) new issues splitting across `type:feat`/ +`type:feature` and `area:db`/`area:database`, which makes every label-based query wrong; +(c) #979/#980/#1000 remaining invisible to every milestone view; (d) three deployment umbrellas +each planning the same four children. + +--- + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Baseline +`fac9e339042c` (== `origin/main`). Sources: `SYNTHESIS.md` §3 (adjudications 2, 3, 4), +`research/github-board-open.md` §4–§7, `research/github-board-history.md` §5–§6, +`research/github-conventions.md` §2–§4, `research/preplan-package.md`, +`research/repo-audit/{web-layer,scaffold-doctrine,runtime-plugins}.md`, `research/wave-6-runs.md`. +Every re-measured figure was executed in this worktree on 2026-08-08; the working tree was left +clean. **No GitHub state was read-modified or written by this run.** diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/FILING-LOG.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/FILING-LOG.md new file mode 100644 index 0000000000..e4c574fadf --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/FILING-LOG.md @@ -0,0 +1,85 @@ +# Stage-H filing log + +Filed on 2026-08-08 after explicit owner ratification. GitHub is now authoritative; the issue +drafts in this planning PR are provenance and implementation context only. + +## Milestone train + +Existing milestone objects were renamed from highest to lowest so their issue membership stayed +attached while two releases were inserted: + +| Milestone object | Previous title | Live title | +| --- | --- | --- | +| #21 | 0.0.13 | 0.0.15 | +| #20 | 0.0.12 | 0.0.14 | +| #19 | 0.0.11 | 0.0.13 | +| #18 | 0.0.10 | 0.0.12 | +| #17 | 0.0.9 | 0.0.11 | +| #16 | 0.0.8 | 0.0.10 | +| #24 | 0.0.7 | 0.0.9 | +| #25 | 0.0.6 | 0.0.8 | +| #26 | — | 0.0.6 (new) | +| #27 | — | 0.0.7 (new) | + +The complete former-0.0.6 membership was first restored to new milestone #26. The planned +exceptions were then reconciled: #1279 → 0.0.15; #979 and #980 → 0.0.8; #1000, #175, #767, +#768, #863, and #864 → Backlog / Triage. No existing issue was closed. + +## Draft-ID to live-issue map + +| Draft | Live issue | Milestone | +| --- | --- | --- | +| T1-01 | #1348 | 0.0.6 | +| T1-02 | #1349 | 0.0.6 | +| T1-03 | #1350 | 0.0.6 | +| T1-04 | #1351 | 0.0.6 | +| T1-05 | #1352 | 0.0.6 | +| T1-06 | #1353 | 0.0.6 | +| T2-01 | #1354 | 0.0.7 | +| T2-02 | #1355 | 0.0.7 | +| T2-03 | #1356 | 0.0.7 | +| T2-04 | #1357 | 0.0.7 | +| T2-05 | #1358 | 0.0.7 | +| T2-06 | #1359 | 0.0.7 | +| T2-07 | #1360 | 0.0.7 | +| T3-01 | #1361 | 0.0.6 | +| T3-02 | #1362 | 0.0.6 | +| T3-03 | #1363 | 0.0.6 | +| T3-04 | #1364 | 0.0.6 | +| T4-01 | #1365 | 0.0.7 | +| T4-02 | #1366 | 0.0.7 | +| T4-03 | #1367 | 0.0.7 | +| T4-04 | #1368 | 0.0.7 | +| T4-05 | #1369 | 0.0.7 | +| T4-06 | #1370 | 0.0.8 | +| T4-07 | #1371 | 0.0.8 | +| T4-08 | #1372 | 0.0.8 | +| T5-01 | #1373 | 0.0.8 | +| T5-02 | #1374 | 0.0.8 | +| T5-03 | #1375 | 0.0.8 | +| T5-04 | #1376 | 0.0.8 | +| T5-05 | #1377 | 0.0.8 | +| T6-01 | #1378 | 0.0.8 | +| T6-02 | #1379 | 0.0.8 | +| T6-03 | #1380 | 0.0.8 | +| T7-01 | #1381 | 0.0.8 | +| TA-01 | #1382 | 0.0.8 | +| TA-02 | #1383 | 0.0.8 | +| TA-03a | #1384 | 0.0.8 | +| TA-03b | #1385 | 0.0.8 | +| TA-03c | #1386 | 0.0.8 | +| TA-04 | #1387 | 0.0.8 | +| TA-05 | #1388 | 0.0.8 | + +## Reconciliation receipts + +- Created 41 issues (#1348–#1388): 10 in 0.0.6, 12 in 0.0.7, and 19 in 0.0.8. +- Replaced internal Draft-ID dependency references in every live issue with live issue numbers. +- Verified every new issue is open, has exactly one milestone, `status:triage`, and at least one + `type:`, `area:`, and `priority:` label. +- Posted the ratified additive amendment blocks to #1278, #1276, #1279, #1275, #1245, #1333, + #1335, #1210, #1208, #922, #301, #1126, #1325, #1326, #1329, #979, #1197, #1090, #175, + and #823. Marker: ``. +- Preserved open PR #1215 and every other former-0.0.6 item during the milestone insertion. +- No issue was closed and no implementation work was started by this filing pass. + diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/FILING-MANIFEST.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/FILING-MANIFEST.md new file mode 100644 index 0000000000..763af2f810 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/FILING-MANIFEST.md @@ -0,0 +1,72 @@ +# Stage-H filing manifest + +Owner ratification: 2026-08-08, in-turn instruction to open all planned issues and execute the +milestone train. PLAN-EVAL and IMPL-EVAL remain explicitly owner-waived for this planning-only run. +GitHub is authoritative after this manifest executes. + +## Preconditions captured immediately before filing + +- Source PR: #1347 at `1d6c6a70fd6250fee07f8942a2f4873bf68c2b0f`. +- Open issues audited: 259 (pull requests excluded). +- Open milestones audited: 13. +- Proposed issue drafts: 41; exact-title collisions with the live open board: 0. +- Every requested label exists live. +- Existing `0.0.6` milestone number: 25; open-issue snapshot count: 23. + +## Milestone rename operations — execute in this order + +Title-only collision-free slide, highest to lowest: + +1. milestone 21: `0.0.13` → `0.0.15` +2. milestone 20: `0.0.12` → `0.0.14` +3. milestone 19: `0.0.11` → `0.0.13` +4. milestone 18: `0.0.10` → `0.0.12` +5. milestone 17: `0.0.9` → `0.0.11` +6. milestone 16: `0.0.8` → `0.0.10` +7. milestone 24: `0.0.7` → `0.0.9` +8. milestone 25: `0.0.6` → `0.0.8` + +Then create: + +- `0.0.6` — Verification, docs truth & RFC ratification. +- `0.0.7` — Typed seams + generation. + +Milestone 25 becomes the new `0.0.8` — Runtime truth + service slice. + +## Existing 0.0.6 preservation snapshot + +Move all 23 issues from renamed milestone 25 back to the newly created `0.0.6` before filing new +issues: + +`#1085`, `#1093`, `#1112`, `#1139`, `#1140`, `#1163`, `#1175`, `#1201`, `#1210`, `#1243`, +`#1215` (open PR), `#1246`, `#1260`, `#1262`, `#1263`, `#1278`, `#1279`, `#1280`, `#1293`, +`#1296`, `#1306`, `#1320`, `#1343`. The execution script must re-read milestone 25 and move its +entire membership, not trust this prose list. After preservation, move `#1279` from the new `0.0.6` +to `0.0.15` per the plan. + +## Other existing-issue moves + +- `#979`, `#980` → `0.0.8`. +- `#1000` → `Backlog / Triage`. +- `#175`, `#767`, `#768`, `#863`, `#864` → `Backlog / Triage` from shipped `0.0.2`. +- No existing issue is closed during filing. + +## New issue source + +File every Markdown draft under `milestones/*/*.md`, using the directory milestone as the locked +milestone decision. Strip the draft marker and metadata preamble from the live issue body. Apply +the declared labels. Count contract: + +- `0.0.6`: 10 new issues. +- `0.0.7`: 12 new issues. +- `0.0.8`: 19 new issues. +- Total: 41. + +After all issues exist, replace every internal Draft-ID dependency reference with its live issue +number and record the complete mapping in `FILING-LOG.md`. + +## Reconciliation + +Apply the additive existing-issue amendment blocks from `EXISTING-ISSUE-AMENDMENTS.md` as comments +where the target remains open. Do not close duplicate umbrellas during this filing; preserve the +seed-run rule that supersession closes only through a later owner action or resolving PR. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/IMPLEMENTATION-HANDOFF.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/IMPLEMENTATION-HANDOFF.md new file mode 100644 index 0000000000..f5c98d9b92 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/IMPLEMENTATION-HANDOFF.md @@ -0,0 +1,98 @@ +# Implementation handoff — DRAFT (no GitHub mutation; owner ratification pending) + +What becomes executable, in what order, on which lanes, once the owner ratifies the fork sweep +(`MASTER-PLAN.md` §7) and a later authorized run files the board from the manifest +(`ISSUE-DEDUP-AND-SUPERSESSION.md`). Implementation lanes launch from **GitHub + these design +packs**, never from this run's chat history. Routing is data: select every lane from +`.llm/harness/workflow/lane-policy.md` at dispatch time; identities below are the *expected* +canonical routes as of 2026-08-08. + +## 0. Pre-implementation gate (owner + one filing run) + +1. Owner ratifies forks F1–F12 (or amends; every default is reversible). +2. A dedicated filing session executes, in order: label parity (`.github/labels.yml` — the file + is missing 33+ live labels and live is missing `status:close-gate-override`/`docs-eval:skip`), + milestone renames highest→lowest + create 0.0.7/0.0.8, amendments from + `EXISTING-ISSUE-AMENDMENTS.md`, then issue filing per milestone directory with `FILING-LOG.md` + mapping draft-ID → live number. Re-verify each touched issue's live state immediately before + mutation (GitHub wins over this plan on conflict). +3. After filing, GitHub is the single source of truth; these run docs get authority banners. + +## 1. First executable groups (PR-sized, cluster rules per `agent-milestone-orchestrator`) + +**Group A — 0.0.5 close-out (already implementation-ready today, no ratification needed).** +The undispatched W2–W5 remainder is fully specified on live issues: the streams pair +(#1326+#1329, one supervisor — they share the envelope), #1333 (p0 scaffold frontend), #1208 +phase 1. Lane: `complex_implementation` (Codex Sol · high) for #1326/#1329/#1333; +`documentation_authoring` for #1208. Ready because: complete contracts + acceptance on the live +issues; the corpus adds only evidence pointers (amendments). + +**Group B — 0.0.6 wave 1 (ready at ratification; docs + gates, cheap lanes).** +- B1: T5-01 dialect fix (**after F7 is decided**) + T5-02 compile-the-docs gate — one docs-lane + PR pair; T5-02's checker is repo tooling (`chore_code` lane). +- B2: T5-03 + T5-04 MCP wiring — small scoped slices, `light_implementation`. +- B3: T6-01 + T6-02 quality/CI gates — `normal_implementation`; T6-02's lock decision first. +- B4: RFC-A + RFC-B tracking issues filed; RFC review is owner + `deep_analysis` (Fable) session + over the `rfcs/` drafts; ratification recorded on the tracking issues. +- Gate set: scoped wrappers + `doc:lint` + the new gates' own RED-first fixtures. Not e2e-cli. + +**Group C — 0.0.7 wave 1 (entry: RFC-A accepted).** +- C1: T1-04 transport consolidation (prereq of T1-02) — `normal_implementation`. +- C2: T1-02 seam re-exposure + T1-03 typed errors — `complex_implementation`, jsr-audit gate + (public surface changes), review per effort-paired ladder. +- C3: T2-03 root-targeting fix (hard prereq of T2-01/02/04) — `normal_implementation`. +- Then C4: T2-02 generator → C5: T2-01 slice generator + T2-04 triad → C6: T1-05 auth dogfood → + C7: T1-06 trace contribution. Byte-identity and no-`any` consumer gates are the slice gates. + +**Group D — 0.0.8 wave 1 (entry: 0.0.7 generators in a canary).** +- D1: T4-01 saga receipts (p0, **after F10**) + T4-08 E2E truth gates (its detector) — one + supervisor, `complex_implementation`. +- D2: TA-02 → TA-01 → TA-03a/b/c auth cluster — TA-02 first (the seam), `complex_implementation`; + security-review skill on every TA PR. +- D3: T3-02 service layout → T3-03 command kit (after RFC-B) — `complex_implementation`. +- D4: T4-02/T4-03/T4-04 runtime truth set — parallelizable across supervisors. +- Exit: T7-01 Wave-7 smoke (arms per `WAVE7-AND-AGENT-ADOPTION.md`, **after F11**). + +## 2. Harness profile per group + +Every brief starts with `use harness` and carries a `## SKILL` chapter. Group A/C/D framework +slices: archetype per touched package (`netscript-doctrine`; SDK work = Archetype 2/4, CLI = +Archetype 6, plugins = Archetype 5) + `SCOPE-service` or `SCOPE-frontend` overlay as fits; gates +from `gates/archetype-gate-matrix.md` **plus** `quality:scan` + `arch:check` (mandatory for +`packages/**`/`plugins/**` — the #745 lesson), jsr-audit for public-surface waves. Docs slices: +`SCOPE-docs` + the doc-audit pipeline (`docs_audit` Sol pass → `docs_polish` Fable pass). +PLAN-EVAL: conditional per current policy — required for RFC-A/RFC-B implementation waves and the +milestone-rename filing run; N/A for single-issue mechanical slices. IMPL-EVAL: mandatory +(this run's waiver does **not** extend to implementation runs). + +## 3. Agent-brief skeleton (per PR cluster) + +```text +use harness +## SKILL +netscript-harness, netscript-doctrine, netscript-pr, , rtk +## Contract + +## Gates (deliverables, not suggestions) + +## Boundaries + +## Trail +branch /; draft PR on first commit; per-slice PR comments; closing keyword only when +every acceptance box is truthfully tickable (close-gate #387). +``` + +## 4. Why these groups are implementation-ready + +Every draft carries current-source evidence (file:line at `fac9e339042c`), an executable +acceptance set with negative tests, explicit boundaries against the 259-issue live board, and +dependency edges that the group ordering above respects. The three verify-first rows (T4-07, +theme-island CORS, saga OOM) and the G16 service-name question are staged as verification tasks, +not implementation, so no group blocks on an unproven claim. The measurement chain +(#1102/#1201/#1197/#1090) is consumed, not duplicated, by T7-01. + +## 5. Standing constraints for every implementing agent + +No `deno.lock` deletion or cache nukes; rtk-prefixed reads; scoped wrappers for verdicts; e2e +(`deno task e2e:cli`) only at merge-readiness; canary discipline per `netscript-release`; +`agentic:leak-check` on any failed runtime session; drift → run `drift.md`, never silent. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/ISSUE-DEDUP-AND-SUPERSESSION.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/ISSUE-DEDUP-AND-SUPERSESSION.md new file mode 100644 index 0000000000..a4c73ca627 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/ISSUE-DEDUP-AND-SUPERSESSION.md @@ -0,0 +1,170 @@ +# Issue dedup & supersession — FILED + +> The owner-ratified filing completed on 2026-08-08. GitHub is authoritative; see +> `FILING-LOG.md` for Draft-ID mappings and live reconciliation receipts. + +Disposition of every remediation-relevant existing issue and every proposed new issue. +Vocabulary: **KEEP** (owner unchanged) · **EXPAND** (additive amendment, text in +`EXISTING-ISSUE-AMENDMENTS.md`) · **SPLIT** (focused child added, parent kept) · **SUPERSEDE** +(folds into the named record; close only via owner-ratified supersession comment or downstream PR +keyword — never by this run) · **MOVE** (milestone change; ledger in `MILESTONE-TRAIN.md` §4) · +**NEW** (draft in a milestone directory). Ground truth: `research/github-board-open.md` +(2026-08-08, 259 open issues). Issues not listed here are governed milestone-level by the rename +train (`MILESTONE-TRAIN.md`) and are untouched individually. + +## 1. Existing issues — dispositions + +### Scaffold / generation cluster + +| Issue | Disposition | Rationale / dependencies | +| --- | --- | --- | +| #1333 (p0, 0.0.5) scaffold frontend + app naming | **KEEP + EXPAND** | Remains the single frontend-modernization owner; acceptance detail added per pre-plan §1 (contract-first route, cache-first SDK, `withResource`, typed params, route-local groups, no-`any` consumer gate, four-seam distinction, states, `/design`). Its #1328 "Related" row is discharged (closed). T2 generators are the tooling it composes with — deliberately separate issues so #1333 stays landable in 0.0.5. | +| #1335 (umbrella, Backlog) scaffold conformance | **KEEP + EXPAND + SPLIT** | Stays the conformance umbrella; sub-issue list refreshed (#1328 closed); gains child T3-02 (service layout) and links to T2 generator drafts as conformance consumers. Never closed by a PR. | +| #1325 (0.0.5) triggers Redis glue | **KEEP + EXPAND** | Owner unchanged; audit evidence pointer attached (`runtime.stub.ts` static repro). Its generalization requirement ("a saga fix cannot ship while the trigger sibling is broken") is reinforced by T4-08's child-probing E2E. | +| #1327 (0.0.5) `db migrate` false success | **KEEP** | Complete contract already on the issue. | +| #1332 (0.0.5) DB-schema-first docs | **KEEP** | Complete; T5-01 must not overlap (Boundaries row in draft). | +| #1343 (0.0.6) installed-consumer canary proof | **KEEP** | The proper home for plugin-doctor-layout reproduction (pre-plan item G routes through it). | +| #979 / #980 (unmilestoned) port pins | **KEEP + MOVE → 0.0.8 + EXPAND** | Prerequisites (endpoint-resolving E2E, docs port passages) are delivered by T4-08/T4-06; evidence amendment attaches the stub-port findings. | + +### Streams / durable runtime + +| Issue | Disposition | Rationale | +| --- | --- | --- | +| #1326 (p0, 0.0.5) producer reconnect | **KEEP + EXPAND** | Owner unchanged (0.0.5 close-out); amendment attaches audit citations (`create-durable-stream.ts` line-level) and R2/R3 reproductions. Receipt-typing for `upsert`/`delete` (currently `void`) is *included* in its acceptance re-read — flagged in the amendment, not a new issue. | +| #1329 (p0, 0.0.5) SSE envelope | **KEEP + EXPAND** | Owner unchanged; #1326 planned as its pair (already cross-declared). | +| T4-03 storage semantics | **NEW** | Explicitly the uncovered remainder both issues' scopes exclude (persistence mode/`STREAMS_DATA_DIR`/restart proof) — pre-plan item F confirmed uncovered by the board sweep. | + +### Docs / MCP / measurement chain + +| Issue | Disposition | Rationale | +| --- | --- | --- | +| #1208 (p0, 0.0.5) tutorials ph.1 | **KEEP + EXPAND** | Amendment records the phase-2 filing obligation (promised as a checklist comment, not yet an issue — top dedup trap). | +| #1210 (0.0.6) per-API deep dives | **KEEP + EXPAND** | Gains the cross-capability golden-recipe list (pre-plan §3). | +| #1260 / #1201 / #1102 (0.0.5–0.0.6) MCP corpus/retrieval | **KEEP** | Chain untouched; T5-03/T5-04 are plumbing-only with Boundaries rows. | +| #1197 / #1090 measurement pair | **KEEP + EXPAND** | Wave-6 measured evidence attached; Wave-7 smoke *consumes* them (see `WAVE7-AND-AGENT-ADOPTION.md` §5). | +| #1275 (Backlog) migration chapter | **SUPERSEDE → #1279** | Duplicate umbrella pair; #1279 is the record. | +| #1279 (0.0.6) migration chapter | **KEEP + MOVE → 0.0.15** | Post-remediation adoption surface. | +| #1277 (Backlog) docs-site polish | **KEEP** | Not remediation-critical; untouched. | + +### Type soundness + +| Issue | Disposition | Rationale | +| --- | --- | --- | +| #1278 (0.0.6, umbrella) | **KEEP as epic-of-record + EXPAND** | Gains #1276's measured numbers (56 casts / 8 ignores / 7 allowances) + T1–T6 tranche structure; T6-01 lands inventory-C as its trackable child. | +| #1276 (Backlog, umbrella) | **SUPERSEDE → #1278** | Same 2026-08-04 directive, same evidence; two prose-only umbrellas is the board's clearest duplicate. | +| #1245 (Backlog) island query types | **KEEP + rescope (EXPAND)** | ~75% landed by merged #1265; remnant = `@throws` mismatch + regression tests + consumer migration note. Re-implementing is the risk. | +| #1249 (Backlog) `controlProps`/Zod 4 | **KEEP** | Both defects execution-confirmed current; contract complete. | + +### Plugin architecture / auth + +| Issue | Disposition | Rationale | +| --- | --- | --- | +| #922 + #923–#946 (0.0.7→**0.0.9** by rename) | **KEEP + EXPAND** | Train moves wholesale by rename; internal wave order intact; amendment maps stale beta.13/15/17 body labels to the renamed cuts and re-raises the #427/#432 re-baseline. #928's contract freeze reviews against ratified RFC-A. | +| #1093 (0.0.6) discovery hardcoding | **KEEP** | Already the right shape; RFC-A cites it as an alignment constraint. | +| #871 + #872–#887 (incl. #884/#885) | **KEEP** | Enterprise scope untouched at 0.0.14 (renamed from 0.0.12). TA drafts carry explicit Boundaries: defects/defaults only, no org contracts, no vendor adapters. | +| #934 gateway / #942 auth frontend | **KEEP** | TA-02/TA-04 align to them via Boundaries; no overlap. | +| #1243 (auth CLI port default) | **KEEP** | Named owner for the hardcoded 4437; TA drafts cite, don't absorb. | + +### Aspire / infra / release + +| Issue | Disposition | Rationale | +| --- | --- | --- | +| #1280 (0.0.6, blocked) backing health | **KEEP (blocked)** | Do not re-litigate; T4-02 is the *plugin child* liveness contract the issue explicitly does not cover. | +| #1320 (0.0.6, blocked) single Zod | **KEEP (blocked)** | Upstream-constrained. | +| #1004 / #1126 / #1163 / #1166 / #1169 (0.0.5 release machinery) | **KEEP** | 0.0.5 close-out scope; #1126's stale checkboxes get a hygiene amendment (9 children already closed). | +| #301 (Backlog, road-to-stable umbrella) | **KEEP + EXPAND** | Hygiene amendment: 5 unchecked children already closed. | +| #863 / #864 / #175 / #767 / #768 (0.0.2 stragglers) | **MOVE → owner retriage** | Explicit retriage (default Backlog); #175 additionally needs labels (zero today). Never silently closed. | + +### Epic-overlap normalization (amendments only — no membership changes) + +#823 ⊂ #327 (children #451/#453–#455): #327 becomes umbrella-of-record, #823 narrows to the +Nitro-output RFC. #892 ↔ #327/#830: mutual boundary notes. #400 ↔ #922: schedule the #427/#432 +re-baseline at 0.0.9 entry. Orphaned single-member epic labels (`epic:desktop-frontend`, +`epic:docs-cut`, `epic:telemetry-revamp`) recorded for the labels.yml parity amendment. + +## 2. Proposed new issues (41 drafts + 2 RFC documents) + +Full text: `milestones//-*.md` and `rfcs/`. Deps reference draft-IDs and live +issue numbers. Every draft carries `## Boundaries` naming adjacent owners. + +### 0.0.6 — Verification, docs truth & RFC ratification (13 drafts) + +| Draft | Title (short) | Prio | Depends on | +| --- | --- | --- | --- | +| T1-01 | rfc: SdkClientContribution tracking issue (RFC-A doc in `rfcs/`) | p1 | — | +| T3-01 | rfc: production command composition kit (RFC-B doc in `rfcs/`) | p1 | — | +| T5-01 | docs/sdk: one canonical client dialect (3 module names, 2 query APIs) | p0 | — (coord #1333/#1335) | +| T5-02 | test(docs): compile-the-docs gate replaces needle checker | p1 | T5-01 | +| T5-03 | fix(agent): emit `--docs-root` in `.mcp.json`; corpus visible | p1 | — | +| T5-04 | fix(mcp): `execute_command` version pin / local-host spawn | p1 | — | +| T5-05 | docs(reference): plugin-core pages, publish-gate path, README dialect | p2 | T5-01, T5-02 | +| T6-01 | chore(quality): `quality:scan` export-blind `any`, allowance ids, docs snippets | p1 | — (Part of #1278) | +| T6-02 | chore(ci): fresh-ui joins root check/lint; lock self-mutation | p1 | — (blocks T6-01 lock decision) | +| T6-03 | docs(doctrine): verdict refresh + `arch:check:repo` two mechanical fixes | p2 | — | + +### 0.0.7 — Typed seams + generation (12 drafts) + +| Draft | Title (short) | Prio | Depends on | +| --- | --- | --- | --- | +| T1-02 | feat(sdk): re-expose oRPC link seams (headers/interceptors/plugins/fetch/link) | p1 | T1-01, T1-04 | +| T1-03 | fix(sdk): `safe()` drops `TError`; docs example doesn't compile | p1 | T1-01 (file as Part of #1278 if its prose already names it — see ledger) | +| T1-04 | refactor(sdk): transport policy behind one owned function (pre-oRPC-v2) | p1 | T1-01 | +| T1-05 | feat(sdk/auth): auth contribution dogfood (`authClient`) | p1 | T1-01, T1-02, T1-04 | +| T1-06 | feat(sdk): trace-context as second, non-auth contribution | p1 | T1-01, T1-02, T1-05 | +| T2-01 | feat(cli): resource route-slice generator | p1 | T2-03 (hard), T2-02; RFC-A for contribution clause | +| T2-02 | feat(cli): contract-derived client/query/invalidation generator | p1 | T2-03; RFC-A clause | +| T2-03 | fix(cli): `resolveProjectRoot` app-root targeting + E2E/docs twins | p1 | — | +| T2-04 | fix(cli): `ui:add page` emits the advertised data-screen triad | p1 | T2-03 | +| T2-05 | chore(design): `/design` registry sync gate (50 vs 66) | p1 | — | +| T2-06 | fix(fresh): `crudExample` route alias defect (test asserts the bug) | p2 | — | +| T2-07 | fix(scaffold): wire `cachedAt`→`initialDataUpdatedAt`; consumer migration note | p2 | — (cites merged #1265) | + +### 0.0.8 — Runtime truth + service slice (19 drafts) + +| Draft | Title (short) | Prio | Depends on | +| --- | --- | --- | --- | +| T3-02 | fix(scaffold/service): service internal layering child (Part of #1335) | p1 | — | +| T3-03 | feat(service): command kit implementation | p1 | T3-01, T3-02 | +| T3-04 | docs: outbound webhook delivery recipe + template | p2 | T3-03 soft | +| T4-01 | fix(sagas): non-ignorable publish receipts; kill 8092 fallback | **p0** | — (seq. T4-06, T4-08) | +| T4-02 | fix(plugins): child liveness contract (workers/triggers bare; sagas partial) | p1 | T4-06, T4-08 | +| T4-03 | fix(streams): durable-storage semantics (`STREAMS_DATA_DIR`) | p1 | — (coord #1326/#1329) | +| T4-04 | fix(sagas): compensation telemetry call-sites + E2E span asserts | p1 | — | +| T4-05 | fix(plugins): WORKER_CONCURRENCY mismatch; always-throwing root exports | p2 | — | +| T4-06 | fix(plugins): pre-randomization port hardcodes in stubs + consumer stub | p2 | #979 prereq | +| T4-07 | verify(aspire): ServiceReferences injection claim (counter-evidence found) | p1 | — verify-first | +| T4-08 | test(e2e): child + streams probes; compensating/COMPENSATED status truth | p1 | T4-02, T4-06 | +| TA-01 | fix(scaffold): `/api` protected by default | **p0** | TA-02, T1-05 | +| TA-02 | fix(plugin): `createPluginService` auth seam (incl. remote authenticator port) | **p0** | — | +| TA-03a | fix(auth): signout revokes arbitrary session ids | **p0** | TA-02 | +| TA-03b | fix(auth): signin/callback discard `Set-Cookie` | **p0** | — | +| TA-03c | fix(service): default CORS `origin:'*'` vs credentialed calls | p1 | — | +| TA-04 | feat(service): typed principal + `$meta` policy metadata | p1 | — (consumed by T1-05/#934; prereq for #884) | +| TA-05 | test(e2e): authenticated + rejection auth gates | p1 | TA-01/02/03a | +| T7-01 | verify(0.0.8): Wave-7 measured adoption smoke (exit gate) | p1 | T2-01/02, T4-01, TA-01, #1197, #1090 | + +## 3. Stage-E ledger — cross-pack items the packs flagged for supervisor decision + +1. **Corpus corrections that must reach the master plan:** T4-07's wave-6 claim has worktree + counter-evidence (drafted verify-first, never implementation); sagas runner *does* get a + generated health check (workers/triggers don't) — T4-02 written against the corrected surface; + auth arch-debt anchors exist in `.llm/harness/debt/arch-debt.md` (audit G13 first half false — + no issue); `#1278` Inventory A is ~80% discharged and Inventory D counts 6 (not ~19) exempt + soundness tests; `quality:scan:repo` already covers all packages (the gap is export-blind + `any` + free-text allowances, not scope); `arch:check:repo` red = 52 A14 false positives + 1 + no-`--root` config bug — two cheap fixes, not package debt. +2. **Ownerless items needing a home (owner decision):** server-side plugin seam + (`PluginContractRouter = object`, Hono-vs-oRPC middleware, G5/G8/G9) — recommend a 0.0.7 + companion draft or an explicit deferral; saga compensation *semantics* (no prior-step rollback, + unpersisted compensation state, silent missing-handler on primary path) — candidate T4-09; + dead `VALIDATE_TRACES_SCRIPT` + lost `validateOtlpExporterEndpoint()` (GAP-6) — p2 tooling row; + harness read-first paths that don't exist (D12) — cheap fix, suggest fold into T6-03 at filing; + MCP generation-surface tools (`list_generators`…) — defer to #1126/#1201 chain decision. +3. **Cross-pack dependency:** `ServiceQueryUtils` context/`TError` narrowing (S10) lives in T2's + query work but bites T1-05/T1-06 at the TanStack layer — sequencing note for the handoff. +4. **Filing-time checks:** T1-03 files as `Part of #1278` if its live prose already names + `safe`/`isDefinedError`; #451 gets an unblocked-by-T1-02 amendment; #1263 cross-references + T1-03 (server-side twin); oRPC 1.14.15 bump folded in T1-04 acceptance (split if owner + prefers a deps issue); TA-03a's "CLI sends a credential" box may move to #1243 as amendment. +5. **Verify-first rows carried (no drafts):** theme-island CORS, saga OOM, plugin-doctor layout + (via #1343), `AUTH_API_SERVICE_NAME` `'auth-api'` vs `'auth'` runtime resolution (G16). diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/MASTER-PLAN.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/MASTER-PLAN.md new file mode 100644 index 0000000000..024cd11a57 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/MASTER-PLAN.md @@ -0,0 +1,151 @@ +# NetScript long-range remediation — MASTER PLAN — FILED + +> Owner-ratified filing completed 2026-08-08. GitHub is authoritative; see `FILING-LOG.md` for +> live issue numbers and milestone receipts. This document remains the planning rationale. + +Seed run `plan-fable5-remediation-roadmap--seed` · PR #1347 · baseline `origin/main` @ +`fac9e339042c` (re-verified unchanged at plan lock, 2026-08-08). Supervisor: Claude Fable 5 · +high. PLAN-EVAL and IMPL-EVAL owner-waived (drift D-2); the owner personally reviews this plan. + +This is the integrating document. Detail lives in: `SYNTHESIS.md` (evidence synthesis), +`ISSUE-DEDUP-AND-SUPERSESSION.md` (per-issue dispositions + 41 new drafts + Stage-E ledger), +`MILESTONE-TRAIN.md` (train + moves + entry/exit), `WAVE7-AND-AGENT-ADOPTION.md`, +`EXISTING-ISSUE-AMENDMENTS.md`, `milestones/*/` (complete issue drafts), `rfcs/` (RFC-A, RFC-B), +`IMPLEMENTATION-HANDOFF.md`, and the cited corpus under `research/`. + +## 1. The product bar + +A credible production meta-framework in 2026 must clear (evidence: +`research/external/meta-frameworks.md`): an end-to-end typed data story at the cache-coherence +frontier (SvelteKit remote functions, TanStack Start serialization checks — the two designs our +oRPC seam is measured against); scaffold/CLI generation that emits the idiomatic app, not a +counter stub; a first-party auth story that composes with the typed client; durable background +work with causal proof; observability that follows one request across every boundary; and an +agent-native surface (docs corpus + MCP + generated conventions) that measurably changes agent +behavior. + +**NetScript's genuine differentiation, ranked** (same corpus): (1) first-party saga/compensation +orchestration — unowned by every JS meta-framework surveyed; (2) Aspire as a non-proprietary +local orchestration graph ("Encore's dev experience without Encore's cloud"); (3) plugin-seam +uniformity as the carrier for agent-teaching plugins; (4) the portable oRPC/OpenAPI contract as +the reason the agent story works; (5) Deno single-toolchain. The remediation program exists to +make the table-stakes true so the differentiation is believable. + +## 2. Principles (owner-ratified inputs, now evidence-hardened) + +1. **Generation over prose.** Six waves prove instruction does not transfer; the only untested + lever that worked was making the right thing the emitted thing (`SYNTHESIS.md` §1). +2. **Types over convention.** Arbitrary `any`/casts in route code are unacceptable; the no-`any` + gate extends to *consumer* output, not just framework exports. +3. **Composable seams over escape hatches.** Auth gets no bespoke hook; it dogfoods the generic + `SdkClientContribution` chain (RFC-A), proven general by a second non-auth contribution. +4. **Runtime truth over green wrappers.** Receipts are non-ignorable, children report liveness, + durable claims survive restart, and every causal claim has a trace that fails if the seam is + removed. (The fourth leg this run adds to the pre-plan's three.) +5. **Current GitHub wins over carried-in reports** — enforced throughout; six corpus corrections + from source re-verification are recorded in `ISSUE-DEDUP-AND-SUPERSESSION.md` §3.1. + +## 3. Current state in one paragraph + +The board holds 259 open issues across 13 open milestones; 0.0.5 is mid-canary (canary.16 green) +with its continuation plan ~40% delivered and four p0s undispatched. The web layer's builder +surface is complete and nearly cast-free, but the scaffold demonstrates none of it; the CLI has +no generator for the canonical slice and its one page verb emits a counter into the wrong tree; +the SDK client is sealed (auth cannot compose; oRPC's machinery is hidden, not missing); docs +teach three names for the client module and two query dialects; runtime plugins can report green +while children are dead, receipts are droppable, and "durable" streams are in-memory; the docs/ +MCP discovery chain is structurally unwired (`--docs-root` never emitted). Full evidence: +`SYNTHESIS.md` §1–2, `research/repo-audit/*`. + +## 4. Target architecture (the five contracts the program lands) + +1. **The typed extension chain (RFC-A).** `SdkClientContribution`: one versioned, typed chain + extending client construction, request context, credentials/headers, transport middleware, + procedure policy metadata (oRPC `$meta`), error types, query factories and invalidation. + Mostly *unhides* oRPC 1.14.6 machinery. Host-app usable without plugins; compile/config-time + failure on absence/version-mismatch/conflict; auth first consumer, trace-context second. +2. **The canonical vertical slice, generated.** DB-derived schema (where present) → API contract + → typed route contract + params/search → generated client/query/invalidation module → + `definePage` composition root → `withResource`/layers → forms/partials/streams → route-local + `(_components)/(_islands)/(_shared)/(_lib)` → Fresh-UI states → tests that reject `any`/raw + fetch/manual parsing. Emitted by `ui:add` slice mode + the client generator; #1333 makes the + default app the exemplar. +3. **The flexible service slice + command model (RFC-B).** Collapsible + `domain/application/ports/adapters/routers/auth` vocabulary; transactional commands with + expected-version, idempotent receipts, audit+outbox in one commit; `service add-handler` + places into the slice; telemetry carries a command vocabulary. +4. **Runtime truth.** Non-ignorable publish receipts; endpoint discovery that errors instead of + guessing; child-process liveness in the health surface; explicit stream persistence modes; + compensation visible in traces; E2E gates that probe children and assert spans. +5. **The agent-native surface.** One docs dialect, compiled docs snippets, MCP corpus wired by + default, generation verbs discoverable, measured adoption (Wave-7) as the program's exit + criterion. + +## 5. Dependency DAG (program level) + +```text +0.0.5 close-out (existing scope only) + └─> 0.0.6 RFC-A ratify ──────────────┬─> 0.0.7 T1 seam impl ─┬─> 0.0.7 T1-05/06 dogfoods + RFC-B ratify ──────────────┼───────────────────────┼─> 0.0.8 T3-03 command kit + T5 docs dialect + gates ───┤ 0.0.7 T2 generators ─┴─> 0.0.8 T7-01 Wave-7 smoke + T6 quality/hygiene gates ──┘ │ ▲ + (T5-01 dialect choice feeds T2-02 naming) │ + 0.0.8 T4 runtime truth + TA auth defects + T3-02 service slice ────────┘ + (T4-06/T4-08 are #979's prerequisites; TA-02 precedes TA-01; T4-01 sequenced with T4-08) +0.0.9 (renamed) #922 frontend-contrib — #928 contracts reviewed against ratified RFC-A +``` + +Intra-milestone edges are on every draft header and in `ISSUE-DEDUP-AND-SUPERSESSION.md` §2. +The only cross-pack sequencing hazard is recorded in §3.3 there (`ServiceQueryUtils` narrowing +lives in T2 but bites T1-05/06). + +## 6. Sequencing and release mechanics + +Per `MILESTONE-TRAIN.md`: two inserted milestones via the house rename pattern (verified twice in +board history); every existing issue retained; five explicit per-issue moves; canary-first +publishing per `netscript-release` throughout; Wave-7 verdict gates the train past remediation. +No `wave:*` labels (dead system); no semver jump — a "0.1.0" claim is exactly the Wave-7 GO. + +## 7. Owner-fork sweep (numbered; none silently taken) + +| # | Fork | Default proposal (reversible) | +| --- | --- | --- | +| F1 | Insert two milestones (rename shift of 0.0.7→0.0.13 up two) vs pack remediation into 0.0.6/0.0.7 | **Insert** — keeps #922's nine-p0 path unmixed | +| F2 | #922 before vs after remediation cuts | **After** (new 0.0.9); #928 contract freeze reviews against ratified RFC-A | +| F3 | Fold #1276→#1278 and #1275→#1279 (close the Backlog duplicates) | **Fold** — amendment text ready; closes happen only on owner action | +| F4 | RFC mechanism: issue-hosted (#1123 precedent) vs first-ever `rfcs/NNNN` file | **Issue-hosted**, divergence from `rfcs/README.md` recorded (zero file RFCs exist on main) | +| F5 | 0.0.2 stragglers (#175/#767/#768/#863/#864) destination | **Backlog + labels fixed** — explicit retriage, no closes | +| F6 | #1279 (migration chapter) leaves 0.0.6 | **→ 0.0.15** — post-remediation adoption surface | +| F7 | Canonical client dialect: module name + ONE query API (`createQueryFactories`+KV vs `createServiceQueryUtils`) | T5-01 proposes `lib/.ts` + query-factories path; **owner ratifies the dialect** before docs rewrite | +| F8 | Server-side plugin seam (`PluginContractRouter = object`, Hono-vs-oRPC middleware) | **Defer** to RFC-A unresolved-questions; revisit at 0.0.7 planning (currently ownerless) | +| F9 | Saga compensation *semantics* (no prior-step rollback, unpersisted state) — file T4-09 now vs after T4-01 evidence | **After T4-01** lands its verification evidence | +| F10 | T4-01 receipt mechanism: compiler-forced discrimination vs throw-on-rejection + usage gate | Draft presents both; **owner picks at ratification** | +| F11 | Wave-7 scale: 3 arms × 2 runs × frontier models (cost) | **Approve as designed**; trimming arm B (init-gate lever) is the acceptable cut | +| F12 | oRPC 1.14.15 bump: folded into T1-04 vs standalone deps issue | **Folded** (patch-level, identical export surface) | + +## 8. Risk register + +| Risk | Mitigation | +| --- | --- | +| Duplicate filing against 9 prose-only umbrellas + promised-but-unfiled #1208-ph.2 | Every draft has `## Boundaries`; dedup table §1/§2; filing happens once from a manifest | +| Re-implementing landed work (#1245→#1265, #1328, #1184) | Dispositions cite merge SHAs; drafts cite current source, not wave-era observations | +| Milestone-rename blast radius (~150 issues' display) | House pattern (title-only rename, zero per-issue mutation) + move ledger | +| RFC-A over-design | Constraint written into the RFC: unhide oRPC, don't parallel it; two-consumer proof required | +| 0.0.5 scope creep | Train rule: no new scope enters 0.0.5; remediation starts at 0.0.6 | +| Corpus staleness at filing time | Stage-H filing (later, owner-ratified) re-verifies issue states before mutation; GitHub wins | +| Program stalls mid-train | Each cut has entry/exit criteria; Wave-7 verdict is a go/no-go, not a vibe | + +## 9. Explicit exclusions (this plan deliberately does not touch) + +Enterprise-auth vendor scope (#871 children, incl. #884/#885 — TA drafts are defects/defaults +only); deployment/process-manager/desktop epics (#327/#510/#830/#892/#823 — normalization +amendments only); Dev Dashboard (#400, paused); AI stack (#238) beyond the trace-context +contribution; docs-site visual polish (#1277); migration-chapter authoring (#1279, moved late); +blocked upstream items (#1280, #1320); `wave:*` label revival; any semver-minor jump; and all +GitHub board mutation, which remains owner-gated Stage H. + +## 10. What ratification unlocks (pointer) + +`IMPLEMENTATION-HANDOFF.md` names the first executable PR groups, their harness profiles, gates, +and agent briefs. Nothing in it launches until the owner ratifies the forks above and the filing +manifest is executed by a later, separately-authorized run. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/MILESTONE-TRAIN.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/MILESTONE-TRAIN.md new file mode 100644 index 0000000000..6530ccf11c --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/MILESTONE-TRAIN.md @@ -0,0 +1,107 @@ +# Milestone train — FILED + +> Executed after owner ratification on 2026-08-08. GitHub is authoritative; see `FILING-LOG.md` +> for the final milestone-object mapping and reconciliation receipts. + +Proposed release train for the long-range remediation program. Grounded in the live board +snapshot (`research/github-board-open.md`, 2026-08-08: 259 open issues, 13 open milestones) and +the house milestone-shift pattern verified twice in `research/github-board-history.md` §10 +(rename-in-place highest→lowest preserving number/state/description, then create the freed title; +per-issue written move reasons; pre/post tables). **Every existing issue is retained; whole +milestones move by rename, so zero per-issue mutation is needed for the shifted content.** + +## 1. Train at a glance + +| Cut | Name / theme | Content | Provenance | +| --- | --- | --- | --- | +| **0.0.5** (current, mid-canary) | Close-out as already scoped | The 21 open issues incl. the undispatched W2–W5 remainder (#1326+#1329 pair, #1333, #1208 ph.1, #1338…). **No new scope enters.** | existing | +| **0.0.6** | **Verification, docs truth & RFC ratification** | Existing 22 (#1343, #1210, #1260, #1201, #1278 amended, #1093, #1280/#1320 blocked…) + new: RFC-A + RFC-B tracking issues (T1-01, T3-01), canonical-dialect docs fixes (T5-01…T5-05), soundness/hygiene gates (T6-01…T6-03). Move out: #1279 → 0.0.15 (see §4). | existing + drafts | +| **0.0.7 (new)** | **Typed seams + generation** | T1-02…T1-06 (oRPC re-exposure, typed errors, transport consolidation, auth + second contribution dogfoods), T2-01…T2-07 (route-slice generator, client/query generator, `resolveProjectRoot`, `ui:add` triad, `/design` sync, defect fixes). | new drafts | +| **0.0.8 (new)** | **Runtime truth + service slice** | T4-01…T4-08 (saga receipts p0, child liveness, stream durability, saga spans, env/port fixes, E2E child gates), TA-01…TA-05 (auth defects + generated-path defaults), T3-02…T3-04 (service layout, command kit, webhook recipe), Wave-7 measured smoke as the exit gate. #979/#980 join here (their E2E/docs prerequisites are T4-06/T4-08). | new drafts + 2 unmilestoned | +| **0.0.9** (was 0.0.7) | Frontend contribution layer | #922 + #923–#941 intact (9 p0 critical path unchanged internally). | rename | +| **0.0.10** (was 0.0.8) | as currently scoped (48) | unchanged membership | rename | +| **0.0.11** (was 0.0.9) | as currently scoped (15, incl. #944) | unchanged | rename | +| **0.0.12** (was 0.0.10) | as currently scoped (2) | unchanged | rename | +| **0.0.13** (was 0.0.11) | as currently scoped (10, incl. #942/#943) | unchanged | rename | +| **0.0.14** (was 0.0.12) | enterprise-auth contracts band (#884/#885 et al., 11) | unchanged | rename | +| **0.0.15** (was 0.0.13) | horizon band (44: process-manager/deploy-plugin tail) + #1279 | rename + 1 move | + +## 2. Why insert exactly two milestones + +- **Dependency shape.** RFC-A/RFC-B ratify in 0.0.6 (docs/verification cut — cheap, already + half-full of verification work). Their implementations are the only content of new-0.0.7/0.0.8, + so the remediation program never mixes with #922's nine-p0 critical path — which was the + pre-plan's explicit constraint ("future plugin/auth milestones shift only as part of a coherent + dependency graph; never silently absorbed", `research/preplan-package.md` §Milestone). +- **Coherence for #922.** #928 (contracts/v1) freezes plugin contribution contracts. RFC-A + defines the *SDK/client* contribution axis. Landing RFC-A first (0.0.6 ratify → 0.0.7 + implement) lets #928's envelope be reviewed against it instead of forcing a v2 envelope later. +- **Wave-7 placement.** The measured unfamiliar-agent smoke is the *exit gate of 0.0.8*: the + program's thesis (generation changes agent behavior) is falsifiable exactly once the + generators + runtime truth exist. Advancing to 0.0.9 (#922) without that proof repeats the + capability-present-not-activated failure the corpus measured six times. + +## 3. Rename execution order (for the filing stage, owner-ratified only) + +Per the house pattern (highest→lowest so titles never collide; title is the only field changed): + +```text +0.0.13 → 0.0.15 0.0.12 → 0.0.14 0.0.11 → 0.0.13 0.0.10 → 0.0.12 +0.0.9 → 0.0.11 0.0.8 → 0.0.10 0.0.7 → 0.0.9 +then create: 0.0.7 "Typed seams + generation", 0.0.8 "Runtime truth + service slice" +``` + +Milestone descriptions get an authority banner + one-line theme; no due dates (house norm: none +exist today). **This run executes none of this.** + +## 4. Per-issue moves (the complete list — everything else moves by rename or stays) + +| Issue | From → To | Reason | +| --- | --- | --- | +| #1279 (migration chapter, umbrella) | 0.0.6 → 0.0.15 | Marketing/adoption surface, not remediation; #1275 duplicate folds into it first (amendments). | +| #979 (plugin API port pins) | none → 0.0.8 | Its two prerequisites (endpoint-resolving E2E gates, docs port passages) are T4-08/T4-06 work in 0.0.8. | +| #980 (`service add` port pin) | none → 0.0.8 | Sibling of #979, same prerequisite chain. | +| #1000 (docs) | none → Backlog / Triage | Untriaged; hygiene amendment adds labels. | +| #175, #767, #768, #863, #864 | 0.0.2 → owner retriage (default Backlog) | 0.0.2 shipped long ago; #175 has zero labels. Explicit retriage, never a silent close. | + +New drafts are filed directly into their §1 milestones at ratification time. + +## 5. Entry/exit criteria per remediation cut + +- **0.0.6 entry:** 0.0.5 stable cut green (canary-pair doctrine, `netscript-release`). **Exit:** + RFC-A + RFC-B ratified (tracking issues closed as accepted); docs speak ONE client dialect and + the compile-the-docs gate is live; #1343 installed-consumer proof green; #1278 guard rail + fail-closed. +- **0.0.7 entry:** RFC-A accepted. **Exit:** a scaffolded app + one added service reach a typed, + cache-first, auth-composable page **entirely through generated modules** (no hand-written + client/query wiring), with the no-`any` consumer gate green; second-run generator byte-identity + proven. +- **0.0.8 entry:** 0.0.7 generators shipped in a canary. **Exit:** saga publish/compensation + causally provable from persisted state + one correlated trace; child-liveness states visible; + durable-stream restart proof; **Wave-7 measured smoke shows the generated path adopted or + explicitly rejected by an unfamiliar agent** (see `WAVE7-AND-AGENT-ADOPTION.md`). +- **0.0.9+ entry:** Wave-7 verdict recorded. #922 proceeds on its own already-planned wave + structure. + +## 6. Epic-overlap normalization (amendments, not moves) + +Recorded here because the train depends on epic scopes staying disjoint; full text in +`EXISTING-ISSUE-AMENDMENTS.md`: + +- #823's entire open membership (#451/#453–#455) sits inside #327's child list → amendment + declares #327 the umbrella-of-record for those four, #823 narrowed to the Nitro-output RFC. +- #400 ↔ #922: #427/#432 "KEEP-and-re-baseline" from the #890 RFC supersession map has never + been executed → amendment schedules the re-baseline at 0.0.9 entry. +- #892 vs #327/#830: no cross-reference in either body → amendment adds mutual boundary notes. +- #922 body wave labels (beta.13/15/17) vs actual milestones → additive clarification comment + mapping waves to the renamed train (0.0.9/0.0.13/0.0.15). +- Duplicate umbrellas: #1276 → folds into #1278 (0.0.6, epic-of-record); #1275 → folds into + #1279 (moves to 0.0.15). + +## 7. What this train deliberately does not do + +No issue is closed by this plan (folds happen via owner-ratified supersession comments; closes +only via downstream PR keywords). No epic absorbs another epic's children. 0.0.5 membership is +untouched. The `wave:*` label system stays dead (conventions corpus: zero recent usage) — the +train uses milestones only. Semver stays 0.0.x; no minor-version jump is proposed until the +0.0.8 exit proof exists (a credible "0.1.0" claim is exactly the Wave-7 verdict). diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/QWEN-FINAL-ADVERSARIAL-EVAL.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/QWEN-FINAL-ADVERSARIAL-EVAL.md new file mode 100644 index 0000000000..43548b101e --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/QWEN-FINAL-ADVERSARIAL-EVAL.md @@ -0,0 +1,226 @@ +# QWEN FINAL ADVERSARIAL ACCEPTANCE EVALUATION — RFC-A (#1390) × RFC-B (#1389) + +## 0. Identity and transport + +| Field | Value | +| --- | --- | +| Evaluator | Qwen 3.8 Max (`openrouter/qwen/qwen3.8-max`), fresh session, maximum reasoning effort | +| Role | Final independent adversarial acceptance reviewer (root-orchestrator-owned pass; separate from generator Codex and cycle-1/2 evaluator Claude Fable 5) | +| Date | 2026-08-08 | +| Transport | OpenCode CLI on Linux; direct `git`/`gh`/registry/web access from the evaluation environment | +| Mutation boundary honored | This session wrote **only** this report. No RFC text, fixture, harness artifact, product code, branch, PR, label, comment, issue, milestone, or merge state was touched. No subagents or rival evaluators were launched. | + +## 1. Exact evaluated SHAs + +| Object | SHA | Verified | +| --- | --- | --- | +| RFC-A accepted content (`rfcs/0000-sdk-client-contributions.md`) | **`78a7cecd1d5eaafa7a65bc25a21af497567128dc`** | `git show` in `/home/codex/repos/ns-rfc-sdk-client`; RFC text byte-identical at branch HEAD (diff content-commit→HEAD touches only `.llm/runs/` artifacts) | +| RFC-A branch HEAD (cycle-2 verdict artifact) | `14b5c858cbead4aabe06e991528d8e9eaaaca7dc` | matches live `gh pr view 1390 .headRefOid` | +| RFC-A fixture | `packages/sdk/tests/type-fixtures/sdk-client-contributions-rfc_type.ts` @ `14b5c858c` | read in full | +| RFC-B accepted content (`rfcs/0000-command-composition-kit.md`) | **`c98c08adabbd992a557ff7c596deae68b9c9cd62`** | `git show` in `/home/codex/repos/ns-rfc-command-kit`; RFC text byte-identical at branch HEAD | +| RFC-B branch HEAD (cycle-2 verdict artifact) | `57b51128fbc28187b55e1e377cd3e1777332dfa1` | matches live `gh pr view 1389 .headRefOid` | +| Cross-RFC brief | `CROSS-RFC-REVIEW.md` @ `cb0ca256e` in `/home/codex/repos/netscript-fable5-remediation-plan` | read in full | +| Product baseline | `origin/main` @ `fac9e339042c5394bf882311657d8981d353a1c3` | merge-base of both RFC branches; both worktrees clean | + +## 2. Evidence read and executed + +**Read (complete):** both RFCs at accepted SHAs (1,611 + 1,966 lines); both `plan-eval.md` histories (cycle 1 findings F-A1–F-A10 / F-B1–F-B7 + cycle-2 resolution tables); RFC-A committed type fixture (503 lines); both `final-handoff.md`; RFC-A `drift.md` (via cycle-2 artifact set); both cycle-2 worklog gate tables; `CROSS-RFC-REVIEW.md`; seed-run `MASTER-PLAN.md`, `ISSUE-DEDUP-AND-SUPERSESSION.md`, `FILING-LOG.md`; `rfcs/README.md` (RFC process); `0000-template.md` presence; netscript-pr skill label/lifecycle sections; `.github/labels.yml` status taxonomy; doctrine `02-public-surface.md` sanctioned oRPC-types exception. + +**Executed (independent, not taken from prior evaluators):** + +- `deno check --unstable-kv packages/sdk/tests/type-fixtures/sdk-client-contributions-rfc_type.ts` → **exit 0** (third independent execution of this gate). +- Live registry queries (npm): `@orpc/client` / `@orpc/server` dist-tags `latest=1.15.0`, `beta=2.0.0-beta.26`; publish timeline (v1.15.0 at 2026-08-08T13:52Z, **after** beta.26 at 11:21Z; seven v1 patches since 2026-07-25); `@orpc/opentelemetry` dist-tags `latest=1.14.11`, `beta=2.0.0-beta.26` (v1 line confirmed). +- Primary upstream fetch: official v1→v2 migration guide (`v2.orpc.dev/docs/migrations/from-v1`) — verified wire incompatibility, middleware-dedup removal, `.$meta`→`defineMeta`, GET rejection by default (`allowMethods` = POST/PUT/PATCH/DELETE), `Sec-Fetch-Mode` CSRF plugin, `status`→`errorStatusMap` split, `safe()` result-shape change, `isDefinedError`→`isInferableError`, serializer-instance change, `@orpc/otel`→`@orpc/opentelemetry`, `RequestHeadersPlugin`→`RequestHeadersHandlerPlugin`, Zod-v4-only `@orpc/zod`, TanStack package consolidation. +- Live GitHub: PR #1390/#1389 state/labels/head/mergeable/body/comments; GraphQL review threads (**0 total / 0 unanswered on both**); check-run state at both HEADs (all lanes `skipped` — path-filtered docs-only); full issue sweep of #1348–#1388 (titles/states/milestones/labels), plus #451, #1093, #1278, #1293, #1347, #1320 context; `rfc`-labelled issue list; PR #822 file list (no `rfcs/` files → no numbering race). +- Worktree source verification at baseline (every load-bearing RFC claim spot-checked): closed 9-field `CreateServiceClientOptions` with reserved no-op `port`/`timeout`; context-less `ServiceClientMethod`; `inferRPCMethodFromContractRouter` at `http-client-link.ts:17/:82` (GET live today); GET-only dedupe filter; `retry: 0` default; trace injection at `:90-91`; `baseContract: ReturnType` erasure at `contract-primitives.ts:81`; six-code `commonErrorMap`; current erased `BaseContractErrors = MergedErrorMap, ErrorMap>`; `'~orpc'` accessor in `ContractProcedureLike`; `withTransaction()` root-client assertion (`database/mod.ts:128`); 5-value `IsolationLevel` incl. MSSQL `Snapshot`; `WorkerIdempotencyPort` "exactly-once-effective" comment (`:30`); MySQL adapter `SNAPSHOT` (`:480`); queue adapter runtime `ensureSchema`/`CREATE TABLE IF NOT EXISTS` (`:296-348`); telemetry `netscript.correlation.id` (`messaging.ts:15`) and `netscript.idempotency.key` (`saga.ts:12`); Desktop `createORPCClient` + MessagePort `RPCLink` (`desktop/application/desktop-rpc-client.ts:1-32`); `packages/service/deno.json` has **no** `@netscript/database` dependency (new edge genuinely new); `plugin-contributions.ts` closed `'auth-backend'` doctor literal, no `sdkClients` group; `create-service-query-utils.ts:63` cast fast path; oRPC procedure context today is `context: {}` (`service/src/primitives/handlers.ts:126/:156`) with `principal` only in the Hono middleware bag; Prisma 7.8.0 in lock; zod 3.25.76 + 4.4.3 coexisting in lock; `deno.lock` oRPC family pinned 1.14.6; `rfcs/` on main contains only template+README (zero numbered RFCs). + +## 3. Severity-ranked findings table + +Prior evaluators' artifacts were treated as evidence only; every finding below was re-derived against current source/live state. + +| # | Sev | RFC | Finding | Evidence / disposition | +| --- | --- | --- | --- | --- | +| Q1 | **Low (advisory, board)** | B | Guide-level envelope derivation uses `context.principal.*`, `context.correlationId`, `context.traceHeaders`, `context.signal` inside the oRPC handler, but today's oRPC procedure context is `context: {}` (`handlers.ts:126/:156`); `principal` exists only as an untyped Hono bag entry — exactly the defect #1387 (TA-04) is filed to fix. RFC-B's issue decomposition never cross-references #1387/#1383. | Not a design blocker: the kit contract is actor-source-agnostic (caller supplies `CommandEnvelope.actor`; system actors explicit; absence never anonymous). **Repair:** amendment on #1363 (and backlink on #1387) naming the typed-principal shape as the consumer dependency for the authenticated envelope path, so Stage-8 generated handler examples compile against a real surface. | +| Q2 | **Low (advisory, board)** | A | #1350's live body is exactly the `safe()`/error-map repair (verified) and does **not** own `NetScriptProcedureMeta` initialization. RFC-A Stage 1b metadata therefore has no owner until the Stage-0 decision (widen #1350 vs dependent child) is recorded. | Correctly staged by the RFC (Stage 0; FCP Q6), but it is a **hard downstream edge**: #1352 auth dogfood cannot ship without the metadata vocabulary. Must be recorded at/before numbering, not left to FCP drift. | +| Q3 | **Info** | A | v2 gate list does not explicitly name the `@orpc/zod` **Zod-v4-only** requirement (verified in migration guide) while the repo lock carries both zod 3.25.76 and 4.4.3 and #1320 (single Zod) is blocked upstream. | Fold into the v2 epic scope row (schema/OpenAPI parity). RFC-A's gate list is a stated minimum, so this is an addition, not a defect. | +| Q4 | **Info** | A | The `74 non-test / 91 total` oRPC file count is the worklog's recorded scan; my independent quick greps with different exclusion rules produced 39–56. Direction (broad cross-package footprint) confirmed; exact count is scan-definition-dependent. | No action; the number is motivation, not a gate. | +| Q5 | **Info** | A+B | `CommandTelemetryStart.idempotency` is `'claimed' \| 'not_requested'` while result/attribute vocabulary adds `'replayed'/'missing'/'mismatch'/'busy'`. Coherent because the span starts before the claim resolves (algorithm step 3 precedes step 5) and final attributes come from `finish(result)` — but the asymmetry deserves one doc line at implementation. | Implementer note, not an RFC defect. | +| Q6 | **Info (process)** | A+B | All CI check runs at both HEADs are `skipped` (path-filtered docs-only + `ci:skip-*` labels, ruled valid by cycle 2 — diff is RFC + run artifacts + one compile-only fixture, no package/lock/generator change). The **only** compile evidence is the recorded fixture gate; re-executed here (exit 0). | Acceptable at RFC bar; root orchestrator should know no CI lane compiled these branches. | +| Q7 | **Info (process)** | A+B | Frontmatter `target-milestone` convention differs (A: 0.0.7 = implementation; B: 0.0.6 = ratification, footnoted). Both are factually consistent with the live board (verified: #1349–#1353 in 0.0.7; #1361/#1348 in 0.0.6; #1362–#1364/#1363 in 0.0.8; #1350 in 0.0.7). | Harmonize convention at numbering. | +| Q8 | **Info (process)** | A+B | MASTER-PLAN fork F4 recorded "issue-hosted" as the default, but execution followed the canonical file-based process of `rfcs/README.md` (file + companion tracking issue). These are the first file RFCs (`rfcs/` on main has none); PR #822 adds no `rfcs/` file, so no numbering race. | Retire the F4 divergence note at numbering; maintainer chooses whether RFC-A or RFC-B receives 0001. | +| Q9 | **Info (hygiene)** | A | PR #1390 body DoD checkbox "Formal cycle-2 verdict is recorded" remains unchecked although the APPROVED verdict comment (19:01:57Z) and artifact exist; body phase line still says `status:plan-eval` while the live label is `status:augment-review`. | Cosmetic; fix at FCP prep. | + +**No critical or major findings.** All 17 cycle-1 findings (F-A1–F-A10, F-B1–F-B7) were mechanically re-checked against the accepted content and are **confirmed resolved** — see §5. + +## 4. Individual verdicts + +### 4.1 RFC-A — Typed SDK client contributions (PR #1390): **PASS_ACCEPT** + +The RFC is decision-complete at the RFC bar: + +- **Seam law is closed.** Descriptor protocol (`family/major` closed at 1), id grammar/limits, context declaration ↔ TypeScript required/optional bijection, `headerKeys` as reserved-output subset, mandatory `responseCache` trichotomy, sequential prepare with deterministic first failure, duplicate rejection with named conflict markers, and order-independence (valid contributions commute) are all normative with runtime re-validation for JS/widened/plugin boundaries. +- **Compatibility is proven, not asserted.** Every widened public generic carries an explicit default (normative table); the committed in-tree fixture compiles real `ContractLike`/`defineServices`/`ServiceQueryUtils`/key/desktop surfaces, pins the exact 3-tuple default server key, the 5-tuple partitioned key, direct-only omission, required-context call/query arguments, duplicate-context diagnostic, and the 16/17 budget — and I re-executed it to exit 0. +- **The hard lifecycle cases are settled.** Prepare-once-per-epoch with byte-equivalent replay across unary retries; iterator-phase reconnect = new epoch with exactly one re-preparation (credential rotation fixture mandated); abort semantics; dedupe header-safety; desktop MessagePort rejection (type + runtime + generator); reserved framework keys excluded from the contribution projection; private ports located, unexported, and absence-gated via `deno doc` + packed-consumer negatives. +- **Security model is explicit.** Redaction list, partition non-secret law, cleartext/loopback bearer rules, redirect/cookie/CORS limitations, input-as-borrowed-data duty, and the metadata-guides-but-does-not-enforce boundary. +- **Upstream boundary is honest.** Zero-oRPC gate scoped to new RFC-A + generated declarations under a non-growing #1350/#1278 allowlist (doctrine's sanctioned oRPC-types exception verified); stable-v1 first adapter; v2 fenced into its own RFC/spike with a complete gate matrix. + +Residual decisions are the eight FCP questions, all policy-safe under my own adjudication (§7), with Q6 (metadata ownership) carrying the §3-Q2 recording obligation. + +### 4.2 RFC-B — Production command composition kit (PR #1389): **PASS_ACCEPT** + +The RFC is decision-complete at the RFC bar: + +- **The atomicity claim is exactly one store commit**, enforced by construction: `sideRecordAtomicity: 'same_commit'` is a construction invariant, all four side-record delegates derive from the transaction callback's `TTx`, `callbackAttempts: 'one'` forbids adapter replay, and the refusal boundary rejects every cross-store/network-in-transaction shape rather than weakening the promise. +- **The previously-silent boundary decisions are now normative.** Per-provider receipt-claim algorithms (PG `ON CONFLICT DO NOTHING RETURNING` + transaction-local `lock_timeout` save/restore, `55P03`→busy, `40001` retryable; MySQL savepoint recovering **only** 1062 with `INSERT IGNORE` explicitly forbidden, session `innodb_lock_wait_timeout` restore-or-discard; MSSQL `UPDLOCK/HOLDLOCK` on the named generated unique index with `SET LOCK_TIMEOUT` restore; busy callback-terminal; every timeout path rolls back) — mechanically sound against provider semantics and poison-free by construction. +- **Package ownership is acyclic and declared.** Relay runtime (decoded delivery/sinks/supervisor) in `@netscript/service/commands/relay`; raw rows/lease/token/release in `@netscript/database/commands`; the only new edge is `service → database` (verified absent today in `packages/service/deno.json`); database imports no service type. +- **Reuse discipline is recorded.** Queue non-wrapping decision with real reasons (delete-on-ack/DLQ semantics, runtime DDL verified present, `TTx` join impossibility); pattern/test reuse only, with a named reconciliation prerequisite before any code sharing. +- **Identity law is complete.** JCS/RFC-8785 request hash over the exact versioned semantic request; key hash separate; scope/fingerprint determinism with frozen-value conformance; execute-as-new for changed scope/renamed command stated honestly as the consequence plus migration obligation; definition-version change under a stable key = mismatch. +- **Capability honesty is enforced.** `selectableIsolationLevels` vs `defaultIsolation` split; MySQL blocked on `SNAPSHOT` removal + allow-listing (defect verified live at `adapter.ts:480`, distinct from #1293); SQLite default-only shape deferred to FCP Q2 with no support claim; Deno KV and multi-store refused. +- **Conformance is the strongest artifact in either RFC**: 20-item positive/negative matrix, 10 named fault seams, real-provider requirement (no type-only fakes), and the root-client negative control. + +Residual decisions are the four FCP questions, all policy-safe with recorded recommendations; plus the §3-Q1 advisory cross-reference. + +### 4.3 Cross-RFC verdict: **COMPOSE CLEANLY — PASS** + +- **No circular dependency.** Shared prerequisite #1350 (0.0.7) is one-way; RFC-A stages 1a/1b and RFC-B stage 0 both consume it. RFC-B's new `service → database` edge is acyclic. Neither RFC imports the other's surface. +- **No conflicting error ownership.** RFC-A's `SdkClientContributionError` is local, pre-dispatch, never a contract error, never `.errors(...)`-mergeable; RFC-B's `commandErrorMap` is route-opt-in contract vocabulary. Both require #1350's literal-preserving `ContractBuilder` spelling; whichever lands first establishes it and the other reuses (obligation explicit in both texts; current erased spelling verified live, so #1350 is genuinely prerequisite). +- **No conflicting context/telemetry/idempotency ownership.** RFC-A reserves trace headers to the client transport (final `traceparent` must describe the SDK client span — verified injection point at `http-client-link.ts:90`); RFC-B persists validated W3C context in durable rows and emits `command.*` vocabulary from `@netscript/telemetry/attributes` with stricter redaction than today's messaging/saga attributes (asymmetry acknowledged, cleanup owned as FCP Q3). Disjoint layers. RFC-A's permitted `idempotency-key` **header** and RFC-B's envelope `idempotencyKey` **input field** are different layers; the future HTTP recipe (#1364) must name the authoritative one for services accepting both — watch item, not a conflict. +- **No duplicated primitives.** No existing JCS/canonicalization utility exists in `packages/` (verified), so RFC-B's codec is new, not reinvented; RFC-A's descriptor/reference types have no prior seam; plugin discovery routes through #1093 rather than a second switch system. +- **One cosmetic inconsistency** (frontmatter milestone convention, §3-Q7) and **one shared spelling obligation** — both already named in the cross-RFC brief. + +## 5. Cycle-2 resolution audit (all 17 findings) + +Independent re-check against the accepted content SHAs; "anchor" = location in the accepted RFC text. + +| Finding | Cycle-2 claim | Adversarial re-verification | Result | +| --- | --- | --- | --- | +| F-A1 scoped zero-oRPC gate | resolved | Anchor present (scoped to new declarations + generated clients; non-growing allowlist; doctrine sanction verified); gate now passable in principle | **Confirmed** | +| F-A2 server key algebra | resolved | Default exact 3-tuple preserved (fixture asserts it against live `createActionQueryKey`); 5-tuple partitioned shape; six-surface disposition table; cast fast-path restricted; upstream fixture dispositioned (verified cast at `create-service-query-utils.ts:63`) | **Confirmed** | +| F-A3 compatibility defaults | resolved | Normative defaults table covers every widened public generic; fixture proves default assignability of `ServiceClient`/`ServiceQueryUtils` | **Confirmed** | +| F-A4 stream reconnect credential | resolved | Epoch model normative; reconnect = fresh single preparation; mandatory A→B rotation fixture with preparation-count 2 and per-epoch byte equality; aborted stream starts no epoch | **Confirmed** | +| F-A5 desktop bypass | resolved | Out of scope with rejection: excess-property + runtime `SDK_CONTRIBUTION_TRANSPORT_UNSUPPORTED`; targets browser/server only; generator failure; docs obligation; fixture `@ts-expect-error` against real `CreateDesktopServiceClientOptions` | **Confirmed** | +| F-A6 private ports | resolved | Location `src/internal/client-contributions/` (named files, no barrel, absent from exports); doc-graph + packed-consumer negative gates incl. exact specifier rejections | **Confirmed** | +| F-A7 context projection | resolved | Contributors see declared-context projection + `signal` only; seven framework keys reserved and rejected; forced-retry fixture drives `context.retry: 1` (verified `retry: 0` default live); private prepared-call channel specified incl. context-replacement preservation | **Confirmed** | +| F-A8 v2 corrections | resolved | GET direction corrected (GET live today at `http-client-link.ts:82`; v2 rejects GET by default — verified in migration guide); `inferRPCMethodFromContractRouter` replacement + dedupe no-op gates present; OTel rename assigned to #1351 (verified `@orpc/opentelemetry@1.14.11` on v1 line and repo using `@orpc/otel`); lock-only family pinning stated | **Confirmed** | +| F-A9 in-tree fixture | resolved | Fixture committed, models real surfaces; **re-executed: exit 0** | **Confirmed** | +| F-A10 adopted facts | resolved | Per-attempt header resolution + header-safe dedupe cited as fact; raw-input sensitivity sentence present; Stage 1a/1b split explicit; v1-maintained evidence present (re-verified live today) | **Confirmed** | +| F-B1 claim algorithms | resolved | Normative per-provider table with lock-timeout mechanics, busy-terminal semantics, rollback-everywhere; poison-free by construction; conformance item 10 adds provider-specific negative controls; primary citations appended | **Confirmed (exceeds bar)** | +| F-B2 relay ownership | resolved | Ownership rows complete; decoded-vs-raw split; `service → database` declared as a **new** edge (verified absent); database imports no service type | **Confirmed** | +| F-B3 queue reuse | resolved | Decision recorded with real reasons; runtime DDL verified live; reconciliation child proposed for owner filing; rejected-alternative entry present | **Confirmed** | +| F-B4 scope instability | resolved | Execute-as-new law where identity is defined; determinism obligation on `scope()` mirroring `fingerprint()`; negative conformance items 5–6 (changed scope; renamed command; changed version = mismatch) | **Confirmed** | +| F-B5 MySQL SNAPSHOT | resolved | Named in capability matrix + Stage-6 exit condition; defect verified live at `adapter.ts:480`; #1293 kept adjacent and distinct | **Confirmed** | +| F-B6 transaction client type | resolved | Generator-owned `CommandTransactionClient = Omit` with engine-module re-export; Prisma 7.8 probe consistent with lock (7.8.0); `withTransaction` root-client repair retained (assertion verified live) | **Confirmed** | +| F-B7 batch (a–h) | resolved | All eight verified: A4 archetypes; literal-preserving spelling pinned to #1350 (current erased spelling verified live); `claimed` vocabulary unified; ratification-milestone footnote; `WorkerIdempotencyPort` wording fix obligated (comment verified live at `:30`); SQLite selectable/default split in FCP Q2; `db command-store add` sub-noun with `db init` distinction + negative CLI test; telemetry asymmetry acknowledged with cleanup question | **Confirmed** | + +**Conclusion:** cycle 2 genuinely resolved every finding, including the negative gates (desktop rejection, packed-consumer negatives, forbidden telemetry fields, root-client negative control, provider negative controls) and public/package ownership (zero-oRPC scoping; service/database relay split; no new package). The PASS verdicts are substantiated. + +## 6. oRPC v2 migrate-first decision (RFC-A question 4) + +### 6.1 Current upstream state (verified live today, 2026-08-08) + +- `@orpc/client` / `@orpc/server` dist-tags: **`latest` = 1.15.0 (stable), `beta` = 2.0.0-beta.26**. v1.15.0 published 2026-08-08T13:52Z — *after* beta.26 (11:21Z). Seven v1 patch releases since 2026-07-25. **Stable v1 is actively maintained; v2 is pre-release and churning (beta.21→beta.26 in 13 days).** +- Official migration guide confirms: RPC wire protocol changed (**v1 client cannot talk to v2 server** — coordinated deploy required); automatic middleware deduplication removed (double-execution hazard); `.$meta` replaced by `defineMeta` plugins; GET rejected by default (`allowMethods` defaults POST/PUT/PATCH/DELETE; `SimpleCsrfProtectionHandlerPlugin` checks `Sec-Fetch-Mode`); `status` removed from error definitions → handler `errorStatusMap`; `safe()` result reshaped (typed third element + `isSuccess` fourth); `isDefinedError` → `isInferableError`; serializer becomes an instance with string-keyed handlers; `RPCLink` url split into `origin`+`url`; interceptor renames; `@orpc/otel` → `@orpc/opentelemetry`; `RequestHeadersPlugin` → `RequestHeadersHandlerPlugin` (incoming server handler plugin); `@orpc/zod` requires **Zod v4**; TanStack consolidated into one package with `scoped` defaults. + +### 6.2 Decision matrix + +| Option | Technical merit | Risk | Verdict | +| --- | --- | --- | --- | +| **A. Migrate to v2 first, then build the seam** | v2 brings typed-error/status redesign, meta plugins, CSRF plugin, serializer instance — none of which provides outbound header ownership, duplicate/conflict law, cache-partition declaration, redaction, or plugin discovery. `RequestHeadersHandlerPlugin` is incoming-server-only and absent for direct calls. v2 TanStack still excludes client context from keys, so RFC-A's partition/direct-only law survives unchanged. | Beta dependency for a framework publish surface; wire incompatibility forces coordinated client/server rollout across a generated-app ecosystem before the seam even exists; 74-file blast radius; middleware double-execution audit; GET/dedupe law rewrite; Zod-v4 entanglement while #1320 is blocked. | **REJECT** | +| **B. RFC-A on stable v1 now; v1.15.0 family move as a separate lock-only decision; v2 as its own RFC/spike later** | Seam is upstream-major-neutral by construction (three NetScript ports; zero upstream identities in new declarations); stable v1 adapter proven against locked 1.14.6 lifecycle facts; v1.15.0 available today with a normative lock-only whole-family gate; v2 adapter later must pass the same conformance suite. | Minimal: v1 line maintenance (verified active); v2 work deferred, not avoided. | **RECOMMEND** | +| **C. Run the v2 spike in parallel now** | Read-only spike could de-risk the future RFC. | Splits attention during 0.0.7/0.0.8 delivery; beta target moves under the spike; no implementation value until RFC-A ports land (ports are the migration boundary). | **Only after RFC-A acceptance, as a filed tracking issue, unscheduled** | + +### 6.3 Real v2 benefits vs. features that do not solve the seam + +- **Real (transport/server-owned, belong to the future v2 RFC):** wire/protocol modernization; `errorStatusMap` + typed-error redesign (interacts with #1350's `safe()` shape — note v2 changes `safe()` arity again); middleware model; GET/CSRF policy plugins; serializer instance (Fresh/Desktop parity work); unified websocket; hibernation; consolidated TanStack package. +- **Do NOT solve the outbound typed contribution seam:** request-header handler plugin (incoming only; no ownership/conflict/async-resolution/redaction/cache-partition semantics); metadata plugins (server procedure metadata, not client preparation); retry/dedupe plugin renames (transport policy the SDK already owns); TanStack client-context exclusion (preserves RFC-A's law rather than replacing it). + +### 6.4 Recommended sequencing and prerequisites + +1. Accept RFC-A; implement stages 0–5 on stable v1. +2. #1351 decides the **lock-only exact-family move to v1.15.0** (timely: it is today's `latest`), with `deno ci --frozen` + no-mixed-family evidence; no manifest pin churn; no v2. +3. After RFC-A acceptance, **file one new v2 migration RFC tracking issue** (`rfc` label, ratification-adjacent milestone, implementation unscheduled). Prerequisites: RFC-A stages 2–3 landed (the three ports are the migration boundary); owner beta-risk decision (wait for `latest` dist-tag unless explicitly accepted); #1351 complete. Gate list: RFC-A's v2 gate matrix **plus** (from this pass) the Zod-v4 requirement and the v2 `safe()`/`isInferableError` shape change vs the landed #1350 spelling. The v2 adapter must pass RFC-A's entire contribution conformance suite before its migration RFC may enter FCP (already normative). + +## 7. FCP safety and owner decisions required before numbering/merge + +Adjudicated independently — all remaining FCP questions are policy-safe (both outcomes typed/implemented in the RFC text; no rework risk): + +**RFC-A (8 questions):** Q1 budget ≥16 (fixture-proven); Q2 env-reading factory (scoped either way); Q3 #451 scheduling (independent); Q4 naming refinements (semantics fixed); Q5 wrapper-vs-memo (both fixture-constrained); Q6 metadata ownership (vocabulary normative either way — but see recording obligation below); Q7 incoming companion default (direct-call absence mandated either way); Q8 v1.15.0 before/after seam (both supported by the lock-only gate). Q9–Q11 belong to the separate v2 RFC. + +**RFC-B (4 questions):** Q1 idempotency default (recommendation: required, explicit opt-out only); Q2 SQLite default-only shape (recommendation: accept shape, release later, no support claim); Q3 correlation telemetry (recommendation: durable-row/log-only default + separate vocabulary cleanup); Q4 retention defaults (recommendation: explicit deployment values until operational evidence). + +**Decisions that MUST be recorded by the owner before/at numbering:** + +1. **RFC-A Stage 1b metadata ownership** — widen #1350 or file a dependent child (hard prerequisite for #1352; verified #1350 does not own it today). +2. **RFC-A v1.15.0 sequencing** — before or after the minimal seam (either valid; decide to unblock #1351). +3. **RFC-B Q1 idempotency default** (shapes generated scaffolds/telemetry defaults). +4. **RFC-B Q2 SQLite disposition** (decides whether the optional child files at all). +5. **Cross-RFC frontmatter `target-milestone` convention** — pick one at numbering. +6. **RFC numbering order** — 0001/0002 assignment between RFC-A and RFC-B (maintainer choice; no race with #822). + +## 8. Board consequence audit (audit only — nothing created) + +### 8.1 Amendments to existing issues (owner-executed, text sources identified) + +| Issue | Amendment | Source | +| --- | --- | --- | +| **#1349** (0.0.7) | Add RFC-A Stage-2 scope: descriptor/composer, defaulted context-generic client/query surfaces, private `src/internal/client-contributions/` ports + `deno doc`/packed absence gates, prepared-header channel statement, server/TanStack key algebra surfaces, reconnect preparation law, desktop rejection, cache handling. | RFC-A stage table + CROSS-RFC §2 | +| **#1350** (0.0.7) | Stage-0 reconciliation: record the literal-preserving four-generic `ContractBuilder` spelling shared by both RFCs; record the Stage-1b metadata decision (widen vs child). Verified current body is `safe()`-only. | RFC-A stages 0/1a/1b; RFC-B stage 0 | +| **#1351** (0.0.7) | Add: `@orpc/opentelemetry` rename decision (v1-line 1.14.11 available; repo currently on `@orpc/otel`); lock-only exact-family pin policy for the v1.15.0 move; GET-dedupe no-op trap as an acceptance row; explicit "no v2 migration in this issue". | F-A8b/c/d; RFC-A transport section | +| **#1352 / #1353** (0.0.7) | #1352: auth-core bearer factory, access-metadata behavior, redaction, partition/direct-only, manifest reference, scaffold choice (blocked-by metadata decision). #1353: re-scoped to final-trace-ownership proof (transport retains sole final injection; contributor trace-header ownership rejected). | RFC-A stages 4–5 | +| **#1362** (0.0.8) | Command-handler generation depends on the #1362 layered service shape; generators refuse missing layering. | RFC-B CLI section | +| **#1363** (0.0.8) | Inherit RFC-B's 10-child table (labels/milestones as listed in the RFC); relay-ownership + claim-algorithm references; `CommandTransactionClient` generator deliverable; **new from this pass:** name #1387's typed-principal shape as the consumer dependency for authenticated envelope derivation (§3-Q1). | RFC-B decomposition + this eval | +| **#1364** (0.0.8) | Consume the stable outbox ID in the webhook recipe; name which idempotency carrier is authoritative (header vs envelope) for services accepting both. | RFC-B sink rules; CROSS-RFC §1 | +| **#1387** (0.0.8) | Backlink: command-kit envelope derivation consumes the typed-principal/context surface this issue creates. | This eval (§3-Q1) | +| **#1293** | None — stays adjacent; the MySQL `SNAPSHOT`/allow-list fix is the distinct Stage-6 child. | F-B5 | + +### 8.2 Genuinely new issues required (file by owner after acceptance) + +| New issue | Type | Milestone/labels | Dedup check | +| --- | --- | --- | --- | +| **oRPC v2 migration RFC tracking issue** (the only new epic-scale object) | `rfc` tracking issue for a future RFC/spike | Ratification-adjacent (`0.0.6`-era or Backlog until scheduled); `rfc`, `type:docs`/`type:feat` at RFC filing, `priority:p2` until owner beta decision | **Not a duplicate**: full #1348–#1388 title sweep verified; #1351 is stable-v1 transport consolidation and must say "no v2 migration" | +| RFC-A Stage 6 locale non-auth proof child | feat child of the #1348 family | 0.0.7; `type:feat`, `area:sdk`, `priority:p1` | Distinct from #1352 (auth) and #1353 (trace proof) | +| RFC-A Stage 1b metadata child — **only if** Stage 0 chooses child-over-widen | feat/fix child | 0.0.7 | Would not duplicate #1350 once the decision is recorded | +| Queue runtime-DDL reconciliation child (RFC-B prerequisite for future code sharing) | fix | 0.0.8; `type:fix`, `area:database`, `priority:p2` | No live issue covers queue `ensureSchema` externalization (verified) | +| SQLite command-store child — **only if** FCP Q2 accepts | feat | FCP-decided | Distinct from #1293/#1363 children | + +### 8.3 Dependencies/milestones verification (live board matches RFC sequencing) + +#1348/#1361 in 0.0.6 (ratification); #1349–#1353 in 0.0.7 (RFC-A implementation); #1350 in 0.0.7 (shared prerequisite, consumed by RFC-B stage 0); #1362–#1364 + #1363 umbrella in 0.0.8 (RFC-B implementation); #451 Backlog; #1093/#1278/#1293 in 0.0.6. All 41 filed issues open, correctly milestoned, `status:triage`. No duplicate of either RFC exists among #1348–#1388 or other live issues (also checked #572/#822 draft PRs — neither overlaps; #822 files no `rfcs/` content). + +## 9. Process readiness audit + +| Check | State | Verdict | +| --- | --- | --- | +| Draft state | Both PRs draft, MERGEABLE, base `main` | Correct — RFCs stay draft until acceptance | +| Labels | Both: `rfc`, `type:docs` (+ `type:test` on #1390 for the compile-only fixture — honest), correct `area:`/`priority:p1`, `ci:skip-e2e`/`ci:skip-scaffold` (valid: no runtime/generator/export/lock change in either diff), exactly one `status:` (`augment-review` — correct phase for this advisory pass) | Compliant | +| Check selection | All CI lanes skipped at both HEADs (path-filtered docs-only); fixture compile gate recorded and re-executed here (exit 0) | Valid, with §3-Q6 caveat | +| Reviewer separation | Generator (Codex) ≠ PLAN-EVAL cycles 1–2 (Claude Fable 5, owner-designated, same session both cycles, separate from generator) ≠ this adversarial pass (Qwen 3.8 Max, fresh, no subagents) | Separation intact | +| RFC numbering | `rfcs/README.md`: maintainer assigns next free integer at acceptance; both files correctly `0000`; first-ever file RFCs; no race (#822 adds no file) | Ready for maintainer | +| FCP rule | Maintainer announces ~7-day FCP with disposition; **not yet announced on either PR** | **Mandatory wait** | +| Unresolved threads | GraphQL: 0 review threads on both PRs; review-thread gate PASS recorded | Clean | +| Closing keywords | PR bodies reference #1348/#1361 **without** closing keywords (correct: tracking issues stay open for implementation); no epic-closing keywords anywhere | Correct | +| PR body hygiene | #1390 DoD checkbox + phase line stale vs live verdict/label (§3-Q9) | Cosmetic fix at FCP prep | + +**Merge readiness:** technically both PRs could merge green today, but **must not**: `rfcs/README.md` requires the formal FCP interval and maintainer acceptance before numbering/renaming/merge, and both PR bodies themselves instruct "keep draft; do not number or merge until maintainer acceptance and remaining owner-directed review passes complete". This evaluation's PASS_ACCEPT is the final technical gate; the FCP interval is a **process** wait, not a technical deficiency. + +## 10. Root-orchestrator action list + +1. **Record this verdict** on both PRs (structured comment citing this artifact); move `status:augment-review` → the FCP-entry state per lifecycle; fix #1390's stale DoD checkbox/phase line. +2. **Owner records the six decisions** of §7 (metadata ownership, v1.15.0 sequencing, RFC-B Q1–Q4, milestone-frontmatter convention, numbering order). +3. **Announce ~7-day FCP** on #1390 and #1389 with disposition **accept**, attaching the FCP question sets and recommendations. +4. **At FCP close:** mark PRs ready-for-review, assign RFC numbers, rename to `rfcs/NNNN-*.md`, fill frontmatter (harmonized milestone convention), `status:ready-merge`, merge. Tracking issues #1348/#1361 stay open, milestoned, carrying the accepted RFC link. +5. **Post board amendments** of §8.1 (marker-comment convention per Stage-H practice). +6. **File new issues** of §8.2 in order: v2 migration RFC tracking issue (after RFC-A merge), locale child, metadata child (if chosen), queue-reconciliation child (owner decision), SQLite child (if FCP accepts). +7. **Dispatch implementation only after** amendments land: RFC-A stages 0–5 against #1349–#1353 (0.0.7) with #1350 first; RFC-B stages 0–9 against #1363 children (0.0.8). +8. **Hold the line:** no v2 beta in any implementation PR; #1351 owns the only oRPC movement (lock-only v1.15.0 decision); the v2 RFC must clear RFC-A's conformance suite on its adapter before its own FCP. + +--- + +## Verdict summary + +- **RFC-A (#1390 @ `78a7cecd1`): PASS_ACCEPT** — technically ready for FCP/acceptance; mandatory process wait (FCP interval) applies; owner decisions §7 items 1–2 must be recorded at numbering. +- **RFC-B (#1389 @ `c98c08ada`): PASS_ACCEPT** — technically ready for FCP/acceptance; mandatory process wait (FCP interval) applies; owner decisions §7 items 3–4 must be recorded. +- **Cross-RFC: PASS** — composes cleanly; no circular dependencies, no conflicting error/context/telemetry/idempotency ownership, no duplicated primitives; one shared #1350 spelling obligation and one watch-item intersection, both already owned. +- **oRPC v2 migrate-first: REJECTED** — implement RFC-A on stable v1 now; separate lock-only v1.15.0 decision in #1351; one new v2 migration RFC tracking issue filed after RFC-A acceptance, implementation unscheduled, gated by RFC-A's conformance suite. +- **Cycle-2 findings:** all 17 (F-A1–F-A10, F-B1–F-B7) independently confirmed resolved. +- **New findings:** none above Low; two Low board advisories (#1363↔#1387 principal dependency; #1350 metadata ownership recording) and seven Info items, all with exact repairs in §3/§8. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/SYNTHESIS.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/SYNTHESIS.md new file mode 100644 index 0000000000..43e07d4213 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/SYNTHESIS.md @@ -0,0 +1,177 @@ +# Stage-C Synthesis — Fable 5 long-range remediation roadmap + +Supervisor synthesis of the full Stage-B corpus (19 artifacts: `research/preplan-package.md`, +4 wave files, 3 board files, 8 repo-audit files, 3 external files). Every claim below traces to a +corpus file; corpus files carry the primary citations. Written by the Tier-A supervisor +(Fable 5 · high) after reviewing all corpus artifacts. **Draft — no board mutation.** + +## 1. Answer to the core question + +**What prevents NetScript from being a credible, differentiated production-grade meta-framework +is not missing capability — it is that the product surface (generated app + docs + typed seams + +runtime proof) contradicts the capability.** The corpus proves this four ways: + +1. **The APIs exist and reach product quality when used.** eis-chat reaches the bar using only + `packages/fresh` APIs on main — with zero `any` — by inventing six app-space conventions the + scaffold does not generate (`research/external/eis-chat.md` §1–3). The framework's own example + route uses `defineRouteContract`/`withResource`/forms (`research/repo-audit/mcp-cli.md` #9). +2. **The generated path contradicts the idiomatic path.** `ui:add page --island` — the verb the + generated agent conventions tell agents to run — emits a `useSignal(0)` counter and an empty + `queryLoaders = {}`, and writes to the wrong tree (`resolveProjectRoot` returns the workspace + root while the app lives at `apps//`) (`repo-audit/mcp-cli.md` #7, #11). The default + scaffold demonstrates none of the page-builder surface (`repo-audit/web-layer.md` §canonical). +3. **The typed seam is sealed exactly where products must extend it.** `CreateServiceClientOptions` + is a closed 9-field record; no headers/interceptors/plugins/fetch/link/context seam; + `createHttpClientLink` unexported; so the shipped auth plugin cannot ride the typed client and + the scaffold leaves `/api` public — a framework test codifies it (`repo-audit/services-sdk.md` + S-findings; `repo-audit/auth.md`). The oRPC pin is **not** the blocker: 1.14.6 vs 1.14.15 + public exports are identical; every gap is NetScript's own wrapper erasure + (`research/external/orpc.md`). +4. **Runtime claims outrun runtime truth.** The shipped sample job discards the saga publish + receipt (mirrored verbatim in docs); `STREAMS_DATA_DIR` is referenced by nothing so "durable" + streams are always in-memory; background children have zero health checks; 5 of 6 saga span + factories have zero callers so compensation is invisible in traces + (`repo-audit/runtime-plugins.md` #4, #7, #8; `repo-audit/observability-aspire.md` GAP-1). + +Six waves of measured agent runs corroborate the mechanism: capability present is not capability +activated (`#1090` thesis); docs/MCP discovery is structurally weak (0 MCP calls across six +consecutive measured runs, `github-board-open.md` §6.3/#1197); and the two GO-grade Wave-6 runs +still hand-rolled everything the missing generators should have emitted +(`research/wave-6-runs.md`, `research/preplan-package.md`). + +**Direction (confirmed from the pre-plan, now evidence-hardened): generation over prose, types +over convention-only guidance, composable seams over one-off escape hatches — plus a fourth leg +the pre-plan under-weighted: runtime truth over green wrappers** (health/receipts/traces that fail +when the seam is removed). + +## 2. Gap taxonomy (charter classes → evidence) + +| Class | Signature findings (corpus refs) | +| --- | --- | +| Docs/discovery failure | 4 P0 docs breaks on one seam — three names for one client module, two query dialects both taught as canonical, `--with-client` cited once site-wide; `docs:accuracy` is a needle-checker; MCP default corpus is 1–2 documents and `agent init` emits `.mcp.json` without `--docs-root` (`repo-audit/docs-quickstart.md`, `repo-audit/mcp-cli.md` #2–3) | +| Scaffold/generation failure | counter-stub `ui:add page`; mis-rooted `resolveProjectRoot`; no second-service client/query generator; hardcoded `dashboard`/`users` names; `/design` gallery 50-item snapshot vs 66-item live registry; quality-runner `SOURCE_ROOTS` decoupled from workspace members (`repo-audit/mcp-cli.md`, `repo-audit/scaffold-doctrine.md` D1–D4) | +| API/type-system seam | closed SDK client record; `PluginContributions` has no client/SDK group and a closed `doctorChecks` literal; `safe` drops `TError` (`isDefinedError` → `never`, executed-check-proven); `ServiceRouter = Record`; `$meta()` used zero times (`repo-audit/services-sdk.md`, `research/external/orpc.md` G1–G11) | +| Runtime correctness | discarded saga publish receipts + `127.0.0.1:8092` fallback; producer drops writes forever (#1326, open); always-in-memory streams; `WORKER_CONCURRENCY` vs `WORKERS_CONCURRENCY`; hardcoded pre-randomization ports in sagas/triggers/streams stubs (`repo-audit/runtime-plugins.md`) | +| Plugin-composition failure | first-party plugin services structurally unguardable (`createPluginService` has no auth option); discovery hardcodes official factory callees (#1093); Aspire `composeAppHost` seam is dead code (`repo-audit/auth.md`, `repo-audit/runtime-plugins.md` #5) | +| Harness/evaluation failure | E2E probes API health only, never background children or streams — why #1325 shipped green; no E2E asserts any `saga.*` span; `docs:accuracy`/`check-exports-drift` cover fractions (`repo-audit/runtime-plugins.md` #7, `repo-audit/observability-aspire.md` gap 3) | +| Product expectation outside framework scope | tenancy in a demo (#884/#885 own the framework leg); webhook dispatcher (no promised primitive — decide recipe vs template); mobile-action loss (→ #1333 acceptance) (`research/preplan-package.md` §verify-first) | + +## 3. Adjudications of supervisor-delegated decisions + +1. **Trigger Redis glue / `ServiceReferences` injection (Wave-6 R2 "D-class" vs R3 "docs").** + Adjudicated **framework generation defects**. The triggers glue omission is proven at source + (`runtime.stub.ts` emits no `@netscript/kv/redis` import — `repo-audit/runtime-plugins.md` #2) + and owned by **#1325**. `ServiceReferences`-parsed-but-never-injected has **no board owner** + found by the board sweep → new issue draft, flagged verify-on-current-canary first. +2. **#1276 vs #1278 (type-soundness duplicates).** Keep **#1278** (milestoned 0.0.6, inventory + A–D + guard-rail shape) as the epic-of-record; propose folding #1276's unique measured numbers + (56 `as unknown as`, 8 lint-ignores, 7 `quality:scan` allowances, tranche T1–T6 structure) + into #1278 via amendment, then owner closes #1276 as superseded. Rationale: milestone + assignment + richer acceptance shape live on #1278; #1276's tranches survive as #1278 phases. +3. **#1275 vs #1279 (migration-chapter duplicates).** Keep **#1279**; fold/close #1275. Propose + moving #1279 out of 0.0.6 (migration docs are post-remediation marketing surface, not + remediation) to the late train. +4. **#1245.** RESCOPE, do not re-implement: ~75% landed via merged #1265. Remnant = + `getIslandQueryClient()` `@throws`-vs-body mismatch, regression tests, and a consumer + migration note so eis-chat-class apps delete their six copied casts + (`repo-audit/web-layer.md`; `external/eis-chat.md` #7). +5. **Webhook dispatch.** Not a framework defect. Disposition: first-party **recipe + worker + template** (docs + generation), not a new plugin; p2 draft under the service/command pillar. +6. **Theme-island CORS, saga OOM, plugin-doctor layout.** Remain verify-first rows (repro on the + current canary before filing); plugin-doctor routes through #1343's installed-consumer smoke. +7. **Divergent agent claims.** Where two corpus agents disagree, the domain agent with source + citations wins (e.g. MCP tool count = 21 per `tool-types.ts:4-26`, over the external agent's + abstention; wave-3 effort = Sol high per 3-of-4 sources). + +## 4. Deep-dive topics (Stage-D design packs) + +- **T1 — Typed extension architecture (RFC-A + oRPC re-exposure).** `SdkClientContribution` + contract; reopen the erased oRPC seams (headers/interceptors/plugins/link/context/typed + errors/`$meta` policy metadata); auth as first dogfood consumer + one non-auth contribution; + transport-policy consolidation ahead of oRPC v2. Depends on nothing; unblocks T2/T5/auth. +- **T2 — Canonical vertical slice + generation.** Resource/route-slice generator; contract-derived + client/query/invalidation generator; fix `resolveProjectRoot`; `ui:add page` triad made real; + #1333 acceptance expansion; route-local groups; `/design` registry sync gate; dynamic app + naming. Consumes T1's client contract. +- **T3 — Service architecture + production command slice.** #1335 service-layout child + (collapsible `domain/application/ports/adapters`); command-composition kit RFC + (transaction/idempotency/audit/outbox/optimistic-concurrency); `service add-handler` placement; + webhook-delivery recipe/template. +- **T4 — Runtime truth.** Saga publish receipts + endpoint discovery (new p0); plugin child + liveness/health contract (distinct from blocked #1280); stream durability semantics + (`STREAMS_DATA_DIR`); saga compensation telemetry call-sites; env-var name mismatches; + hardcoded-port stubs (feeds #979); E2E gates that probe children and assert `saga.*` spans. +- **T5 — Docs & agent discovery.** Tier-1 docs rewrite to ONE dialect of the canonical seam; + compile-the-docs gate replacing the needle checker; MCP corpus wiring (`--docs-root` in emitted + `.mcp.json`, #1260/#1201/#1102 chain intact); README/Quickstart truth; package reference pages. +- **T6 — Type soundness + board hygiene.** #1278-of-record amendment + fold plan; #1245 rescope; + #1249; remaining-cast burn-down; `quality:scan` extension to exported types + docs snippets; + board hygiene amendment pack (stale epic checkboxes #301/#1126/#1335, labels.yml parity, + 0.0.2 stragglers, unmilestoned #979/#980/#1000). +- **T7 — Wave-7 harness + measured adoption.** Thin: capability-map rows proved/simulated/absent/ + rejected; causal-trace rules; generated-slice-command usage or recorded rejection; measurement + chain #1102/#1201 (build) → #1197 (re-measure) → #1090 (observe) untouched as owners. +- **T8 — Milestone train + program sequencing.** Rename-shift insertion per house pattern; + epic-overlap normalization (#823⊂#327, #400↔#922 re-baseline, #892 vs #327/#830); every issue + retained and moved with written reasons. + +## 5. Milestone-train direction (to lock at Stage E) + +Constraints from the corpus: 0.0.5 is mid-canary with 21 open issues incl. 4 p0 + #1338 +(`status:impl`); 0.0.7 is entirely #922's critical path (9 p0s); 0.0.8/0.0.13 are the two big +forward buckets; the house shift pattern is rename-in-place highest→lowest then create the freed +title; canary-first release doctrine per `netscript-release`. + +Working proposal (Stage E finalizes exact renames + per-issue moves): + +1. **0.0.5 closes as re-scoped** — the undispatched W2–W5 remainder (streams pair #1326+#1329, + #1333, #1208 phase 1, plus its existing tail) finishes the milestone; no new scope enters. +2. **0.0.6 stays the verification + docs/MCP + soundness cut** (#1343, #1210, #1260, #1201, + #1278 amended, #1093), gaining only the docs P0-dialect fixes and the two RFC ratifications + (RFC-A SDK composition; RFC command-kit) as *RFC tracking issues* — implementation stays out. +3. **Insert two remediation milestones** after 0.0.6 by the rename-shift pattern: new **0.0.7 = + "Typed seams + generation"** (RFC-A implementation, client/query generator, route-slice + generator, `resolveProjectRoot`, #1333 completion follow-ups) and new **0.0.8 = "Runtime truth + + service slice"** (saga receipts, child liveness, stream durability, service layout + command + kit). Current 0.0.7–0.0.13 shift to 0.0.9–0.0.15 wholesale, preserving every issue and + internal ordering (#922's train moves intact; its stale beta.13/15/17 body labels get an + amendment, not a rewrite). +4. **Wave-7 smoke** gates the exit of new-0.0.7/0.0.8 (a measured unfamiliar-agent run must show + the generated path changes behavior before the train advances past remediation). + +Open fork for the owner (carried to plan lock): whether #922 Wave-0 proofs (#923–#927) should +precede RFC-A implementation (contract-coherence argument: both define plugin contribution axes) +or run after new-0.0.8. Default proposal: RFC-A ratifies in 0.0.6 with #928's envelope reviewed +against it; #922 implementation stays at its shifted position (new 0.0.9). + +## 6. New-issue surface (drafting input for Stage D) + +Every candidate below was checked against the dedup checklist (`github-board-open.md` §7) and has +**no existing owner**; drafts must carry `## Boundaries` naming the adjacent owners. Grouped by +pack: T1: RFC-A tracking issue; oRPC seam re-exposure; typed-error repair (`safe`/`isDefinedError`, +`baseContract` widening); transport-policy consolidation (pre-oRPC-v2). T2: route-slice generator; +client/query/invalidation generator; `resolveProjectRoot` fix; `ui:add page` triad; `/design` +registry sync; `crudExample` alias bug; canonical island `initialDataUpdatedAt` wiring. T3: +service-layout child; command-kit RFC; webhook recipe. T4: saga receipt p0; plugin child liveness; +stream durability; saga-span call-sites; env-name mismatches; stub port hardcodes; +`ServiceReferences` injection (verify-first); E2E child/span gates. T5: Tier-1 docs rewrite; +compile-the-docs gate; MCP `--docs-root` wiring; `execute_command` version pin fix; package +reference pages. T6: `quality:scan` extension; fresh-ui inclusion in root check/lint; doctrine +verdict refresh (D6); `arch:check:repo` accepted-red closure plan (D8); board-hygiene amendments. +T7: Wave-7 rubric/harness issue(s). Auth (T1-adjacent, checked against #871's 16 children which +own *enterprise* scope, not these defects): plugin-service auth seam; signout-without-session +defect; oRPC Set-Cookie discard; scaffold `/api` protection default; principal typing. + +## 7. Sharpest risks + +1. **Duplicate filing** — 9 prose-only umbrellas + the #1208-phase-2 promised-but-nonexistent + issue are the top traps; every draft carries `## Boundaries` + dedup row. +2. **Re-implementing landed work** — #1245 (75% landed), #1328/#1184 closed; drafts must cite + current source, not wave-era observations. +3. **Board-shift blast radius** — renaming 7 milestones touches ~150 issues' display; mitigated + by the house rename pattern (no per-issue mutation) + a written move ledger. +4. **RFC-A over-design** — the seam must stay host-app-usable without plugins; oRPC already + provides the machinery; the RFC should mostly *unhide* it (`external/orpc.md` key negative + result). +5. **0.0.5 scope creep** — remediation lands after 0.0.5 closes; only already-scoped W2–W5 work + finishes there. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/WAVE7-AND-AGENT-ADOPTION.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/WAVE7-AND-AGENT-ADOPTION.md new file mode 100644 index 0000000000..464c65acd1 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/WAVE7-AND-AGENT-ADOPTION.md @@ -0,0 +1,80 @@ +# Wave 7 & agent adoption — DRAFT (no GitHub mutation; owner ratification pending) + +Proposed Wave-7 harness changes and the measured smoke matrix. Deliberately **thin**: Wave 7 +proves docs/MCP/generation adoption; it does not script product choices. Sources: +`research/wave-5-6-plans.md` (brief lineage, rubric, measurement method), +`research/wave-6-runs.md` (R1–R3 + recurrence table), `research/preplan-package.md` (§Wave-7 +rules, owner constraints), `research/github-board-open.md` §6.3 (the measurement chain). + +## 1. What Wave 7 exists to falsify + +**Hypothesis under test:** after the 0.0.7/0.0.8 remediation cuts, the generated path (resource +slice, client/query module, protected-by-default auth, durable-runtime truth) changes unfamiliar- +agent behavior — adoption becomes the path of least resistance instead of a compliance ask. + +Wave 6's evidence base: capability present ≠ activated (six consecutive measured runs with zero +MCP/doctor/otel usage; `#1197`); R3's only-GO run is confounded (supervisor-enforced init gate, +different model, different canary — `research/wave-6-runs.md` #7). Wave 7 must remove those +confounds, not re-run Wave 6 harder. + +## 2. What carries over unchanged (proven Wave-5/6 machinery) + +- **Never name a capability** in the brief; pressure-not-feature forcing functions + (`projects/README.md:25-28` rule; the `withForm`-derived-unprompted validation). +- Project catalog with version gate (Quickstart canary pin — the Wave-6 pilot's silent stable + install is the cautionary case), distinctive-skin bar, "one entity is not a product". +- Taxonomised executed-command census (SETUP/LOOP/DIAG/REPO; parse tool calls, not transcript + greps; exclude generated code; track the nested init repo). +- Capability-map rows end **proved / simulated / absent / rejected**, one evidence pointer each; + rejected-substitutes column (the Kimi runbook's non-de-scopeable matrix). +- Causal claims need a trace/state transition that fails if the named seam is removed; + config presence, registry presence, green wrapper exit, and screenshots are not proof. +- Runtime truth wins over article/README; mechanical article fidelity check; supervisor + contamination rules (seven classes) + intervention ledger; leak-check/teardown hygiene. + +## 3. What changes in Wave 7 (the additions, each tied to a remediation deliverable) + +1. **Generated-slice usage is a first-class measured row.** The agent must use + `ui:add page` (slice mode), the client/query generator, and `service add-handler` — or record + a rejection with a reason. Both outcomes are valid data; silence is a harness failure. + (Measures T2; the #1090 box "runs `ui:add` or records why not" becomes executable.) +2. **Type-escape review blockers become mechanical gates.** Arbitrary `any`, `as unknown as`, + raw route/search parsing, raw `fetch` on a supported seam → the consumer-side no-`any` gate + (T2/T6 deliverables) runs against the agent's product, not just framework exports. +3. **Runtime-truth gates replace wrapper-exit gates.** Saga publish receipt handling, child + liveness states, durable-stream restart proof (T4) are probed by the evaluator from persisted + state + one correlated trace — the R2/R3 "registration and enqueue acceptance explicitly + insufficient" rule, now backed by framework surfaces that make it checkable. +4. **MCP-on-by-default measurement.** The scaffold's `.mcp.json` carries the docs corpus (T5-03); + Wave 7 measures MCP call counts with the mirror absent (#1201's "workspace with no docs/ + directory" condition). Six-zeros is the baseline to beat; the #1102/#1201 capability issues — + not Wave 7 — own retrieval quality. +5. **Every configured gate enumerated and run separately**; wrapper/last-stage exit is + insufficient (Wave-6 rule, kept because #1328's 154-finding surface shipped green under it). +6. **Confound control.** Same brief, same canary, same budget across arms; the R3 enforced-init + gate becomes a **measured arm variable** (enforced vs not), because it is the strongest + untested causal lever in the corpus (`research/wave-5-6-plans.md` #17). + +## 4. Measured smoke matrix (the minimum publishable design) + +| Arm | Canary | Init gate | Slice commands | What it isolates | +| --- | --- | --- | --- | --- | +| A | post-0.0.8 | not enforced | available | pure product effect of generation | +| B | post-0.0.8 | enforced (R3 protocol) | available | init-gate lever on top of generation | +| C (control) | pre-remediation 0.0.6 | not enforced | absent | the baseline delta | + +Two runs per arm minimum, different frontier models, blind gap-audit scoring (Wave-6 rubric). +Success criterion for the program (0.0.8 exit): Arm A shows non-zero generated-slice adoption +AND zero unexplained type-escapes; Arm A vs C shows a categorical difference on the capability +map's frontend-composition and runtime-truth rows. Anything less feeds confirmed residual gaps +back as issues — not as harness thickening. + +## 5. Board footprint (kept minimal) + +Wave 7 adds **one** issue to the train: `verify(0.0.8): Wave-7 measured adoption smoke — arms +A/B/C, matrix + verdict` (0.0.8 exit gate; draft lives in the 0.0.8 milestone directory). The +existing measurement chain stays untouched as owners: **#1102 + #1201 build capability, #1197 +demands re-measurement, #1090 holds the observational boxes** — the Wave-7 issue *consumes* +them; planning it separately from that chain would duplicate the measurement harness +(`research/github-board-open.md` §6.3 chain finding). Harness texts themselves live in the +owner's Drive wave folders + `.llm/harness/`, not on the board. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T1-01-rfc-a-tracking-issue.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T1-01-rfc-a-tracking-issue.md new file mode 100644 index 0000000000..d7391bc4f8 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T1-01-rfc-a-tracking-issue.md @@ -0,0 +1,135 @@ +# rfc: SdkClientContribution — one typed chain for client construction, credentials, transport, policy metadata, and query invalidation — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T1-01 · **Proposed milestone:** `0.0.6` · **Labels:** `rfc` `type:feat` `area:sdk` +`area:plugins` `priority:p1` `status:triage` · **Depends on:** none + +> Filing note (not part of the issue body): file via the `rfc_proposal.yml` form, which auto-applies +> `rfc` + `status:triage` and prefixes the title with `rfc: `. The RFC text is +> `rfcs/RFC-A-sdk-client-composition.md` in this run; per `research/github-conventions.md` §5.4 the +> live house pattern is an issue-hosted RFC (#1123), not a merged `rfcs/NNNN-*.md` file — zero +> numbered RFC files have ever merged. Paste the RFC's numbered sections into the issue body, or +> open the RFC PR **and** this tracking issue if the owner wants the documented file process +> exercised for the first time. Record which was chosen. + +## Summary + +`@netscript/sdk` owns a transport nobody can extend. `CreateServiceClientOptions` is a closed +nine-field record with no `headers`, `fetch`, `interceptors`, `plugins`, `link`, or context type +parameter, and `ServiceClientContext` is a closed interface rather than a type parameter — so the +shipped auth plugin cannot ride the typed client, no plugin can contribute to client construction, +and procedure policy metadata does not exist. Every mechanism needed is already present in the +pinned oRPC 1.14.6, so this is an un-hiding problem, not a missing-primitive problem. This RFC +ratifies one versioned `SdkClientContribution` contract that carries client construction, request +context, headers/credentials, transport middleware, `$meta` policy metadata, response/error types, +and query defaults/invalidation through a single typed chain, before #928 defines a second +contribution dialect and before #934 invents its own policy shape. + +## Evidence + +- RFC text: `rfcs/RFC-A-sdk-client-composition.md` (this run). +- Corpus: `research/repo-audit/services-sdk.md` §2, §3.1–§3.6, gap register S4/S5/S11/S12/S13; + `research/repo-audit/auth.md` §0, §2, §3.1, gaps G1/G2/G9/G10; + `research/external/orpc.md` §1, §4 (G1–G4, G6, G7, G11), §5, §6. +- Source at baseline `fac9e339042c` (re-verified for this draft): + - `packages/sdk/src/ports/service-client.ts:203-222` — the closed nine-field options record; + `port` and `timeout` documented "Reserved …". + - `packages/sdk/src/ports/service-client.ts:129-155` — `ServiceClientContext` is a concrete + interface, not a type parameter. + - `packages/sdk/src/client/service-client.ts:41-49` — `port`/`timeout` are never destructured. + - `packages/sdk/src/client/http-client-link.ts:82-126` — `method`, `headers`, and `plugins` are + literals inside the link. + - `packages/sdk/src/client/mod.ts:15-36` — `createHttpClientLink` is not exported; + `packages/sdk/src/ports/mod.ts:7` advertises "the transport seam" while + `packages/sdk/src/ports/client-link-factory.ts:18-25` is unexported. + - `packages/service/src/primitives/handlers.ts:41-58` vs + `packages/service/src/builder/service-rpc.ts:57` — `RPCHandlerConfig.plugins` exists and is never + populated by the builder path. + - `packages/plugin/src/config/domain/plugin-contributions.ts:12-39` — twelve contribution groups, + none client-side; `:16` is a closed literal union naming a first-party plugin. + - `grep -rnE '\$meta<|\.meta\(' packages plugins` → no oRPC metadata usage. +- Board: #928 (contribution contracts v1, `0.0.7`), #934 (deny-by-default gateway, `0.0.7`), #1093 + (discovery hardcodes official factories, `0.0.6`), #884 (org-aware policy contracts, `0.0.12`), + #451 (in-process link mode, `Backlog / Triage`). + +## Current surface + +`createServiceClient(options)` → `createORPCClient(createHttpClientLink(...))` +(`packages/sdk/src/client/service-client.ts:41-66`). The link is constructed once with literal +`headers`, literal `plugins`, hard-coded `fetch`, and `method: +inferRPCMethodFromContractRouter(contract)`. The only per-call seam that works is the retry/dedupe +knob set typed into `ServiceClientContext` (`http-client-link.ts:27`, regression-tested at +`packages/sdk/tests/integration/service-client-runtime_test.ts:113,153`). `defineServices()` forwards +the same nine fields and inherits the gap wholesale +(`packages/sdk/src/presets/define-services.ts:106-116`). There is no plugin-facing client axis +anywhere. + +## Target contract + +A ratified RFC-A that fixes, at minimum: + +- a versioned `SdkClientContribution` envelope (literal `contractVersion`, namespaced `name`, + `environment`, `requires`, typed `context` marker, `headerKeys` + `headers`, the four oRPC + interceptor/plugin arrays, `fetch`, `errors`, `query`); +- a composition algebra where the client's per-call context is the intersection of the chain's + declared contexts and `with` is optional — a host app with no plugins compiles unchanged; +- a failure taxonomy in which absence, version mismatch, and conflict fail at compile time or + construction time, never as a missing header or a silently dropped plugin; +- `NetScriptProcedureMeta.policy` as the single policy-metadata shape, threaded through + `baseContract` via `oc.$meta<…>()`; +- server/client environment boundaries that are enforced, not documented; +- the rule that contributions never observe the HTTP method, so oRPC v2's POST-only default lands in + one owned function. + +Ratification means: the owner accepts or amends the shape, the tracking issue receives its +milestone, and T1-02…T1-06 may proceed. This issue closes only when all implementation children are +merged. + +## Acceptance + +- [ ] RFC-A text is published in the house shape and linked from this issue. +- [ ] The envelope's field list, version rule, and composition algebra are ratified or amended in + writing by the owner. +- [ ] The failure taxonomy (absence / version mismatch / conflict) is ratified with the detection + point named for each row. +- [ ] `NetScriptProcedureMeta.policy` is ratified as the single policy shape, and #934 is notified on + its issue before it reaches implementation. +- [ ] The envelope is reviewed against #928's contribution contract and the divergences are recorded + on both issues. +- [ ] Q1 (cookie topology) and Q2 (`PluginContributions` group shape) are answered on this issue + before T1-05 opens. +- [ ] Every implementation child (T1-02…T1-06) references this issue with `Part of #`. +- [ ] This issue is not closed by any implementation PR's closing keyword. + +## Boundaries + +- Do **not** duplicate **#928** (`[frontend-contrib S6] plugin-frontend-core contracts/v1`) — it owns + the frontend contribution contract; this RFC only asks that the two envelopes share a version + field, a conflict key, and a failure taxonomy. +- Do **not** duplicate **#934** (generated deny-by-default procedure gateway) — it owns the gateway; + this RFC owns only the metadata type it will read. +- Do **not** duplicate **#1093** (plugin discovery hardcodes official factory functions) — it owns + discovery; this RFC owns the contribution shape. +- Do **not** duplicate **#884** (organization-aware identity and authorization policy contracts) — + tenancy typing on `Principal`/`AuthSession` stays there; this RFC only keeps the client context + extensible enough that a tenant field can arrive later. +- Do **not** duplicate **#885** (auth conformance/mocking/scaffold test kit) or **#872** + (auth capability discovery). +- Do **not** duplicate **#451** (in-process link-mode adapter) — T1-02's public link seam unblocks + it; it stays its own issue. +- Do **not** duplicate **#1278** (type soundness ratification) — the `safe`/`isDefinedError` repair + is filed as T1-03 against a specific executed check, not as a soundness sweep. +- Not in scope: implementation. This is a tracking issue; no code lands under it directly. + +## Docs/consumer proof + +Ratification is proved by documents, not by code: the RFC text, the owner's written disposition, the +cross-links recorded on #928 and #934, and answers to Q1/Q2 on this issue. Adoption proof belongs to +the children — T1-05 and T1-06 must each show a working consumer, and T1-03 must show +`docs/site/services-sdk/sdk.md:199` compiling. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Drafted from the Stage-B +corpus (`research/repo-audit/services-sdk.md`, `research/repo-audit/auth.md`, +`research/external/orpc.md`) and Stage-C synthesis §4 (pack T1) and §5. All source claims re-verified +against worktree baseline `fac9e339042c`. No GitHub mutation was performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T3-01-rfc-b-tracking-issue.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T3-01-rfc-b-tracking-issue.md new file mode 100644 index 0000000000..58e9bab5ca --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T3-01-rfc-b-tracking-issue.md @@ -0,0 +1,98 @@ +# RFC: production command composition kit — one transactional boundary for business state, audit, outbox and idempotency — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T3-01 · **Proposed milestone:** 0.0.6 · **Labels:** `rfc` `type:docs` `area:service` +`area:database` `priority:p1` `status:triage` · **Depends on:** none (ratification only; T3-03 is the +implementation and depends on this) + +## Summary + +NetScript ships every part of a production command and no composition of them: `withTransaction` is +exported and has zero callers, idempotency exists only for worker deliveries, and "outbox" and +"audit" do not exist anywhere in the codebase. Consumers therefore hand-roll the boundary that +decides whether a retried request charges twice or a committed state change is never announced — the +Wave-6 billing run, the only GO-grade run of three, wrote its entire command layer in app space. This +issue tracks ratification of **RFC-B**, which proposes a DB-adapter-neutral command composition kit +and, equally important, states per adapter where that boundary cannot exist. Ratification only — +implementation is a separate issue in a later milestone. + +## Evidence + +- RFC draft: `.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/rfcs/RFC-B-command-composition-kit.md` + (seed run; not yet a repo file). +- `packages/database/mod.ts:128` — `withTransaction(client, fn, options)` is exported and documented + (`docs/site/reference/database/index.md:52`); `grep -rn "withTransaction"` across `packages/`, + `plugins/`, `docs/` finds no caller. `$transaction` appears in no scaffold template and in no + `docs/site/services-sdk` page. +- `packages/cli/src/kernel/assets/service/routers/v1.ts.template:25-53` — the generated write path is + a bare Prisma delegate call with no transaction, no version condition, and no receipt. +- `packages/plugin-workers-core/src/ports/worker-idempotency-port.ts` — `claim`/`markApplied`/`release` + with `caller`/`message-id`/`payload-hash` resolution exists, KV-backed, scoped to worker deliveries + only. +- `grep -rln "outbox"` over `packages/`, `plugins/`, `docs/` → no match. No audit-record primitive. +- `packages/telemetry/src/attributes/helpers.ts` has job/saga/execution attribute helpers and **no** + command vocabulary; `packages/telemetry/src/attributes/spans.ts:45-46` has `rpc.client`/`rpc.server` + only. +- Corpus: `research/preplan-package.md` item **H**; `research/wave-6-runs.md` §R3 (billing product owns + its command layer; two `FAIL_FIX` rounds before state advanced; empty `trigger_events` / + `job_execution_history` relational projections while authority lived in KV) and §R2 (app-owned + retry/compensation executor, `plugin-sagas` never adopted). + +## Current surface + +`@netscript/service` gives a builder, per-request context (`ctx.db`, `ctx.principal`, +`ctx.traceHeaders` — `packages/service/src/builder/service-builder-impl.ts:259-282`) and oRPC +handlers. `@netscript/database` gives an unused transaction helper and an isolation vocabulary +(`packages/database/ports/database-client.ts:59-77`). `@netscript/plugin-sagas-core` gives +`sagaCompensate`/`send`/`spawn` (`src/public/mod.ts:43`). Nothing connects them: there is no unit of +work at the handler seam, no expected-version condition, no idempotency receipt, no same-commit side +records, and no command span. `commonErrorMap` +(`packages/contracts/src/application/contract-primitives.ts:21-52`) has no conflict code, so an +optimistic-concurrency failure can only surface as a 500. + +## Target contract + +RFC-B is ratified (accepted, rejected, or accepted-with-amendments) with, at minimum, a recorded +decision on: the kit's public home and shape; the unit-of-work port and its published +`UnitOfWorkCapabilities`; the idempotency key + request-hash + replay-receipt semantics; the +same-commit rule for audit and outbox and the relay that delivers them; the command telemetry +vocabulary; the injected-failure conformance suite; the per-adapter capability table including the +explicit statement that **no portable atomic boundary exists across two stores**; and the refusal +boundary where a command becomes a saga. The three forks in RFC-B §8 (receipt-storage owner, package +home, `expectedVersion` transport) are answered rather than deferred. + +## Acceptance + +- [ ] RFC-B body is on this issue in the house numbered-section shape. +- [ ] The per-adapter capability table names, per store, whether same-commit side records are possible. +- [ ] The RFC states the cross-store negative result explicitly and claims no exactly-once delivery. +- [ ] The saga refusal boundary is defined and cross-references `sagaCompensate`. +- [ ] The three §8 forks each carry a recorded owner decision. +- [ ] The RFC names no billing-domain symbol; a reviewer confirms the surface is domain-free. +- [ ] The RFC declares its dependency on the typed-error repair and what lands without it. +- [ ] An implementation issue exists and carries `Part of #` before this issue closes. +- [ ] Ratification adds no code; `packages/` and `plugins/` are unchanged by the ratifying PR. + +## Boundaries + +Do not duplicate: **#1123** (OpenAPI→MCP RFC — agent legibility of an existing API surface, not how a +command commits); **#922**/**#928** (frontend contribution contracts); **#884**/**#885** (org-aware +identity/authorization contracts and the auth conformance kit — the kit consumes `Principal`, it does +not define authorization); **#742** (saga definition versioning); **#1278** (type-soundness +ratification — this RFC must not restate the cast inventory); **#1332** (DB-schema-first docs); +**#1263** (generated by-id handler returns 500 instead of a defined 404 — a contract-error defect in +the existing CRUD template, fixed independently of this seam). This issue does **not** implement +anything: no `packages/` or `plugins/` change lands under it. + +## Docs/consumer proof + +Ratification is proven by the decision record, not by adoption. Adoption proof belongs to the +implementation issue: a generated project whose non-CRUD command uses the kit, a services-sdk how-to +that replaces the hand-rolled pattern, and a docs example that type-checks against the published +export map. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Pack T3 of the Fable-5 +remediation plan (`SYNTHESIS.md` §4); source item **H** in `research/preplan-package.md`. Checked +against the dedup checklist in `research/github-board-open.md` §7 — no existing owner for +transactional/idempotent command composition. Draft only; no board mutation performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T5-01-canonical-client-dialect.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T5-01-canonical-client-dialect.md new file mode 100644 index 0000000000..48b96852a8 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T5-01-canonical-client-dialect.md @@ -0,0 +1,149 @@ +# fix(docs/sdk): the golden path names three different client modules and two incompatible query APIs, and the file the quickstart points at is a CSS entry — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T5-01 · **Proposed milestone:** 0.0.6 · **Labels:** `type:fix` `area:docs` `area:cli` +`area:sdk` `priority:p0` `status:triage` · **Depends on:** none (coordinate with #1333, #1335) + +## Summary + +The documented path from a contract to a rendered page names three different modules for the one +data-layer file — `apps/dashboard/client.ts`, `lib/api-clients.ts`, `lib/example-service.ts` — and +only the third is ever written by a generator. The first is the scaffold's CSS hot-reload entry, so +the quickstart's closing instruction sends a first-time reader to a stylesheet. On top of that the +SDK ships two query surfaces with incompatible call signatures (`createQueryFactories` positional, +`createServiceQueryUtils` object-wrapped) and the docs teach both as "the" canonical module without +ever naming the fork. The one flag that bridges service → UI, `netscript service add --with-client`, +appears exactly once site-wide, and when it does run it writes a file whose exported symbols are all +named `exampleService*` regardless of the service. This is the seam the framework's entire pitch +depends on, and it is unfollowable today. + +## Evidence + +Corpus: `research/repo-audit/docs-quickstart.md` §2.1–§2.4, §3, §4 Tier-1 table; +`research/repo-audit/mcp-cli.md` §4.1; `SYNTHESIS.md` §2 (docs/discovery row), §4 T5. + +Verified in the worktree at `origin/main` `fac9e339042c`: + +1. `packages/cli/src/kernel/assets/app/client.ts.template` is three lines, all CSS imports + (`import './assets/styles.css'; import './assets/design.css';`). It contains no client. +2. `docs/site/quickstart.vto:65` documents `client.ts # [owned] contract-derived client instance` + and `:252` tells the reader to "connect its query loader to the contract-derived client in + `apps/dashboard/client.ts`". `lib/` appears nowhere in the quickstart tree (`:57-87`). +3. The real module is `apps//lib/example-service.ts`, written from + `packages/cli/src/kernel/assets/app/lib/example-service.ts.template` by + `packages/cli/src/kernel/application/scaffold/writers/write-example-service-app-files.ts:66`. + The template binds `createServiceClient` + `createQueryFactories` + `bridgeInvalidation`. +4. `lib/api-clients.ts` is written by no generator and is cited across 10 published pages: + `docs/site/index.vto`, `services-sdk/sdk.md`, `web-layer/query.md`, `web-layer/examples.md`, + `web-layer/interactive.md`, `web-layer/form.md`, `tutorials/storefront/06-storefront-ui.md`, + `tutorials/live-dashboard/01-scaffold.md`, `.../03-sdk-cache-first-query.md`, + `.../04-definePage-QueryIsland.md`. +5. Two query APIs: `createQueryFactories` at `packages/sdk/src/query/query-factory.ts:192` + (positional `queryOptions(input, options?)`, plus the server KV tier) vs + `createServiceQueryUtils` at `packages/sdk/src/query-client/create-service-query-utils.ts:55` + (a remap of oRPC `createTanstackQueryUtils`, `queryOptions({ input })`, no KV tier). Dialect A is + taught at `docs/site/services-sdk/sdk.md:92-115`; dialect B at `docs/site/web-layer/query.md:139-150` + — both call their module "the spine" / "one module per app". +6. `--with-client` exists at + `packages/cli/src/public/features/services/add/add-service-command.ts:37` and scaffolds + `apps//lib/.ts` via + `packages/cli/src/kernel/adapters/service/client-scaffolder.ts:9-21,33-56`. It appears in the docs + exactly once, at `docs/site/reference/cli/commands.md:150`. +7. **New defect found while verifying:** `client-scaffolder.ts:47` renders + `TEMPLATE_KEYS.appLibExampleService` for *any* service name, so + `netscript service add orders --with-client` writes `apps//lib/orders.ts` exporting + `exampleServiceName`, `exampleServiceClient`, `exampleServiceQueries`, + `exampleServiceListInvalidation` (template lines 8, 16, 22, 11). The generated symbol names do not + mention the service and two services collide on meaning, not on file path. +8. Fabricated aliases in the same samples: `@contracts` (`web-layer/query.md:143`, + `services-sdk/sdk.md:100`, `tutorials/live-dashboard/02-contract-to-service.md:112`, + `.../03-sdk-cache-first-query.md:55`) and `@/lib/api-clients.ts` (`services-sdk/sdk.md:189,194,199`). + The scaffold generates only `'@app/' -> './'` and `'@/contracts'` + (`packages/cli/src/kernel/adapters/templates/app/generate-app-deno-json.ts:62-63,125-130`). + +## Current surface + +- One generated data-layer module per scaffolded project: `apps//lib/example-service.ts`, using + dialect A (`createQueryFactories`), plus `bridgeInvalidation` with a hand-written + `(routerName, action)` string pair. +- One re-runnable path to a second module: `service add --with-client`, which reuses the same + template verbatim and therefore emits `example*` symbol names. +- Docs teach three module names and two dialects, with two aliases that do not resolve. +- `packages/sdk/README.md:32` — the JSR landing page (`jsr-package-settings.json:6` + `readmeSource: "readme"`) — front-loads `createServiceQueryUtils`, i.e. dialect B. + +## Target contract + +One name, one dialect, ratified in this issue and enforced by T5-02's gate: + +1. **Module name — `apps//lib/.ts`.** Rationale: it is what the CLI already writes + (`client-scaffolder.ts:19`, `write-example-service-app-files.ts:66`); it is per-service, so a + second service is a new file rather than a merge-edit into an aggregate; and + `apps//client.ts` is unavailable — it is the Fresh CSS entry. `lib/api-clients.ts` is + retired from the docs (a single aggregate module cannot be generated per-service without + rewriting an existing file) and its 10 pages are rewritten to the per-service form. +2. **Query dialect — `createQueryFactories` (dialect A) is canonical.** Rationale: it is what the + scaffold emits, it is the only surface with the server KV tier (`getCachedEntry`, `prefetch`, + `invalidate`, `key`) that the cache-first loader story on `web-layer/query-bridge.md` depends on, + and `defineServices()` already returns it. `createServiceQueryUtils` remains public and is + documented in exactly one place, explicitly labelled as the thin oRPC/TanStack remap with no KV + tier and a different call shape (`queryOptions({ input })`), with a one-line + "do not mix" warning. No page outside that one may present it as the app's data-layer spine. +3. **Template naming.** `example-service.ts.template` becomes service-name-derived so + `service add orders --with-client` emits `ordersClient` / `ordersQueries` / + `ordersListInvalidation`, and the scaffolded default keeps working because its service name is + substituted the same way. +4. **Discoverability.** `--with-client` is documented on the golden path: `quickstart.vto`, + `cli-reference.md`, `services-sdk/how-to/add-a-service.md`, and `how-to/index.md`. +5. **Aliases.** Every sample uses `@app/lib/.ts` and `@/contracts` — the two + aliases the scaffold actually generates. + +## Acceptance + +- [ ] Docs name exactly one data-layer module path, `apps//lib/.ts`. +- [ ] `lib/api-clients.ts` appears in zero published pages. +- [ ] `client.ts` is documented as the CSS entry it is, in the quickstart file tree. +- [ ] The quickstart file tree shows `lib/` and its contents. +- [ ] `createQueryFactories` is the only query dialect taught on the golden path. +- [ ] Exactly one page documents `createServiceQueryUtils`, naming its differing call shape and its + missing KV tier. +- [ ] `--with-client` is documented in quickstart, cli-reference, and add-a-service. +- [ ] `service add --with-client` emits symbols derived from ``, not `exampleService*`. +- [ ] `@contracts` and `@/lib/...` appear in zero code samples. +- [ ] A CLI test asserts the scaffolded client path and exported symbol names for a non-default + service name. +- [ ] Negative gate: a docs check fails when `lib/api-clients.ts`, `apps/*/client.ts` as a client, or + `@contracts` reappears in `docs/site/**`. +- [ ] Negative gate: a docs check fails when `createServiceQueryUtils` appears outside its one + allow-listed page. + +## Boundaries + +- **#1333** owns making the default scaffolded app idiomatic and deriving its name from the project. + This issue does not restructure the default app; it fixes the naming/dialect contract the docs and + the client template must both honour. Coordinate the chosen module name with #1333 before either + lands. +- **#1335** owns the repo-wide generated-surface conformance inventory. Do not re-file that + inventory here; this is one ratified seam, not the sweep. +- **#1208** owns tutorials teaching the page builder (phase 1). Rewriting tutorial prose for the + page-builder story is theirs; this issue only replaces the module name and query dialect where + tutorials already cite them. +- **#1210** owns per-API deep dives and the competitive benchmark. No new per-API pages here. +- **#1332** owns DB-schema-first docs; **#1334** owns the home page's capability story. `index.vto` + is touched here only to fix its uncompilable `lib/api-clients.ts` import. +- **#1260** owns which SDK prose enters the MCP corpus; **#1201** owns the export-surface corpus. +- Not a goal: adding a client/query generator for a second service (that is the T2 pack's + contract-derived generator), or changing `resolveProjectRoot` (T2 pack). + +## Docs/consumer proof + +`docs/site/quickstart.vto` reads end-to-end as one dialect: `contract add` → `service add --with-client` +→ `apps//lib/.ts` → loader → island, with every path in the file tree existing after a +real `netscript init`. `docs/site/index.vto` tab 3 compiles against the published entrypoints under +T5-02's gate. A reader who copies `services-sdk/sdk.md` and `web-layer/query.md` into the same app +gets one query key shape, not two. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Drafted from the Stage-B +docs/quickstart and MCP/CLI audits and re-verified against the worktree at `fac9e339042c`; finding 7 +(`example*` symbol names from `--with-client`) is new in this pass and is not in the corpus. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T5-02-compile-the-docs-gate.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T5-02-compile-the-docs-gate.md new file mode 100644 index 0000000000..b936f92f78 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T5-02-compile-the-docs-gate.md @@ -0,0 +1,120 @@ +# test(docs): docs:accuracy is a fixed-string needle checker — no docs/site code block is ever compiled, and a CLI change never revalidates the site — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T5-02 · **Proposed milestone:** 0.0.6 · **Labels:** `type:test` `area:docs` +`area:tooling` `priority:p1` `status:triage` · **Depends on:** T5-01 (the gate codifies the ratified +dialect) + +## Summary + +`deno task docs:accuracy` passes today while the front door points readers at a CSS file and teaches +two incompatible query APIs, because it only asserts that certain literal strings are present or +absent in certain files. No code block anywhere in `docs/site/**` is ever type-checked or executed, +and the Pages workflow only fires on `docs/site/**` pushes, so a change to the CLI or the SDK never +revalidates the prose that documents it. Every defect in T5-01 and T5-05 is invisible to CI by +construction. Until a snippet compiles against the published entrypoints, docs accuracy is an +unenforced convention. + +## Evidence + +Corpus: `research/repo-audit/docs-quickstart.md` §2.8 and §4 Tier 3; `SYNTHESIS.md` §2 +(harness/evaluation row), §4 T5. + +Verified in the worktree at `fac9e339042c`: + +1. `.llm/tools/docs/check-accuracy-and-discoverability.ts:9-19` defines `requireText` / `forbidText` + as `text.includes(needle)`; `:152-174` iterates a hardcoded `requiredMutationFamilies` list and + asserts only that the string `` `netscript `` appears in `cli-reference.md`. Nothing it + asserts is derived from the CLI, the scaffold writers, or the SDK exports. +2. `.llm/tools/docs/check-exports-drift.ts:13-80` is real code-derived verification but its + `AUTHORITATIVE_MAPPING` covers 8 packages (`fresh-ui`, `plugin`, `config`, `contracts`, `queue`, + `sdk`, `service`, `telemetry`), and most entries set `checkSymbols: false`. The remaining + reference pages and all guide pages are unverified. It is invoked from + `check-accuracy-and-discoverability.ts:176-184`. +3. `.github/workflows/pages.yml:3-11` triggers on `push` to `main` with + `paths: [docs/site/**, .github/workflows/pages.yml]`, plus `release: published` and + `workflow_dispatch`. A `packages/**` change never rebuilds or re-link-checks the site. +4. The only precedent cited for executed doc examples, + `packages/service/tests/_fixtures/readme-examples_test.ts`, is itself a needle checker: it calls + `assertStringIncludes(readme, 'const service = await defineService')` and asserts the absence of + `addHealthCheck` / `addReadinessCheck`. It never compiles the README block. So there is currently + **no** compile-the-docs precedent in the repo at all. +5. `deno task docs:links` (`.llm/tools/validation/check-internal-doc-links.ts`) is clean and real — + `docs=102 broken-links=0 broken-anchors=0 orphans=0` per the Stage-B audit — but it validates + links, not code. +6. `deno.json:81-83` wires `docs:links` and `docs:accuracy` into `docs:maintenance`; no docs task + invokes `deno check`. + +## Current surface + +Three docs gates, none of which can observe a wrong API: `docs:links` (link/anchor graph), +`docs:accuracy` (literal needles over `cli-reference.md` and a fixed preferred-path list, plus +`check-freshRootImports` over `docs/`), and `check-exports-drift` (8 of 32 reference pages, symbol +checking mostly disabled). Pages CI is path-gated to `docs/site/**`. + +## Target contract + +1. **A docs-snippet compile gate.** A checked-in tool extracts fenced `ts`/`tsx` blocks from + `docs/site/**` and type-checks them against the **published entrypoints** (`@netscript/*` import + specifiers, not relative source paths), so a snippet that imports a symbol the package does not + export fails. +2. **Explicit opt-out, not opt-in.** Blocks that are deliberately non-compiling (counter-examples, + partial fragments, shell, config) carry an inline marker (e.g. an info string + ```` ```ts no-check: ````). The gate fails on an *unmarked* block that does not compile + and on a marked block that has no reason. The marker census is reported so the number of exempt + blocks is visible and can only shrink. +3. **Compilation harness, not execution.** Blocks are assembled into synthetic modules with a shared + preamble/import map; the gate is `deno check --unstable-kv` over the generated set. No network, + no service start. +4. **Coverage floor.** The gate starts at the T5-01 Tier-1 pages (`quickstart.vto`, `index.vto`, + `services-sdk/sdk.md`, `services-sdk/how-to/add-a-service.md`, `web-layer/query.md`, + `web-layer/examples.md`, `web-layer/interactive.md`, `web-layer/form.md`, + `web-layer/query-bridge.md`) and records a written expansion plan for the rest. +5. **Trigger fix.** `pages.yml` adds `packages/**` and `plugins/**` to its `paths`, or the same + validation runs as a `ci.yml` job on those paths, so a framework change revalidates the site. +6. **Needle checker demoted.** `docs:accuracy` keeps only the assertions that are genuinely + string-shaped (forbidden stale claims, mutation-map columns) and hands API truth to the compile + gate and to `check-exports-drift`. + +## Acceptance + +- [ ] A checked-in tool extracts fenced TS/TSX blocks from `docs/site/**` and type-checks them. +- [ ] Snippets resolve `@netscript/*` specifiers, not relative source paths. +- [ ] The gate runs in CI on changes to `docs/site/**`, `packages/**`, and `plugins/**`. +- [ ] `pages.yml` (or an equivalent CI job) revalidates the site when `packages/**` changes. +- [ ] Non-compiling blocks require an explicit marker with a stated reason. +- [ ] The gate reports the count of marked-exempt blocks on every run. +- [ ] All T5-01 Tier-1 pages compile with zero unmarked failures. +- [ ] Negative test: a fixture page importing a non-exported symbol makes the gate exit non-zero. +- [ ] Negative test: a fixture block marked exempt with no reason makes the gate exit non-zero. +- [ ] Negative test: reintroducing the `queryOptions({ input })` call shape into a dialect-A snippet + makes the gate exit non-zero. +- [ ] `deno task docs:accuracy` no longer asserts API truth by literal needle. + +## Boundaries + +- **#1278** owns type-soundness ratification across the public surface and the docs, including the + `as unknown as` / `any` inventory and its guard-rail. This issue does **not** add a cast guard; + where a docs snippet needs a cast to compile, that is #1278's finding, and this gate must not + silently legalise it. +- **#1108** owns verifying generated package references against live export maps; the expansion of + `check-exports-drift` from 8 to all reference pages belongs there, not here. This gate covers + *prose* code blocks. +- **#1210** owns per-API deep-dive pages; **#1208** owns tutorial rewrites. This issue does not + author prose — it only makes the prose falsifiable. +- **#767** owns `docs:readme:check` being a dead gate; package READMEs are T5-05. +- **#1343** owns the installed-consumer canary smoke. This gate compiles against workspace-resolved + `@netscript/*`; it is not a published-artifact install proof. +- Not a goal: executing snippets, starting services, or screenshotting the site. + +## Docs/consumer proof + +After this lands, the proof that T5-01's rewrite is real is mechanical rather than editorial: a PR +that reintroduces `lib/api-clients.ts` or dialect-B call shapes on a golden-path page fails CI. The +exempt-block census, published in the run log, is the honest measure of how much of the site is still +prose-only. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Corpus claim that +`packages/service/tests/_fixtures/readme-examples_test.ts` executes README examples was checked and +corrected in this pass: it is a string-inclusion test, so there is no existing compile precedent. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T5-03-mcp-docs-root-wiring.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T5-03-mcp-docs-root-wiring.md new file mode 100644 index 0000000000..69563b36ac --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T5-03-mcp-docs-root-wiring.md @@ -0,0 +1,124 @@ +# fix(agent): emitted .mcp.json carries no --docs-root, so the corpus agent init just installed is invisible and search_docs indexes two documents — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T5-03 · **Proposed milestone:** 0.0.6 · **Labels:** `type:fix` `area:agentic` +`area:tooling` `area:docs` `priority:p1` `status:triage` · **Depends on:** none + +## Summary + +`netscript agent init --with-docs` writes an offline documentation bundle into `.netscript/docs/`, +and `netscript agent mcp` accepts a `--docs-root` flag that switches `search_docs` from the embedded +corpus to a filesystem corpus. The host config that `agent init` emits never passes `--docs-root`, +and the MCP server never probes the project for a docs directory, so the bundle the user just +installed is unreachable and `search_docs` / `list_docs` / `get_doc` see two documents: the MCP +package README and `help.md`. This is the plumbing under the measured result that six consecutive +agent runs made zero MCP calls — an agent that tries `search_docs` once and gets two documents will +not try again. + +## Evidence + +Corpus: `research/repo-audit/mcp-cli.md` §1.3 and §1.6 M1/M2; `research/github-board-open.md` §6.3; +`research/wave-5-6-plans.md` §6 (three-run table: MCP 0/0/0 against curl 41/35/38); +`SYNTHESIS.md` §1.4, §4 T5. + +Verified in the worktree at `fac9e339042c`: + +1. `packages/mcp/cli.ts:114-121` — the corpus is `FilesystemDocsCorpus` only when + `options.docsRoot ?? resolveDocsRoot([], Deno.env.get('NETSCRIPT_DOCS_ROOT'), projectRoot)` is + truthy; otherwise `EmbeddedDocsCorpus` with `[{ slug: 'mcp', source: MCP_PACKAGE_README }, + ...(options.embeddedDocs ?? [])]`. +2. `packages/mcp/cli.ts:82-92` — `resolveDocsRoot` reads only the `--docs-root` argv flag and the + `NETSCRIPT_DOCS_ROOT` environment variable. It performs no project probing. +3. `packages/cli/src/public/features/agent/mcp/agent-mcp-command.ts:39` — the flag exists: + `--docs-root `, "Public NetScript documentation root", plumbed through + `AgentMcpInput.docsRoot` (`agent-mcp-input.ts:5`) into `createAgentMcpOptions` + (`run-agent-mcp.ts:42`). +4. `packages/cli/src/public/features/agent/init/init-agent.ts:94-104` — `--with-docs` + (`init-agent-command.ts:29`) writes each bundle file to + `join(input.projectRoot, ".netscript", "docs", path)`. +5. `packages/cli/src/public/features/agent/init/init-agent.ts:226-262` — `writeHostConfig` emits + `args: ["run", "--config", /deno.json, "-A", cliSpecifier, "agent", "mcp", + "--project-root", projectRoot]`. There is no `--docs-root` argument and no + `env: { NETSCRIPT_DOCS_ROOT }` block, for either the `mcpServers` (`.mcp.json`) or `servers` + (`.vscode/mcp.json`) key. +6. `packages/cli/src/public/features/agent/mcp/run-agent-mcp.ts:48` — the CLI-hosted server adds + exactly one embedded document: `{ slug: "help", source: EMBEDDED_SKILL_FILES["help.md"] }`. Total + default corpus via `agent mcp`: 2 documents. Standalone (`deno x jsr:@netscript/mcp/cli`): 1. +7. The bundle builder exists and is checked in: `.llm/tools/docs/build-agent-docs-bundle.ts` + (with `build-agent-docs-bundle_test.ts`), consumed through + `packages/cli/src/public/adapters/agent/deno-agent-docs-generator.ts` (wired at + `agent-group.ts:21,41`). + +## Current surface + +- Flag: present on `agent mcp`, honoured by the server, documented nowhere on the golden path. +- Bundle: installed by `agent init --with-docs` at `.netscript/docs/`. +- Config: emitted by `agent init` without the flag or the environment variable. +- Result: two-document corpus for every agent that starts the server from the generated config, with + no signal to the agent that the corpus is degraded. + +## Target contract + +1. **`agent init` wires what it installs.** When the offline bundle is written, the emitted host + configs carry `--docs-root /.netscript/docs` (or the equivalent `env` + `NETSCRIPT_DOCS_ROOT`), for every host config it writes. +2. **Auto-detection as the fallback.** `resolveDocsRoot` gains a project probe: with no flag and no + environment variable, a `/.netscript/docs` directory that contains at least one + indexable document is used. Explicit flag beats environment beats probe. +3. **The default corpus is bounded and release-matched.** When no filesystem root resolves, the + embedded corpus carries a bounded, versioned subset of the published docs rather than one README: + the golden-path pages needed to answer "how do I get from a contract to a page" — the corpus set + is enumerated in the issue's implementation PR, is generated (not hand-copied), and carries the + same framework-version provenance check that the export-surface corpus already enforces + (`packages/mcp/src/infrastructure/export-surfaces/embedded-export-surface-corpus.ts:46,58-62` + throws on a version mismatch). A size budget is asserted so the package does not grow unbounded. +4. **Degraded state is observable.** `list_docs` reports the corpus kind (`filesystem` | `embedded`), + its document count, and the resolved root, so an agent (and a wave run) can see a two-document + corpus instead of inferring it from bad answers. + +## Acceptance + +- [ ] `agent init --with-docs` emits host configs containing a `--docs-root` pointing at the + installed bundle. +- [ ] Every host config `agent init` writes carries the same docs wiring. +- [ ] With no flag and no environment variable, the server resolves `/.netscript/docs` + when it exists. +- [ ] Explicit `--docs-root` overrides the environment variable, which overrides auto-detection. +- [ ] The embedded fallback corpus contains the enumerated golden-path document set, generated at + build time. +- [ ] The embedded corpus carries framework-version provenance and fails closed on mismatch. +- [ ] A size budget for the embedded corpus is asserted in CI. +- [ ] `list_docs` reports corpus kind, resolved root, and document count. +- [ ] Tests cover: bundle installed → filesystem corpus; no bundle → embedded corpus; flag beats + env beats probe. +- [ ] Negative test: a `.netscript/docs` directory with no indexable documents does not silently + produce an empty corpus — it falls back to embedded and says so. +- [ ] Negative test: an embedded corpus whose provenance version differs from the package version + fails to construct. + +## Boundaries + +- **#1260** owns *which SDK prose* enters the shipped `search_docs` corpus. This issue is the + **plumbing** — flag wiring, auto-detection, provenance, and observability. The concrete SDK + document selection is #1260's call; if #1260 lands first, this issue consumes its set. +- **#1201** owns serving the generated export surfaces through MCP. Do not add export-surface + content here; the corpus in scope is prose. +- **#1102** owns making capability discovery intent-aware. This issue does not change ranking or + retrieval strategy — `EmbeddedDocsCorpus` stays lexical. +- **#1197** owns re-measuring agent-surface adoption and **#1090** owns observing whether behaviour + changed. Do not restate the measurement here; this issue is a precondition for it. +- **#1324** owns OpenCode ignoring the generated MCP config; a client that never reads `.mcp.json` + is not fixed by writing a better `.mcp.json`. +- Not a goal: new MCP tools (`list_generators`, `plan_generation`), or indexing the full + `docs/site/**` tree into the published package. + +## Docs/consumer proof + +A fresh `netscript init` + `netscript agent init --with-docs` followed by `search_docs "typed client +for a service"` returns the golden-path page, not the MCP README. `list_docs` on the same project +reports `filesystem` and a document count in the tens or hundreds. The agent-tooling page documents +`--docs-root` and the auto-detection order. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. All line citations +re-verified against the worktree at `fac9e339042c`. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T5-04-execute-command-version-pin.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T5-04-execute-command-version-pin.md new file mode 100644 index 0000000000..2371266e11 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T5-04-execute-command-version-pin.md @@ -0,0 +1,122 @@ +# fix(mcp): execute_command spawns a downloaded jsr:@netscript/cli pinned to the MCP package version even when the server is hosted by a local CLI — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T5-04 · **Proposed milestone:** 0.0.6 · **Labels:** `type:fix` `area:agentic` +`area:tooling` `area:cli` `priority:p1` `status:triage` · **Depends on:** none + +## Summary + +When an agent calls `execute_command` against the MCP server started by `netscript agent mcp`, the +command does not re-enter the running CLI. It shells out to +`deno run -A jsr:@netscript/cli@`, downloading and executing a *different* +binary at a version pinned to the MCP package rather than to the host CLI. A maintainer working from +a local or workspace build silently gets published behaviour, and an agent that scaffolds through +MCP can produce artifacts from a different release than the one it is documenting. The same host +reports `version: "current"` to `list_commands`, so the agent has no way to notice. `list_commands` +and `execute_command` are also the only two flows that are not receipt-wrapped, so work performed +through MCP cannot satisfy the `record_drift` evidence gate. + +## Evidence + +Corpus: `research/repo-audit/mcp-cli.md` §1.4, §1.6 M3/M5, §4.5 C4; `SYNTHESIS.md` §4 T5. + +Verified in the worktree at `fac9e339042c`: + +1. `packages/mcp/src/infrastructure/spawn-command-executor.ts:6-14`: + ```ts + import { MCP_PACKAGE_VERSION } from '../publish-assets.generated.ts'; + export const DEFAULT_CLI_COMMAND: readonly string[] = Object.freeze([ + 'deno', 'run', '-A', `jsr:@netscript/cli@${MCP_PACKAGE_VERSION}`, + ]); + ``` +2. `packages/cli/src/public/features/agent/mcp/run-agent-mcp.ts:44` passes + `commandExecutor: new SpawnCommandExecutor()` with no `cliCommand` override, so + `DEFAULT_CLI_COMMAND` applies inside the CLI-hosted server. +3. `run-agent-mcp.ts:27-30` builds the command-catalog program with `version: "current"`, so + `list_commands` never reports the real CLI version. The public tree otherwise reports + `CLI_PACKAGE_VERSION` (`packages/cli/src/public/features/root/public-command-tree.ts:121`). +4. `packages/mcp/cli.ts:194-200` binds `list_commands` and `execute_command` **without** + `withReceipt`, while every read/analytics/doctor/service flow at `:150-234` is wrapped + (`withReceipt` at `:236-264` writes `{resource, command, timestamp, exitStatus}` through + `FilesystemDiagnosticEvidence`). +5. `packages/mcp/src/application/flows/record-drift-flow.ts:5,29-42` refuses unless a receipt for the + named resource exists, has `exitStatus === 0`, and is younger than + `DIAGNOSTIC_RECEIPT_TTL_MS = 15 * 60 * 1000`. Its refusal text + (`diagnosticEvidenceRefusal`, `:17-22`) tells the agent to run `plugin doctor` or a doctor/telemetry + tool — which is correct only because `execute_command` cannot produce a receipt. +6. `packages/mcp/src/domain/command-policy.ts` allows mutating verbs through `execute_command` + (`db init|generate|migrate|seed|…`, `generate`, `contract`, `plugin install|sync|doctor`, + `ui:add|ui:init|ui:list|ui:update`), so the mismatched binary can write to the project. +7. Version pinning elsewhere is host-correct by comparison: `agent init` writes + `jsr:@netscript/cli@${CLI_PACKAGE_VERSION}` + (`packages/cli/src/kernel/constants/jsr-specifiers.ts:33-45`, used at `init-agent.ts:232`), i.e. + the CLI's own version — not the MCP package's. + +## Current surface + +- One process boundary with three independent version identities: the host CLI + (`CLI_PACKAGE_VERSION`), the MCP server (`MCP_PACKAGE_VERSION`, also `serverInfo.version`), and the + spawned child (`jsr:@netscript/cli@MCP_PACKAGE_VERSION`). They agree today only because both + packages read `0.0.4` at this baseline. +- `list_commands` advertises `version: "current"`. +- Two mutating tools produce no diagnostic receipt. + +## Target contract + +1. **The CLI-hosted server executes the CLI that hosts it.** `run-agent-mcp.ts` injects a + `cliCommand` derived from the running entrypoint (`Deno.execPath()` / `import.meta.url` for a + source run, the installed binary for a global install), so `execute_command` never downloads a + second CLI when one is already running. +2. **Version identity is truthful.** `list_commands` reports `CLI_PACKAGE_VERSION`, and the tool + result carries the version and the resolved executor command so an agent can see what it is + driving. `serverInfo.version` remains the MCP package version; a mismatch between the two is + surfaced, not hidden. +3. **The standalone default stays pinned, but to the right thing.** When no host CLI exists + (`deno x jsr:@netscript/mcp/cli`), `DEFAULT_CLI_COMMAND` remains a pinned JSR specifier; the pin + must be documented as MCP-package-derived and asserted equal to the CLI version by the existing + publish-assets generation, or explicitly decoupled with a stated policy. +4. **Mutating tools leave receipts.** `execute_command` is receipt-wrapped on the same terms as the + read flows: a successful run writes a receipt for its resource; a failed run writes + `exitStatus: 1`. `list_commands` is wrapped or explicitly exempted with a written reason. The + `record_drift` refusal text is updated to name `execute_command` once it can authorize. + +## Acceptance + +- [ ] `netscript agent mcp` injects a `cliCommand` that re-enters the running CLI. +- [ ] No JSR download occurs for `execute_command` when the server is CLI-hosted. +- [ ] `list_commands` reports `CLI_PACKAGE_VERSION`, not `"current"`. +- [ ] `execute_command` results include the resolved executor command and version. +- [ ] `execute_command` writes a diagnostic receipt on success and on failure. +- [ ] `record_drift` accepts a receipt produced by a successful `execute_command`. +- [ ] The `record_drift` refusal message lists the tools that can authorize it, accurately. +- [ ] Tests cover: CLI-hosted executor resolution; standalone fallback to the pinned specifier; + receipt written on both exit paths. +- [ ] Negative test: a denied command (`deploy`, `init`, `db reset`, `plugin remove`, `ui:remove`) + writes no success receipt and cannot authorize `record_drift`. +- [ ] Negative test: with the host CLI at a version different from the MCP package, no spawn resolves + to the MCP-pinned specifier. + +## Boundaries + +- **#1197** owns the agent-surface adoption fix and re-measurement; **#1090** owns observing whether + agent behaviour changed. This issue does not restate or re-measure adoption — it removes one + mechanism by which MCP-driven work is silently wrong. +- **#1093** owns plugin discovery hardcoding official factory callees; unrelated to executor + resolution. +- **#1343** owns the installed-consumer canary smoke; proving the published pin works end to end is + theirs. +- **T5-03** owns docs-corpus wiring in the same host composition root; keep the two changes + separable — do not fold either into the other's PR. +- **#1126 / #1139** own the OpenAPI→MCP tool surface; this issue adds no new tools. +- Not a goal: adding `list_generators` / `plan_generation` (corpus gap M4), or changing + `command-policy.ts` allow/deny sets. + +## Docs/consumer proof + +The agent-tooling reference states which binary `execute_command` runs in each hosting mode and what +`list_commands` reports. A wave run's transcript shows an `execute_command` receipt followed by an +accepted `record_drift`, which is currently impossible. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. All line citations +re-verified against the worktree at `fac9e339042c`. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T5-05-reference-completeness.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T5-05-reference-completeness.md new file mode 100644 index 0000000000..d5f4fbc317 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T5-05-reference-completeness.md @@ -0,0 +1,144 @@ +# docs(reference): four publishable plugin-*-core packages have no reference page, the publish gate demands a path the IA does not use, and the JSR landing README teaches the wrong dialect — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T5-05 · **Proposed milestone:** 0.0.6 · **Labels:** `type:docs` `area:docs` +`area:packages` `priority:p2` `status:triage` · **Depends on:** T5-01 (dialect), T5-02 (gate) + +## Summary + +The reference tree does not cover the publish set, and the gate that is supposed to enforce coverage +has never run against it and would demand paths the site does not use. Twenty-two package READMEs — +including `packages/sdk/README.md`, which is the JSR landing page — have not been touched since a +single bulk commit and still teach the query dialect the scaffold does not emit. Seven public CLI +verbs are documented nowhere, one documented claim about `deploy docker`/`compose` is the inverse of +the code, and the root README hardcodes a scaffold file count that the quickstart explicitly says not +to trust. Individually these are small; together they are the reason a reader who leaves the golden +path cannot get back on it. + +## Evidence + +Corpus: `research/repo-audit/docs-quickstart.md` §2.6, §2.7, §2.9, §2.10, §4 Tier 2; +`research/repo-audit/mcp-cli.md` §2.1, §4.5 C3; `SYNTHESIS.md` §4 T5. + +Verified in the worktree at `fac9e339042c`: + +1. **Missing reference pages.** `docs/site/reference/` has 32 package directories. + `plugin-ai-core` and `plugin-auth-core` have pages; `plugin-sagas-core`, `plugin-streams-core`, + `plugin-triggers-core`, `plugin-workers-core` do not. All four declare an object-valued + `publish` key in `deno.json` (`packages/plugin-sagas-core/deno.json:39`, + `plugin-streams-core:20`, `plugin-triggers-core:32`, `plugin-workers-core:42`), i.e. they are + publishable — contrast `packages/bench/deno.json:33` `"publish": false`, which correctly has no + page. The omission is inconsistent, not policy. +2. **Gate vs IA disagreement.** `.llm/tools/release/publish-readiness.ts:302-306` requires + `docs/site/reference/${packageSegment(member.name)}/index.md`, where `packageSegment` is the part + after `@netscript/` (`:411-415`). The site documents `@netscript/plugin-sagas` at + `docs/site/reference/sagas/index.md` (its front matter reads `title: "@netscript/plugin-sagas"`), + and the same for `streams`, `triggers`, `workers`. A first publish under the current rule would + demand `/reference/plugin-sagas/`. +3. **The gate has never checked the existing tree.** It runs only over `newPackages`, computed as + members with no registry versions (`publish-readiness.ts:158-180`), through `auditFirstPublish` + (`:179`). +4. **README staleness (measured by `git log -1` per file).** 22 of 30 `packages/*/README.md` and 4 of + 6 `plugins/*/README.md` were last touched 2026-07-18, including `packages/sdk/README.md`. + `jsr-package-settings.json:6` sets `readmeSource: "readme"`, so that file *is* the JSR landing + page. `packages/sdk/README.md:32-33` front-loads `createServiceQueryUtils` as the TanStack story — + the dialect T5-01 demotes. +5. **Root README.** `README.md:41` asserts "The scaffold reports **183 files, 44 directories**", + directly contradicted by `docs/site/quickstart.vto:51` ("treat the printed result—not a static + number in this guide—as the authority"); the counts vary with `--db/--service/--editor`. + `README.md:35` prints the literal `jsr:@netscript/cli@` while the site derives the + specifier automatically (`docs/site/_data.ts:30-33`). +6. **Undocumented verbs** (grep over `docs/site` returns zero files for each): + `netscript agent drift`, `netscript deploy desktop`, `netscript deploy package-cli`, + `netscript deploy list`, `netscript config list`. `netscript plugin ai` appears only in + `docs/site/reference/plugin-ai/index.md`, never as a CLI verb in the command reference. All exist + in the public tree (`packages/cli/src/public/features/agent/agent-group.ts:44-51`, + `.../deploy/deploy-group.ts:25-97`, `.../config/config-group.ts:13-29`, + `.../plugins/plugins-group.ts:33-151`). +7. **Inverted claim.** `docs/site/cli-reference.md:246-249` states "`netscript deploy docker` and + `deploy compose` exist as command groups but are not wired — they only print help." Both are + implemented by `AspireComposeDeployTarget`, whose operation list is + `plan · up · down · status · logs` + (`packages/cli/src/kernel/adapters/aspire/aspire-compose-deploy-target.ts:65-70,92,109-117`). +8. **Fabricated aliases.** `@contracts` (`web-layer/query.md:143`, `services-sdk/sdk.md:100`, + `tutorials/live-dashboard/02-contract-to-service.md:112`, `.../03-sdk-cache-first-query.md:55`) + and `@/lib/api-clients.ts` (`services-sdk/sdk.md:189,194,199`) are never generated; the scaffold + emits only `'@app/' -> './'` and `'@/contracts'` + (`packages/cli/src/kernel/adapters/templates/app/generate-app-deno-json.ts:62-63,125-130`). +9. **Maintainer version.** `netscript-dev` reports a hardcoded `version('1.0.0')` + (`packages/cli/src/maintainer/features/root/maintainer-command-tree.ts:32`) against the real + `CLI_PACKAGE_VERSION` train. + +## Current surface + +32 reference pages for a larger publish set; a first-publish-only docs gate keyed to a path +convention the IA does not follow; 26 READMEs frozen at 2026-07-18, one of which is the SDK's JSR +landing page teaching a non-canonical dialect; a root README with a hardcoded file count and a +literal version placeholder; seven verbs with no prose; one reference claim that is the inverse of +the code. + +## Target contract + +1. **Coverage.** Every package whose `deno.json` `publish` key is not `false` has a + `docs/site/reference//index.md`, generated or hand-written, listed in the site nav. +2. **One path convention, written down.** Either the gate's `packageSegment` rule is relaxed to a + declared alias map (`@netscript/plugin-sagas` → `sagas`), or the IA moves to the gate's rule. The + decision is recorded in the reference index; the gate and the IA agree afterwards. +3. **The gate runs over the whole publish set**, not only first-publish packages, so a new package + without a page and an existing package without a page fail the same way. +4. **READMEs match the ratified dialect.** `packages/sdk/README.md` leads with + `createQueryFactories` per T5-01; every package README's code blocks are covered by T5-02's + compile gate; `docs:readme:check` staleness is #767's problem and is not re-litigated here. +5. **Root README truth.** The file count becomes "the scaffold prints its own count" and the install + snippet derives its specifier the way the site does, or states explicitly that the reader should + substitute the current release. +6. **Verb coverage.** `docs/site/reference/cli/commands.md` documents `agent drift`, `plugin ai`, + `deploy desktop`, `deploy package-cli`, `deploy list`, `config list`, and corrects the + docker/compose row and the "three-verb lifecycle" claim. +7. **Aliases.** Every sample uses the two aliases the scaffold generates. + +## Acceptance + +- [ ] Every package with a non-`false` `publish` key has a reference page. +- [ ] `plugin-sagas-core`, `plugin-streams-core`, `plugin-triggers-core`, `plugin-workers-core` have + reference pages. +- [ ] The reference path convention is stated once and the publish gate matches it. +- [ ] The docs-reference check runs over the full publish set, not only first-publish packages. +- [ ] `packages/sdk/README.md` leads with `createQueryFactories`. +- [ ] `README.md` carries no hardcoded scaffold file or directory count. +- [ ] `README.md`'s install snippet resolves to a real specifier or states the substitution. +- [ ] `agent drift`, `plugin ai`, `deploy desktop`, `deploy package-cli`, `deploy list`, and + `config list` are documented in the CLI reference. +- [ ] The docker/compose "not wired" claim is replaced by the real five-verb surface. +- [ ] `@contracts` and `@/lib/...` appear in zero code samples. +- [ ] Negative test: adding a publishable package without a reference page fails the docs gate. +- [ ] Negative test: a CLI command group present in the public tree but absent from the command + reference fails a check derived from the command tree, not from a literal list. +- [ ] [post-merge] The published JSR landing page for `@netscript/sdk` shows the canonical dialect. + +## Boundaries + +- **#1108** owns verifying generated package references against live export maps — the *content* + correctness of reference pages and the expansion of `check-exports-drift`. This issue owns + *existence*, path convention, and gate scope; do not re-file #1108's export verification here. +- **#767** owns `docs:readme:check` being a dead gate (checker/template/house-style divergence). This + issue fixes README *content*, not the README standard checker. +- **#1201** owns serving export surfaces through MCP; **#1260** owns SDK prose in the MCP corpus. + Fixing `packages/sdk/README.md` here does not decide what ships in the corpus. +- **#1210** owns per-API deep dives; **#1208** owns the tutorial page-builder rewrite. No new + conceptual pages here — reference and README truth only. +- **#1277** owns docs-site layout/UI polish. +- **T5-01** owns the dialect decision itself; this issue applies it to READMEs and reference prose. +- Not a goal: authoring per-package tutorials, or renaming published packages. + +## Docs/consumer proof + +`deno task publish:dry-run` and the release readiness check agree with the site tree for every +publishable member. A reader running `netscript --help` can find prose for every group it prints. +The JSR pages for `@netscript/sdk` and the four `plugin-*-core` packages link to a reference page +that exists. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. README staleness re-measured +with `git log -1 --format=%ad` per file in this pass (22/30 packages and 4/6 plugins at 2026-07-18); +the corpus figure of 21/30 is superseded by this measurement. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T6-01-quality-scan-exported-any-docs-snippets.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T6-01-quality-scan-exported-any-docs-snippets.md new file mode 100644 index 0000000000..eec8dbba5d --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T6-01-quality-scan-exported-any-docs-snippets.md @@ -0,0 +1,123 @@ +# chore(quality): `quality:scan` cannot see an `any` in an exported type, an unbudgeted allowance, or a docs snippet — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T6-01 · **Proposed milestone:** 0.0.6 · **Labels:** `type:chore` `area:tooling` +`area:packages` `area:docs` `priority:p1` `status:triage` · **Depends on:** none (Part of #1278 — +this is Inventory C filed as its own trackable child) + +## Summary + +#1278 Inventory C asks for a fail-closed gate on new `as any` / `as unknown as` / `@ts-ignore` +"outside an allowlist that requires a linked issue id", covering docs snippets and not only source. +None of those three properties exists today. `quality:scan` is a line-regex scanner whose +`explicit-any` rule cannot distinguish an exported type from a local one, whose `quality-allow:` +suppression accepts any free-text reason with no issue id and no budget, and whose file filter never +opens a Markdown file. The type-soundness selling point is therefore defended by a gate that a +single trailing comment disables. Now: #1278 is milestoned 0.0.6 and its other inventories are +nearly discharged (see Evidence), so the guard rail is the remaining durable value. + +## Evidence + +Executed at baseline `fac9e339042c` on 2026-08-08; corpus refs +`research/repo-audit/web-layer.md` §11, `research/github-board-open.md` §6.4 (#1278 Inventory C/D, +#1276 tranche T6). + +- **Scope.** `.llm/tools/quality/scan-code-quality.ts:18` — `DEFAULT_ROOTS = + ['packages/cli/src', 'plugins']`. `deno.json:50-52` defines `quality:scan` (defaults), + `quality:scan:repo` (`--root packages --root plugins`), `quality:gate`. + **Corpus correction:** #1276 T6 states `quality:scan` "covers `packages/cli/src` + `plugins` + only". That is true of the *default* task; `quality:scan:repo` already covers all of + `packages/` + `plugins/` and runs on push-to-main and a Monday 07:17 UTC cron + (`.github/workflows/code-quality.yml:50-59`). The PR gate scans only changed files + (`code-quality.yml:36-42`). Root scope is **not** the gap; rule power and suppression discipline + are. +- **Exported types are invisible.** `scan-code-quality.ts:51` — `explicit-any` is + `/(?:<|:\s*)any(?:\s*[,>;)\]}]|\b)/` against a raw line. It has no notion of `export`. +- **A rule that does know about exports exists and never fails.** + `.llm/tools/fitness/check-doctrine.ts:467-484` emits `A1/F-5: 'any' in exported declaration` — but + at `WARN` level only, matching only `export function` / `export type` / `export interface` line + starts (not `export const`, class members, generic defaults, or re-exports), and only under the + 16 roots `arch:check` names (`deno.json:155`). +- **Allowances are unbudgeted and unlinked.** `scan-code-quality.ts:136` accepts + `// quality-allow: `. `--max-allow` exists (`:173-181`) and is passed by + **no** task and **no** workflow (grep over `deno.json` + `.github/workflows/`). Executed counts: + `deno task quality:scan` → `findings: []`, `allowCount: 7`; `--root packages --root plugins` → + `ok: true`, `findings: 0`, `allowCount: 10`. So the repo-wide surface already carries **3 + allowances beyond the 7 that #1276 ratified**, and nothing reports the delta. +- **Docs snippets are never opened.** `scan-code-quality.ts:87` — `isScannable` matches only + `/\.[cm]?[jt]sx?$/`. No `.md`, and no `.template` (so scaffold templates are unscanned too). + `scan-code-quality.ts:47` additionally skips any line beginning with a quote or backtick. +- **What is actually left in docs (re-measured, contradicts #1278 Inventory A).** The only real + hit repo-wide is `docs/site/reference/triggers/index.md:310` `const observedEvents: any[] = [];` + with its executable twin `docs/site/reference/triggers/examples_test.ts:65`. + `docs/site/web-layer/query-bridge.md` no longer contains `as unknown as`, and `~orpc` / + `BaseContractProcedure = Readonly<{ ~orpc: any }>` no longer appears in + `docs/site/reference/contracts/index.md` (grep returns nothing at this baseline). +- **Inventory D is already satisfied and must stay that way.** `scan-code-quality.ts:87` excludes + `_test.ts` / `.test.ts` / `.spec.ts`, so soundness tests are exempt by construction. There are + **6**, not "~19": `packages/plugin-{workers,sagas,triggers,auth,ai}-core/tests/contracts/ + *-contract-soundness_test.ts` and `plugins/workers/services/src/routers/health-soundness_test.ts`. + +## Current surface + +`quality:scan` reports five rules (`explicit-any-ignore`, `unsafe-cast`, `explicit-any`, +`plugin-name-check`, `ts-error-suppression`), all line-regex, over `.ts/.tsx/.js/.jsx` non-test, +non-`.generated.ts` files. Any finding is silenced by appending `// quality-allow: `. The scan +is green today at 0 findings / 7 default / 10 repo-wide allowances. `check-doctrine.ts` carries a +separate, warn-only, export-aware `any` rule over a hand-listed 16-root subset. + +## Target contract + +One gate, three added properties, no third scanner: + +1. **Export-aware severity.** An `any` reachable from a package's published entrypoints is a + failure, not a warning. Reuse and generalize `check-doctrine.ts:467-484` (or drive it from + `deno doc --json` on each package's `exports` map, which already answers "is this symbol + published") rather than adding a fourth regex pass. Non-exported `any` keeps its current + severity. +2. **Registered allowances.** `// quality-allow:` requires a linked issue reference (`#`) that + is open and milestoned; a reason without one is a finding. The allowance list is budgeted with + `--max-allow` wired into `quality:scan` and `quality:scan:repo`, so the count can only fall. +3. **Docs snippets in scope.** Fenced TypeScript blocks under `docs/site/**` are extracted and + scanned by the same rule set; `*_test.ts` companions under `docs/site/**` are treated as docs + fixtures, not exempt tests. + +Exempt by explicit rule, asserted by test: the 6 `*-soundness_test.ts` files whose +`@ts-expect-error` lines *are* the assertion. + +## Acceptance + +- [ ] A new `any` in an exported type fails `quality:scan` on a red-first fixture. +- [ ] A new `as unknown as` without a linked open issue id fails `quality:scan`. +- [ ] An `as any` inside a fenced TS block under `docs/site/**` fails `quality:scan`. +- [ ] The 6 `*-soundness_test.ts` files stay green with their `@ts-expect-error` lines unchanged. +- [ ] `--max-allow` is wired into `quality:scan` and `quality:scan:repo` at the measured count. +- [ ] The allowance budget cannot be raised without an accompanying issue link in the same PR. +- [ ] `docs/site/reference/triggers/index.md:310` and `examples_test.ts:65` are typed, not `any`. +- [ ] Tests cover: exported vs local `any`, linked vs unlinked allowance, docs fence, soundness-test + exemption, and budget overflow. +- [ ] `gate:` `deno task quality:scan:repo` and `deno task arch:check` are green after the change. + +## Boundaries + +- Do **not** re-file #1278; this is its Inventory C child and carries `Part of #1278`. +- Do **not** absorb #1278 Inventory B (the 12 production assertion sites) or #1276's tranches + T1–T5 — this issue only builds the rail that keeps them from regrowing. +- Do **not** touch #1245 (island query types) or #1249 (`controlProps` / Zod 4 constraints); both + are `packages/fresh` source defects with their own owners. +- Do **not** change `arch:check`'s root list here — that is T6-03's `arch:check:repo` closure plan. +- Do **not** extend `quality:scan` into `packages/fresh-ui` before T6-02 decides its lock policy. + +## Docs/consumer proof + +The scanned corpus is the published docs corpus: after this lands, no page under `docs/site/**` +can teach a cast, and the "documented cast is a framework bug with a paragraph attached" claim in +#1278 becomes machine-enforced rather than editorial. The consumer-visible proof is that +`docs/site/reference/triggers/index.md` and its `examples_test.ts` compile with real types, so an +agent copying the triggers reference no longer inherits `any[]`. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Drafted from +`research/github-board-open.md` §6.4 and `research/repo-audit/web-layer.md` §11, with every count +re-measured at `fac9e339042c` because the #1278/#1276 inventories were measured 2026-08-04 and are +stale in three places (docs items discharged, soundness-test count, allowance count). diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T6-02-fresh-ui-root-check-lint.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T6-02-fresh-ui-root-check-lint.md new file mode 100644 index 0000000000..7c6ef95117 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T6-02-fresh-ui-root-check-lint.md @@ -0,0 +1,120 @@ +# chore(ci): `packages/fresh-ui` is excluded from root check and lint, runs in no workflow, and its own check task rewrites its lock — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T6-02 · **Proposed milestone:** 0.0.6 · **Labels:** `type:chore` `area:tooling` +`area:fresh-ui` `priority:p1` `status:triage` · **Depends on:** none (T6-01 must not extend +`quality:scan` into `packages/fresh-ui` until this issue settles the lock policy) + +## Summary + +`packages/fresh-ui` is a published `@netscript/fresh-ui` package with 6 subpaths and a 66-item +registry, and it is the surface the scaffold's `/design` routes and every `ui:add` slice consume. +It is excluded from the root `check` task, excluded from the root `lint` task, and named by no step +in any workflow — so no CI job type-checks or lints it. It is not broken: its own `check` task +passes today. It is ungated, which means the next change to it is the one that breaks silently. +The reason it was excluded is real and must be solved, not bypassed: it carries its own +`deno.lock`, and running its check **mutates that lock**. + +## Area + +fresh-ui / tooling + +## Reproduction steps + +1. `rtk grep -n "fresh-ui" deno.json` → the name appears in the `check` exclude regex and the + `lint` exclude regex. +2. `rtk grep -rn "fresh-ui" .github/` → hits are `CODEOWNERS:26` and the two issue-template `area` + dropdowns only. No workflow step names it. +3. `md5sum packages/fresh-ui/deno.lock` → record. +4. `deno task --cwd packages/fresh-ui check` → exits **0**. +5. `md5sum packages/fresh-ui/deno.lock` → **changed**; `git status --porcelain` reports + ` M packages/fresh-ui/deno.lock`. +6. `git checkout -- packages/fresh-ui/deno.lock` to restore. + +Executed 2026-08-08 at `fac9e339042c`: step 4 exit `0`; lock md5 `93b0bf5e…` → `5a83f729…`. + +## Evidence + +Corpus: `research/repo-audit/web-layer.md` §11.3 and gap register item 12; verified at source. + +- `deno.json:34` — root `check` runs `.llm/tools/run-deno-check.ts --root packages --root plugins` + with `--exclude "^(packages/(fresh-ui)|…)"`. +- `deno.json:143` — root `lint` excludes `^(packages/(fresh-ui|cli)|…)`. +- `.github/workflows/ci.yml` runs the root `check`; nothing runs + `deno task --cwd packages/fresh-ui check`. +- `deno.json` workspace globs are `packages/*`, `packages/cli/e2e`, `plugins/*`, `examples/*`, + `apps/*` — so `packages/fresh-ui` **is** a workspace member, yet it ships + `packages/fresh-ui/deno.lock` and its tasks pass `--lock=deno.lock` + (`packages/fresh-ui/deno.json` tasks `check`, `test`, `tokens:build`). +- The structural cause of the private lock: `packages/fresh-ui/deno.json` imports resolve the SDK + from the **registry**, not the workspace — `"@netscript/sdk/auto-update": "jsr:@netscript/sdk@0.0.4/auto-update"`, + `"@netscript/sdk/desktop": "jsr:@netscript/sdk@0.0.4/desktop"`. +- One fresh-ui artifact is already gated: `deno.json:108` `check:assets-barrel` git-diff-gates + `packages/fresh-ui/registry.generated.ts`. The generated barrel is protected; the 80 source files + that produce it are not. +- Consumer stake: `research/repo-audit/web-layer.md` §7.1 — the scaffolded `/design/components` + gallery lists 50 of the registry's 66 items with no sync gate. An ungated registry package and an + ungated gallery snapshot are the same blind spot at two ends of one seam. + +## Current surface + +`packages/fresh-ui` type-checks and lints only when a human runs its package-local tasks. Its lock +is a second lock in a workspace whose other 29 packages share the root lock, and any invocation of +its `check` rewrites it, so a naive "just delete the exclusion" fix would make every CI run dirty +the working tree and fail lock-hygiene review. + +## Target contract + +`packages/fresh-ui` is type-checked and linted by CI on every PR that touches it, with a lock policy +that is decided explicitly and asserted, choosing one of: + +- **(a) Join the root lock.** Drop `packages/fresh-ui/deno.lock`, let the workspace root lock cover + it, and repoint `@netscript/sdk/*` at the workspace member. Requires confirming the published-pin + is not load-bearing for the registry's consumer story. +- **(b) Keep the private lock, gate it frozen.** Keep the pins, and run the package check with a + frozen-lock flag so a lock rewrite is a CI failure rather than a silent mutation, with the + regeneration step named in the failure message. + +Either way, the package-local check runs in CI, the lint exclusion is removed or narrowed to a +named rule set with a reason, and the working tree is clean after the job. + +## Acceptance + +- [ ] `packages/fresh-ui` is type-checked by a CI job on PRs that touch it. +- [ ] `packages/fresh-ui` is linted by a CI job on PRs that touch it. +- [ ] The lock policy is recorded in the PR body as (a) join-root-lock or (b) frozen-private-lock. +- [ ] Running the new CI step leaves `git status --porcelain` empty. +- [ ] A lock rewrite during the check fails the job instead of being committed. +- [ ] A deliberately broken type in `packages/fresh-ui/registry.ts` fails the new job (red-first). +- [ ] A deliberately introduced lint violation in `packages/fresh-ui` fails the new job. +- [ ] `deno.json:34` and `deno.json:143` no longer exclude `packages/fresh-ui`, or the remaining + exclusion names the specific rule and a linked issue. +- [ ] Tests cover the frozen-lock failure path if option (b) is chosen. +- [ ] `gate:` root `deno task check`, `deno task lint`, and `deno task fmt:check` stay green. + +## Boundaries + +- Do **not** fix the `/design/components` 50-vs-66 registry drift here — that is a separate + scaffold-generation defect owned by the T2 pack's `/design` registry sync draft, referenced from + #1335's inventory. +- Do **not** reopen #1328 (closed 2026-08-07): that issue owns the *generated consumer project's* + quality gates; this issue owns the *framework repo's* CI coverage of one package. +- Do **not** duplicate #1335 — the umbrella owns generated-surface conformance, not framework CI + wiring. +- Do **not** widen `quality:scan` roots here; T6-01 owns the quality gate and is blocked on this + issue's lock decision. +- Do **not** remove `packages/cli` from the `lint` exclusion in the same change; it is a separate, + larger backlog with its own noise profile. + +## Docs/consumer proof + +`@netscript/fresh-ui` is published to JSR and consumed by every scaffolded app. After this lands, +the package's README archetype/permissions claims and its `deno doc` surface are backed by a CI +type-check, so a consumer reading the registry reference is reading a gated surface. The +consumer-visible receipt is a CI run link on a PR that touches only `packages/fresh-ui`. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Source: +`research/repo-audit/web-layer.md` §11.3 ("New issues worth filing … the `packages/fresh-ui` CI +check/lint exclusion" — no board owner found by the §7 dedup sweep). Lock mutation and green-check +status re-verified by execution at `fac9e339042c`; the lock was restored with `git checkout --`. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T6-03-doctrine-verdict-refresh.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T6-03-doctrine-verdict-refresh.md new file mode 100644 index 0000000000..7f0f214bba --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.6-verification-docs-rfcs/T6-03-doctrine-verdict-refresh.md @@ -0,0 +1,143 @@ +# docs(doctrine): the codebase verdict table names five deleted packages, omits 14 live units, and its `arch:check:repo` gate has been accepted-red since 2026-06-21 — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T6-03 · **Proposed milestone:** 0.0.6 · **Labels:** `type:docs` `area:docs` +`area:tooling` `area:packages` `priority:p2` `status:triage` · **Depends on:** none (this is the +denominator the rest of the remediation train measures against) + +## Location + +`docs/architecture/doctrine/10-codebase-verdict-and-handoff.md`, `.llm/harness/debt/arch-debt.md` +(entry "repo doctrine task — full historical scan remains red"), `.llm/tools/fitness/check-doctrine.ts`, +`deno.json:155-156`, `rfcs/`. + +## Kind of issue + +Out-of-date content with governance consequences — the doctrine's own definition of done cannot be +evaluated against the current package set, and its mechanical gate cannot be read as a verdict. + +## Summary + +The doctrine's per-package verdict table is the roadmap's denominator: `10-…md:197-208` defines +doctrine completion as "`arch:check` passes for every package … and the codebase walk above shows +zero Restructure or Rewrite verdicts". That walk names five packages that no longer exist, omits 14 +that do, the promised engineering reference is ~20% written, and the repo-wide gate that would +adjudicate it has been accepted-red for seven weeks. Now: a remediation roadmap that schedules +refactor work against this table would schedule work on deleted units and skip the entire auth and +`plugin-*-core` tiers. + +## Details + +All counts executed at baseline `fac9e339042c` on 2026-08-08. Corpus: +`research/repo-audit/scaffold-doctrine.md` §3.1-3.5, divergences D6/D7/D8/D9/D10. + +**D6 — verdict table vs reality.** `10-…md:22-51` holds 29 rows. +- Rows naming units absent from `ls packages/`: `@netscript/shared`, `@netscript/streams`, + `@netscript/triggers`, `@netscript/workers`, `@netscript/sagas`. Row naming a unit absent from + `ls plugins/`: `plugins/hello-world`. (Four of the five packages were plausibly renamed into the + `plugin-*-core` tier; the re-walk must record rename-vs-deletion per row, not assume.) + Note `@netscript/shared` also has a dedicated doctrine subsection at `06-archetypes.md:378-388` + and is remediation priority #3 at `10-…md:46,67-69`. +- Live units: **30** dirs under `packages/` + **6** under `plugins/` = 36. Units with **no row**: + `ai`, `auth-better-auth`, `auth-kv-oauth`, `auth-workos`, `bench`, `mcp`, `plugin-ai-core`, + `plugin-auth-core`, `plugin-sagas-core`, `plugin-streams-core`, `plugin-triggers-core`, + `plugin-workers-core`, `plugins/ai`, `plugins/auth` — **14**, including the whole auth family and + the entire `plugin-*-core` tier that `11-plugin-thinness-and-base-seams.md` is built around. +- The same staleness reaches the archetype assignment table at `06-archetypes.md:368-381`. + +**D7 — engineering reference.** `10-…md:79-181` specifies ten required contents. §7 (debt registry) +exists as `.llm/harness/debt/arch-debt.md`. §6 (fitness-function source) is partial: +`.llm/tools/fitness/` contains exactly 5 files (`audit-jsr-package.ts`, `check-doctrine.ts`, +`check-ds-color-utilities.ts`, `check-ds-no-raw-hex.ts`, `check-ds-gates_test.ts`). §1–§5 and +§8–§10 — archetype recipes, role-folder recipes, pattern skeletons, per-anti-pattern fix catalogue, +refactor playbooks, review checklist, glossary, phased roadmap — do not exist. + +**D8 — the accepted-red gate, decomposed.** `deno task arch:check` exits **0**; it iterates 16 +hand-listed roots (`deno.json:155`) out of 36 live units, so **20 live units have no doctrine gate +at all**. `deno task arch:check:repo` exits **1** with `FAIL=53 WARN=341 INFO=1`. The 53 failures +decompose into exactly two mechanical causes, neither of which is package debt: +- **52 × `FAIL A14: Jest/Vitest globals`** are false positives. `check-doctrine.ts:407` matches a + bare `describe(` / `it(` / `expect(` anywhere in a `*_test.ts` file without checking where the + identifier came from. Example: `packages/database/tests/migrate-retry_test.ts:10` is + `import { describe, it } from 'jsr:@std/testing@^1/bdd';` — the sanctioned Deno BDD API. +- **1 × `FAIL A1: mod.ts missing`** is structural. `deno.json:156` runs `check-doctrine.ts` with no + `--root`, so the checker evaluates the repository root as a single package + (`check-doctrine.ts:110-113`) and walks trees that are not packages: 16 of the cited findings are + under `.llm/tmp/eis-chat/…`, plus `docs/site/` and `.llm/tools/`. +The `arch-debt.md` entry recording this is Created **2026-06-21**, `Status: open, DEBT_ACCEPTED`, +Target "2026-Q3", closing gate "reduce unrelated root failures **or** replace the legacy root scan +with debt-aware package selection". The decomposition above shows the second branch is the cheap +one. + +**D9/D10 — RFC practice divergence.** `ls rfcs/` → `0000-template.md`, `README.md`. Zero numbered +RFCs have ever landed. Real design records live at `.llm/runs/plan-*--seed/design/canonical/`, +produced by merged PRs #891 (deploy plugin family) and #1123 (OpenAPI→MCP) and cited by +`.github/labels.yml` label descriptions as "RFC #891" / "RFC #1123". `rfcs/README.md:82-86` +self-flags as provisional, deferring to "a ratified doctrine governance statement" that does not +exist in `docs/architecture/doctrine/`. Meanwhile `arch-debt.md` carries 5 `DECISION_PENDING` +entries, 4 of them public-surface questions that `rfcs/README.md:15-24` says *require* an RFC: +`CRON-SUBSYSTEM-DUP` (1536), `RUN-ARTIFACT-ARCHIVAL-POLICY` (1582), `PAGEBUILDER-LEGACY-COMPAT-TREE` +(1598), `FORMPAGEPROPS-PLAYGROUND-MIGRATION` (1613), `REDIS-LEGACY-VALUE-FALLBACK` (1628). The +archival-policy entry proposes pruning the very tree where the de-facto RFCs live. + +## Target contract + +1. The verdict table enumerates every live unit under `packages/` and `plugins/` and nothing else, + with rename-vs-deletion recorded for each removed row. +2. `arch:check:repo` is a real verdict: it iterates live workspace members rather than treating the + repository root as a package, and its A14 rule does not fire on `@std/testing/bdd` imports. + Whatever residue remains is either green or listed as named debt entries with owners. +3. The `arch-debt.md` accepted-red entry either closes or states a dated closure plan naming the two + causes above. +4. The engineering reference has a written, dated plan for §1–§5/§8–§10 — authored *from* the + refactors as a byproduct, not as a separate project. +5. The RFC divergence is recorded and resolved one way: either `.llm/runs/*/design/canonical/` + bundles are promoted to numbered `rfcs/NNNN-*.md` at acceptance, or `rfcs/README.md` is retired + and the harness path is named as canonical. + +## Acceptance + +- [ ] The verdict table lists all 36 live units and no deleted ones. +- [ ] Each removed row is recorded as renamed (with its new name) or deleted. +- [ ] `06-archetypes.md` archetype assignment table matches the refreshed verdict table. +- [ ] `arch:check:repo` iterates live workspace members instead of the repository root. +- [ ] The A14 rule does not fire on a test importing `describe`/`it` from `@std/testing/bdd`. +- [ ] `arch:check:repo` no longer walks `.llm/tmp/`, `docs/`, or `.llm/tools/`. +- [ ] The `arch-debt.md` accepted-red entry is closed or carries a dated closure plan. +- [ ] The doctrine records which of the 36 units `arch:check` gates and why any are excluded. +- [ ] The engineering-reference gap (§1–§5, §8–§10) is recorded as a dated plan, not silence. +- [ ] The RFC-location divergence is resolved in `rfcs/README.md` with the 5 `DECISION_PENDING` + entries mapped to the chosen location. +- [ ] A test fails if the verdict table names a directory that does not exist. +- [ ] A test fails if a live `packages/*` or `plugins/*` directory has no verdict row. +- [ ] `gate:` `deno task arch:check` stays green and `deno task arch:check:repo` exits 0 or its + residue is enumerated in `arch-debt.md`. + +## Boundaries + +- Do **not** perform the six open verdict-Refactor/Restructure refactors here (`packages/database`, + `packages/kv`, `packages/service`, `packages/workers`, `plugins/triggers`, `plugins/workers`) — + this issue re-establishes the denominator; the refactors are separate slices. +- Do **not** file the five `DECISION_PENDING` RFCs here; this issue only records where RFCs live. + The generated-workspace governance RFC (D5) is a separate T3/T8 item. +- Do **not** duplicate #1093 — plugin-discovery hardcoding is its own defect with its own doctrine + check requirement. +- Do **not** touch #1280 (`status:blocked` upstream) or #1320 (blocked on `@ag-ui/core`). +- Do **not** re-file #232 or #301 (docs/stable umbrellas); this is a doctrine-document refresh, not + a docs program. + +## Docs/consumer proof + +`docs/architecture/doctrine/` is the published governance surface every framework contributor and +every harnessed agent reads via `.agents/skills/netscript-doctrine`. The consumer proof is that a +contributor running `deno task arch:check:repo` gets a verdict they can act on rather than 53 +failures they must learn to ignore — and that the doctrine skill's routing no longer points at rows +for packages that do not exist. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Source: +`research/repo-audit/scaffold-doctrine.md` §3.1-3.5 and divergence rows D6–D10; §6 Phase A names the +verdict re-walk as the roadmap's denominator. The `arch:check:repo` failure decomposition (52 A14 +false positives + 1 root-as-package A1) is new to this draft — executed at `fac9e339042c`, not +present in the corpus, and it converts the seven-week-old "reduce unrelated root failures" gate text +into two named, cheap fixes. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T1-02-orpc-seam-re-exposure.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T1-02-orpc-seam-re-exposure.md new file mode 100644 index 0000000000..6c92f7b53a --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T1-02-orpc-seam-re-exposure.md @@ -0,0 +1,157 @@ +# feat(sdk): createServiceClient seals every oRPC link seam — headers, interceptors, plugins, fetch and the link itself are unreachable from the supported API — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T1-02 · **Proposed milestone:** `0.0.7` (post-rename-shift "Typed seams + +generation", SYNTHESIS §5.3) · **Labels:** `type:feat` `area:sdk` `area:service` `area:plugins` +`priority:p1` `status:triage` · **Depends on:** T1-01 (RFC-A ratification), T1-04 (transport-policy +consolidation must land first or concurrently) + +## Summary + +`CreateServiceClientOptions` is a closed nine-field record and `ServiceClientContext` is a closed +interface, so no consumer can add a header, an interceptor, a link plugin, a custom `fetch`, or a +typed per-call context field. The link factory that would let them work around it is +package-private, even though the ports module's own doc comment advertises "the transport seam". On +the server side `RPCHandlerConfig.plugins` exists but the builder never populates it, and the plugin +type it accepts hands `unknown` to plugin authors. The result is that the only supported way to send +one extra header is to fork ~90 lines of framework internals per app — and silently lose NetScript's +client span in the process. + +## Evidence + +- Corpus: `research/repo-audit/services-sdk.md` §2.1–§2.4, §3.1–§3.3, §3.6, gap register + S4/S11/S12/S13/S21; `research/external/orpc.md` §4 (G2, G3, G6, G7), §5; + `research/repo-audit/auth.md` §0 proof 1–2, §2. +- Source at baseline `fac9e339042c` (re-verified for this draft): + - `packages/sdk/src/ports/service-client.ts:203-222` — nine fields: + `contract, serviceName, routerName, protocol, apiPath, apiVersion, port, timeout, + propagateTraceContext`. No `headers` / `fetch` / `interceptors` / `plugins` / `link` / context + parameter. + - `packages/sdk/src/ports/service-client.ts:129-155` — `ServiceClientContext` is a concrete + interface (`signal`, `cache`, retry knobs, `traceHeaders`); `:160-171` hard-codes it into + `ServiceClientMethod`. + - `packages/sdk/src/client/service-client.ts:41-49` — `port` and `timeout` are never destructured; + `packages/sdk/src/presets/define-services.ts:106-116` forwards both into that ignoring function, + so the dead options look live at L2 and L3. + - `packages/sdk/src/client/http-client-link.ts:82` (`method:` literal), `:82-101` (`headers` + literal: `Content-Type` + optional trace), `:102-126` (`plugins` literal: + `ClientRetryPlugin{retry:0}` + `DedupeRequestsPlugin` with frozen filter/groups), `:127+` + (hard-coded `fetch` carrying the CLIENT span). + - `packages/sdk/src/client/mod.ts:15-36` — exports only `createServiceClient`, `isDefinedError`, + `safe`, and types. `createHttpClientLink` is private. + - `packages/sdk/src/ports/mod.ts:7` — "…discovery metadata, and the transport seam" — while + `packages/sdk/src/ports/client-link-factory.ts:18-25` (`ClientLinkPort`, + `ClientLinkCallOptions`) is not exported from that module. + - `packages/service/src/primitives/handlers.ts:41-58` — `RPCHandlerConfig` declares `plugins`, + `tracing`, `errorHandling`, `deduplication`, `warnOnlyCodes`, `logging`, `debug`; + `packages/service/src/builder/service-rpc.ts:57` calls + `createRPCHandler(options?.rpcRouter ?? router, { serviceName, debug })` — nothing else is ever + passed. + - `packages/service/src/types.ts:216-225` — `ServiceHandlerPlugin.init?(options: unknown, router: + unknown)`; `packages/logger/orpc-plugin.ts:11-42` redeclares shim option types because of it. + - `packages/plugin/src/config/domain/plugin-contributions.ts:12-39` — no client-side contribution + group. +- Upstream (pinned 1.14.6, surface identical to 1.14.15 per `research/external/orpc.md` §0): + `StandardLinkOptions` (`interceptors`, `clientInterceptors`, `plugins`), + `LinkFetchClientOptions` (`fetch`, `adapterInterceptors`, `plugins`), + `StandardRPCLinkCodecOptions` (`headers`, `method`, `fallbackMethod`, `maxUrlLength`, + `customJsonSerializers`), `ClientContext` as a free type parameter. + +## Current surface + +One constructor with no seams. `createServiceClient` builds `rpcPath`, calls the private +`createHttpClientLink`, and wraps the result in `createORPCClient` +(`packages/sdk/src/client/service-client.ts:41-66`). Consumers get exactly what that function +decided: two frozen client plugins, one header set, one `fetch`. `@netscript/sdk/desktop` proves the +link is swappable in principle (`packages/sdk/src/desktop/application/desktop-rpc-client.ts:18-20` +builds a MessagePort `RPCLink` with `customJsonSerializers`) and equally closed in practice. On the +server, `withRPC()` (`packages/service/src/builder/service-builder.ts:91-104`) and +`DefineServiceOptions` (`packages/service/src/presets/define-service.ts:112-143`) accept no +`plugins`, so a plugin that wants a server oRPC plugin must drop to L1 and hand-mount into Hono. + +## Target contract + +Per RFC-A §3.1–§3.5, §3.9, §3.10: + +1. **Client construction opens.** `CreateServiceClientOptions` gains an optional, + ordered `with?: TWith` contribution chain and a mutually exclusive `link?: ClientLinkPort<…>` + escape hatch. Omitting both yields byte-identical behaviour and types to today. +2. **Context becomes a parameter.** `ServiceClient` and + `ServiceClientMethod`; `BaseServiceClientContext` is today's + shape; `ServiceClientContext` survives one minor as a `@deprecated` alias. +3. **The transport seam is exported.** `createHttpClientLink` from `@netscript/sdk/client`; + `ClientLinkPort` / `ClientLinkCallOptions` from `@netscript/sdk/ports` — closing the + doc-vs-export contradiction at `packages/sdk/src/ports/mod.ts:7`. +4. **Framework defaults become contributions.** Retry, dedupe, and the CLIENT span are composed + through the same public path, so there is no private fast lane. +5. **Dead options die.** `port` and `timeout` are removed from `CreateServiceClientOptions` and + `DefineServiceConfig`; `timeout` is re-expressed as a contribution over `AbortSignal.timeout`. +6. **Server reachability + typing.** `withRPC()` and `DefineServiceOptions` accept `plugins` and + `warnOnlyCodes` and pass them to `createRPCHandler`; `ServiceHandlerPlugin` is typed to upstream's + `init?(options: StandardHandlerOptions, router: Router)`; the never-read + `deduplication` option is either wired or removed. +7. **Env boundary.** Contribution `environment` is checked at construction; `server`-only in a + browser build throws. + +## Acceptance + +- [ ] `createServiceClient` accepts an ordered contribution chain and composes headers, interceptors, + client interceptors, adapter interceptors, link plugins and `fetch` from it. +- [ ] `createServiceClient` called without a chain produces the same request on the wire as today. +- [ ] A type fixture proves an existing nine-field call site compiles unchanged after the change. +- [ ] `ServiceClient` and `ServiceClientMethod` carry a client-context type parameter with + `BaseServiceClientContext` as the default. +- [ ] `createHttpClientLink`, `ClientLinkPort` and `ClientLinkCallOptions` are exported from their + documented subpaths. +- [ ] Retry, dedupe and the CLIENT span are composed through the public contribution path, not + inline literals. +- [ ] `port` and `timeout` are removed from the client and `defineServices` option records. +- [ ] `withRPC()` and `defineService()` forward `plugins` and `warnOnlyCodes` to + `createRPCHandler`, proven by a server test asserting a custom plugin's `init` ran. +- [ ] `ServiceHandlerPlugin.init` is typed to upstream's `StandardHandlerOptions`, and + `packages/logger/orpc-plugin.ts` deletes its shim option types. +- [ ] `RPCHandlerConfig.deduplication` is either honoured by `createRPCPlugins` or removed. +- [ ] NEGATIVE: a test asserts a request carries no contribution-supplied header when the + contribution is absent from the chain. +- [ ] NEGATIVE: a test asserts two contributions declaring the same header key fail at construction + with both names in the message. +- [ ] NEGATIVE: a test asserts two contributions supplying `fetch` fail at construction. +- [ ] NEGATIVE: a type fixture asserts a contribution built against a future envelope version is not + assignable to the current host. +- [ ] NEGATIVE: a test asserts a `server`-only contribution throws when constructed in a browser-like + environment. +- [ ] `gate:` `deno task publish:dry-run` passes for `@netscript/sdk` and `@netscript/service` with + `--isolatedDeclarations` intact. +- [ ] `gate:` `deno task check` and `deno task test` pass at the repo root. + +## Boundaries + +- Do **not** implement the auth contribution here — that is T1-05. +- Do **not** move trace-context propagation onto the chain here — that is T1-06. +- Do **not** repair `safe`/`isDefinedError` or `baseContract` here — that is T1-03. +- Do **not** change HTTP method inference or GET dedupe policy semantics here — that is T1-04. +- Do **not** duplicate **#451** (in-process link-mode adapter) — this issue creates the public link + seam #451 needs; #451 stays open and separate. +- Do **not** duplicate **#1093** (discovery hardcodes official plugin factories). If a + `PluginContributions` client group is added here rather than in T1-05, it must not use a closed + literal union like `plugin-contributions.ts:16`. +- Do **not** duplicate **#928** / **#934** (frontend contribution contracts, deny-by-default + gateway). +- Do **not** touch the query-key algebra, `createQueryFactory`'s dropped abort signal, or nested + routers — T2 generation pack. +- Do **not** touch `PluginContractRouter = object` or the Hono-vs-oRPC middleware seam — T3. + +## Docs/consumer proof + +`docs/site/services-sdk/sdk.md` and `docs/site/reference/sdk/index.md` gain one worked contribution +example that compiles under the docs gate; `packages/sdk/README.md`'s export table lists the newly +exported transport seam; and the "escape hatch = fork the link" paragraph is deleted rather than +softened. Consumer proof is that an app can add one header without importing anything from +`@netscript/telemetry` and without losing its client span — assert the span is still emitted in the +same test that asserts the header. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Sourced from +`research/repo-audit/services-sdk.md` (S4, S11, S12, S13, S21), `research/external/orpc.md` (G2, G3, +G6, G7) and `research/repo-audit/auth.md` (G1); all cited lines re-verified against worktree baseline +`fac9e339042c`. No GitHub mutation was performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T1-03-typed-error-repair.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T1-03-typed-error-repair.md new file mode 100644 index 0000000000..7901cad9c8 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T1-03-typed-error-repair.md @@ -0,0 +1,134 @@ +# fix(sdk): safe() drops the contract error type — isDefinedError narrows to never and the published docs example does not compile — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T1-03 · **Proposed milestone:** `0.0.7` (post-rename-shift "Typed seams + +generation", SYNTHESIS §5.3) · **Labels:** `type:fix` `area:sdk` `area:contracts` `area:docs` +`priority:p1` `status:triage` · **Depends on:** T1-01 (RFC-A ratification, §3.7); independent of +T1-02 and can land first + +## Summary + +NetScript re-implements oRPC's `safe()` and `isDefinedError()` without the `TError` generic, so the +error channel is `unknown`, `Extract` evaluates to `never`, and reading +`error.code` after a successful `isDefinedError` narrow is a type error. This is proven by an +executed `deno check`, and it is the exact snippet the published docs tell users to write +(`docs/site/services-sdk/sdk.md:199`). A contributing cause sits one layer down: `baseContract` is +annotated `ReturnType`, which instantiates the generic at its constraint and erases +the six declared error codes to the open `ErrorMap` index signature — so even a correct `safe()` +could not narrow to NetScript's error vocabulary today. oRPC's headline typed-error DX is fully lost +while three published documents assert it works. + +## Evidence + +- Corpus: `research/repo-audit/services-sdk.md` §3.4, §3.5, gap register S1/S2/S3; + `research/external/orpc.md` §4 (G4) with the executed probe transcript. +- Source at baseline `fac9e339042c` (re-verified for this draft): + - `packages/sdk/src/client/errors.ts:75-77` — + `export function isDefinedError(error: T): error is Extract`. + - `packages/sdk/src/client/errors.ts:86-92` — + `export async function safe(promise: PromiseLike): Promise>`. + - `packages/sdk/src/client/errors.ts:49` — `SafeResult`, so `TError` + defaults to `unknown` at every call site. + - `packages/sdk/src/ports/service-client.ts:160-171` — `ServiceClientMethod` returns + `Promise`, discarding upstream's `ClientPromiseResult>`. + - `packages/contracts/src/application/contract-primitives.ts:81` — + `export const baseContract: ReturnType = oc.errors(commonErrorMap);` (the six + codes are declared at `:21-52`); the doc comment at `:54-69` claims the contract is "genuinely + typed rather than erased to `any`" — true for input/output, false for the error map. + - `docs/site/services-sdk/sdk.md:199` — tab "Safe error narrowing" ships + `if (isDefinedError(error)) return { code: error.code, status: error.status };`. + - `docs/site/services-sdk/how-to/discover-services.md:138-147` and `:212-224` — the same pattern, + with the comment "`error.code` and `error.data` are typed from the contract". + - `packages/sdk/tests/readme-doctest_test.ts:36-37` — the guard that should have caught this + **re-declares** `safe` and `isDefinedError` with different signatures instead of importing them, + so the doctest passes against a fiction. +- Executed probe (reproduced from `research/repo-audit/services-sdk.md` §8; re-run before filing): + + ```ts + import { isDefinedError, safe } from 'packages/sdk/src/client/mod.ts'; + declare const p: Promise<{ ok: boolean }>; + const [error] = await safe(p); + if (error && isDefinedError(error)) { const c: string = error.code; } + ``` + + `deno check --unstable-kv --config deno.json .ts` → + `TS2339 [ERROR]: Property 'code' does not exist on type 'never'.` + +- Upstream signature (pinned 1.14.6): `safe(promise: + ClientPromiseResult): Promise>`. + +## Current surface + +Three published documents and one README export table advertise contract-typed error narrowing; +the shipped helpers cannot provide it, and the only test that touches them tests re-declared +fictions. Consumers who follow the docs get a compile error, and the workaround the compiler pushes +them toward is `error as { code: string }` — an unsound cast the type-soundness epic (#1278) then +counts as debt. + +## Target contract + +Per RFC-A §3.7: + +1. `safe` and `isDefinedError` regain the upstream shape, with + `SafeResult`'s failure arms discriminating on `isDefined` exactly as upstream + does. +2. `ServiceClientMethod` carries the error channel so `TError` reaches `safe()` from the contract — + either by aliasing to oRPC's derived client type or by threading + `ErrorFromErrorMap` through the existing structural derivation. +3. `baseContract`'s annotation preserves the six literal error-map keys. `ReturnType` is replaced with a spelling that is both literal-preserving and + `--isolatedDeclarations`-safe, following the precedent already set by `BaseContractRoute` / + `BaseContractOutputRoute` (`packages/contracts/src/application/contract-primitives.ts:125-159`). +4. `packages/sdk/tests/readme-doctest_test.ts` imports the real helpers; the local `declare + function` shims are deleted. + +## Acceptance + +- [ ] `safe` and `isDefinedError` carry the upstream error generic and `SafeResult` discriminates on + `isDefined`. +- [ ] A type fixture proves `error.code` narrows to the contract's declared code union after + `isDefinedError`. +- [ ] `baseContract`'s type preserves the six literal error-map keys. +- [ ] NEGATIVE: a type fixture asserts a code that is not in the contract's error map is rejected + (today `'TOTALLY_MADE_UP_CODE'` is assignable to `keyof` the error map). +- [ ] NEGATIVE: a type fixture asserts `isDefinedError` does not narrow a non-oRPC thrown value to a + defined error. +- [ ] The docs snippet at `docs/site/services-sdk/sdk.md:199` compiles as written, proven by an + executed check rather than by inspection. +- [ ] The equivalent snippets in `docs/site/services-sdk/how-to/discover-services.md` compile as + written. +- [ ] `packages/sdk/tests/readme-doctest_test.ts` imports `safe` and `isDefinedError` from the + package instead of re-declaring them. +- [ ] `gate:` `deno task publish:dry-run` passes for `@netscript/sdk` and `@netscript/contracts` + with `--isolatedDeclarations` intact. +- [ ] `gate:` `deno task check` and `deno task test` pass at the repo root. + +## Boundaries + +- Do **not** open the client construction seam here — that is T1-02. +- Do **not** duplicate **#1263** (`service: generated by-id handler returns 500 {defined:false} for a + missing row instead of a defined 404`, `0.0.6`). #1263 is the **server** raising the wrong error; + this issue is the **client** being unable to narrow a correctly-raised one. Both should be true + before the docs example is honest end to end — cross-reference, do not merge. +- Do **not** duplicate **#1278** (type soundness ratification, `0.0.6`). #1278's inventory is + prose-only; **read its body before filing**. If it already lists `safe`/`isDefinedError` or the + `baseContract` widening, file this as a child with `Part of #1278` instead of standalone. Either + way, do not turn this issue into a soundness sweep — it fixes two symbols and one annotation. +- Do **not** duplicate **#1296** (`contracts/ai` source-side rows) or **#1108** (generated package + references vs live export maps). +- Do **not** rewrite the SDK docs page wholesale — the Tier-1 docs rewrite is the T5 pack. This issue + changes only what is needed to make the existing snippets true. +- Do **not** extend the error map with new codes. + +## Docs/consumer proof + +The proof is executable: the two published snippets compile unmodified, and the README doctest +exercises the real exports. Consumer-side, a scaffolded service-to-service call can branch on a +contract error without a cast — demonstrate it in the fixture so the pattern is greppable, and state +in the SDK reference that `error.data` is typed from the contract's Zod schema. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Sourced from +`research/repo-audit/services-sdk.md` (S1, S2, S3) and `research/external/orpc.md` (G4), both of +which carry executed `deno check` transcripts; all cited lines re-verified against worktree baseline +`fac9e339042c`. No GitHub mutation was performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T1-04-transport-policy-consolidation.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T1-04-transport-policy-consolidation.md new file mode 100644 index 0000000000..e26c95be1f --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T1-04-transport-policy-consolidation.md @@ -0,0 +1,125 @@ +# refactor(sdk): HTTP method and GET-cache policy are decided inline inside the link — oRPC v2's POST-only default would break every client call path — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T1-04 · **Proposed milestone:** `0.0.7` (post-rename-shift "Typed seams + +generation", SYNTHESIS §5.3) · **Labels:** `type:refactor` `area:sdk` `area:contracts` `area:deps` +`priority:p1` `status:triage` · **Depends on:** T1-01 (RFC-A ratification, §3.11); must land before +or with T1-02 + +## Summary + +NetScript's HTTP transport policy — which procedures are GET, and which requests are deduplicated +and cached — is three literals sitting inside `createHttpClientLink`. oRPC v2 is in public beta and +its `main` branch already carries `feat(rpc): restrict RPC handlers to POST, PUT, PATCH and DELETE by +default` plus a `MethodOverrideHandlerPlugin`. Once T1-02 makes the link publicly composable, that +policy becomes observable to every contribution and every app, and the v2 migration stops being a +one-function change. Consolidating it behind one NetScript-owned function now is cheap; doing it +after a public extension seam exists is not. + +## Evidence + +- Corpus: `research/external/orpc.md` §0 (v2 `beta` dist-tag `2.0.0-beta.25`; the two v2 commits + verified absent from the 1.14.15 tarball), §6 "Upgrade implications" — *"Plan the remediation so + the transport policy lives behind one NetScript-owned function, not scattered across link + construction — otherwise the v2 migration touches every client call path"*; + `research/repo-audit/services-sdk.md` §0, §3.1, gap register S21/S22. +- Source at baseline `fac9e339042c` (re-verified for this draft): + - `packages/sdk/src/client/http-client-link.ts:17,82` — `inferRPCMethodFromContractRouter` is + imported from `@orpc/client` and applied inline as `method:`. Repo-wide, `grep -rn + 'inferRPCMethodFromContractRouter\|StrictGetMethodPlugin\|fallbackMethod' packages plugins` + returns **only** these two lines — there is no owned policy function anywhere. + - `packages/sdk/src/client/http-client-link.ts:109` — + `filter: ({ request }) => request.method === 'GET'`, i.e. the dedupe policy re-derives the method + decision from the wire instead of from the contract. + - `packages/sdk/src/client/http-client-link.ts:110-125` — the `force-cache` group condition is a + second, independent literal keyed off `context?.cache`. + - `packages/sdk/src/client/http-client-link.ts:103-107` — `ClientRetryPlugin` default frozen at + `retry: 0`; `research/repo-audit/services-sdk.md` §3.2 lists the three upstream client plugins + (`BatchLinkPlugin`, `RetryAfterPlugin`, `SimpleCsrfProtectionLinkPlugin`) that are unreachable. + - `packages/sdk/src/desktop/application/desktop-rpc-client.ts:18-20` — the second transport makes + its own independent decisions (`customJsonSerializers`), so there is already policy divergence + across two links with no shared source of truth. + - Version state: `deno.json:215-221` pins `^1.14.6` (`@orpc/otel` `^1.14.7`); `deno.lock` resolves + `1.14.6`, which drags `@orpc/shared@1.14.6` **and** `@orpc/shared@1.14.7` into one graph — a + known `instanceof ORPCError` hazard the pinned client already ships a `Symbol.hasInstance` + workaround for (`research/external/orpc.md` §6). + +## Current surface + +Three coupled decisions with no owner: the contract-derived method, the GET-only dedupe filter, and +the `force-cache` grouping. They are consistent today only because one function wrote all three. A +consumer reading the SDK cannot discover the policy without reading the link body, and nothing +prevents the desktop link, a future in-process link (#451), or a contribution from disagreeing with +it. `deno task deps:latest` reports all seven oRPC packages at `1.14.6/1.14.7 → 1.14.15`, and the +1.14.6 → 1.14.15 public export list for `@orpc/server` is byte-identical, so the bump itself is +mechanical. + +## Target contract + +Per RFC-A §3.11: + +1. One exported-internal function — e.g. `resolveTransportPolicy(contract, options)` — returns the + full policy object: `method`, `fallbackMethod`, `maxUrlLength`, the dedupe predicate, and the + cache-group descriptors. Every link (HTTP, desktop, and any future one) consumes it; no link + re-derives policy from `request.method`. +2. The policy is derived from the **contract and procedure metadata**, not from the wire. When + `NetScriptProcedureMeta.policy.cache` is present (RFC-A §3.6) it is an input to the policy + function, not a second mechanism. +3. **Contributions never observe the HTTP method.** They observe procedure path, input, context and + metadata. This is the forward-compat rule that keeps the v2 migration inside one function. +4. A documented policy-override point exists for the v2 transition (a single `transportPolicy?` + option, resolved before contributions compose), so `MethodOverrideHandlerPlugin`-style adaptation + is a config change rather than a code change. +5. oRPC is bumped `1.14.6 → 1.14.15` and the duplicated `@orpc/shared` copies collapse to one, + using the `.llm/tools/deps/` wrappers rather than hand-rolled registry reads. + +## Acceptance + +- [ ] One NetScript-owned function returns the complete client transport policy for a contract. +- [ ] `createHttpClientLink` and the desktop link both consume that function; neither contains a + method or cache literal. +- [ ] The dedupe predicate is derived from the resolved policy, not from `request.method`. +- [ ] Tests cover GET-inferred, POST-inferred, and metadata-overridden procedures against the policy + function directly. +- [ ] NEGATIVE: a test asserts a contribution cannot read or alter the resolved HTTP method. +- [ ] NEGATIVE: a test pins the current wire behaviour (which procedures are GET, which requests + dedupe) so the refactor is proven behaviour-preserving. +- [ ] A simulation test flips the policy to "POST for everything" and asserts every client call path + still succeeds, standing in for oRPC v2's default. +- [ ] `deno task deps:latest` shows the oRPC family at `1.14.15` and `deno why @orpc/shared` shows a + single resolved copy. +- [ ] `gate:` `deno task check`, `deno task test`, and `deno task publish:dry-run` pass. +- [ ] `gate:` `deno task e2e:cli run scaffold.runtime --cleanup --format pretty` passes, proving the + generated app's client calls are unchanged. + +## Boundaries + +- Do **not** migrate to oRPC v2 in this issue. This makes the future migration a one-function change; + it does not perform it. +- Do **not** open the client construction seam here — that is T1-02. If T1-02 lands first, this issue + must additionally prove no contribution can reach the method decision. +- Do **not** unfreeze the retry/dedupe defaults or expose `BatchLinkPlugin` / `RetryAfterPlugin` / + `SimpleCsrfProtectionLinkPlugin` here — those become reachable through the contribution chain in + T1-02. +- Do **not** duplicate **#1320** (`deps: collapse to a single Zod instance`, `0.0.6`, blocked) — that + is a different duplicated dependency; this issue's dedup target is `@orpc/shared` only. +- Do **not** duplicate **#451** (in-process link-mode adapter) — it becomes a third consumer of the + policy function, and stays its own issue. +- Do **not** change server-side handler method policy (`StrictGetMethodPlugin` and friends are + unused today); server-side transport policy is out of scope. + +## Docs/consumer proof + +The SDK reference gains one short "transport policy" section stating where the method and cache +decisions are made and that they are contract-derived — replacing the current situation where the +only answer is "read `http-client-link.ts`". Consumer proof is the scaffold runtime E2E: the +generated app's showcase calls behave identically before and after, and the pinned-behaviour test +names the exact procedures whose method would change under oRPC v2, so the migration's blast radius +is a list rather than a guess. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Sourced from +`research/external/orpc.md` §0/§6 (the explicit forward-compat recommendation) and +`research/repo-audit/services-sdk.md` (S21, S22); all cited lines re-verified against worktree +baseline `fac9e339042c`, including the repo-wide grep proving there are exactly two policy call +sites. No GitHub mutation was performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T1-05-auth-contribution-dogfood.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T1-05-auth-contribution-dogfood.md new file mode 100644 index 0000000000..ad07153f71 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T1-05-auth-contribution-dogfood.md @@ -0,0 +1,151 @@ +# feat(sdk/auth): the typed service client cannot send any credential — prove the contribution chain with a first-party auth contribution — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T1-05 · **Proposed milestone:** `0.0.7` (post-rename-shift "Typed seams + +generation", SYNTHESIS §5.3) · **Labels:** `type:feat` `area:sdk` `area:auth` `area:plugins` +`priority:p1` `status:triage` · **Depends on:** T1-01 (RFC-A ratification, incl. answers to Q1 +cookie topology and Q2 contribution-group shape), T1-02 (the chain), T1-04 (transport policy) + +## Summary + +`@netscript/service/auth` reads `Authorization: Bearer …` and `x-api-key`, and +`createServiceClient` cannot send either — the SDK has no concept of a credential at any layer. +NetScript's own CLI proves the consequence: it calls the auth service with raw `fetch` and +hand-rolled JSON shape-sniffing rather than the typed client it already depends on. This issue makes +auth the first dogfood consumer of the RFC-A contribution chain: a first-party `authClient(…)` / +`authContribution(…)` that attaches credentials, reads procedure policy metadata, surfaces a defined +credential error, and is declared from the auth plugin's manifest — so the seam is validated by the +sharpest real consumer rather than by a toy. + +## Evidence + +- Corpus: `research/repo-audit/auth.md` §0 (three independent proofs), §2 (the capability table), + §4.2 (cross-origin + `__Host-` cookie topology), §5 (no procedure policy metadata), gaps + G1/G2/G8/G9/G10; `research/repo-audit/services-sdk.md` §2.4 (S5); + `research/external/orpc.md` §1.5, §4 (G1, G3). +- Source at baseline `fac9e339042c` (re-verified for this draft): + - `packages/service/src/auth/static-credential-authenticator.ts:108-117` — server reads + `Authorization: Bearer …` and `x-api-key`. + - `packages/sdk/src/client/http-client-link.ts:82-101` — the client's entire header authorship is + `Content-Type` plus optional `traceparent`/`tracestate`. `grep -rn 'Authorization' packages/sdk/src` + → no match. + - `packages/sdk/src/ports/service-client.ts:129-155,203-222` — no credential field on the options + record or on per-call context. + - `packages/cli/src/public/features/plugins/auth/auth-session-client.ts:7-23,27-37` — the + first-party CLI calls the auth service with raw `fetch` and no credential; hardcoded URLs at + `auth-plugin-command.ts:87,98-100` (the first is already broken — #1243). + - `grep -rn 'authClient' packages plugins` → **no such symbol exists**; this issue creates it. + - `packages/plugin/src/config/domain/plugin-contributions.ts:12-39` — no client contribution group; + `:16` is the closed-literal precedent to avoid. + - `grep -rnE '\$meta<|\.meta\(' packages plugins` → no oRPC procedure metadata anywhere, so + `policy.public` has no current consumer to conflict with. + - `packages/plugin-streams-core/src/application/stream-url-resolver.ts:136-150` — `getStreamsAuth()` + is the repo's only working credential header, built outside the typed client; existence proof + that a header seam fits this architecture. +- Board: #872 (`[enterprise-auth S1]` capability discovery, `0.0.8`), #884 (org-aware policy + contracts, `0.0.12`), #885 (auth conformance kit, `0.0.12`), #1243 (broken CLI default URL, + `0.0.6`), #942 (auth v1 frontend, `0.0.11`). + +## Current surface + +Server-side auth is real and correctly ordered — `withAuthn`/`withAuthz` install before RPC routes +mount, authz fails closed, and the principal reaches the oRPC handler context +(`research/repo-audit/auth.md` §1.1). Client-side there is nothing: no credential option, no +per-call override, no cookie forwarding, no emitter for the trusted-header authenticator that +`packages/service/src/auth/trusted-header-authenticator.ts:32-54` is waiting for. The docs cannot +show an authenticated typed call because none can be written — the authz tutorial can only show +`curl -H 'authorization: Bearer read'` (`docs/site/tutorials/workspace/05-route-authz.md:248-258`). + +## Target contract + +A first-party contribution shipped from the auth plugin, consumed through the RFC-A chain: + +```ts +import { authContribution } from '@netscript/plugin-auth/sdk'; + +const users = createServiceClient({ + contract: UsersContractV1, + serviceName: 'users', + with: [authContribution({ scheme: 'bearer' })], +}); +await users.list({ limit: 20 }, { context: { auth: { token } } }); +``` + +1. **`authContribution(options)`** declares `{ auth: { token: string } }` as per-call context, so + oRPC's `ClientRest` makes it required at the call site — omitting it is a compile error, not a + 401. A server-only variant (`@netscript/plugin-auth/sdk/server`) may close over a resolver that + reads a secret; the isomorphic descriptor never does. +2. **`authClient(...)`** is the convenience wrapper the CLI and generated apps use: a + `createServiceClient` pre-composed with the auth contribution against `authContractV1`, replacing + the raw-`fetch` client at + `packages/cli/src/public/features/plugins/auth/auth-session-client.ts`. +3. **Policy-aware behaviour.** The contribution reads `NetScriptProcedureMeta.policy` (RFC-A §3.6): + it does not attach a credential to a `policy.public` procedure, and it raises a defined + `CREDENTIAL_UNAVAILABLE` rather than sending an unauthenticated request to a non-public one. +4. **Declared from the manifest.** The auth plugin declares the contribution through the + `PluginContributions` client group added by RFC-A — with a nameable, non-closed shape (contrast + `plugin-contributions.ts:16`). +5. **Scheme scope is bounded by RFC-A Q1.** Bearer and `x-api-key` land here. Cookie/session + transport does **not** — `research/repo-audit/auth.md` §4.2 proves it cannot work across the + current cross-origin discovery + `__Host-` prefix + `origin:'*'` CORS combination, and the + topology decision is the auth pack's. + +## Acceptance + +- [ ] `authContribution()` ships from the auth plugin and composes through `createServiceClient`'s + contribution chain. +- [ ] A call to a guarded service with the contribution present carries the expected credential + header, proven against a service configured with `createStaticCredentialAuthenticator`. +- [ ] `authClient(...)` replaces the raw-`fetch` client in the CLI's auth session commands. +- [ ] The contribution declares its per-call context so omitting the credential is a compile error. +- [ ] The contribution skips credential attachment for procedures marked `policy.public`. +- [ ] A defined `CREDENTIAL_UNAVAILABLE` error is raised instead of an unauthenticated request to a + non-public procedure. +- [ ] NEGATIVE: a test asserts the same client without the contribution sends no credential header. +- [ ] NEGATIVE: a test asserts an unauthenticated call to a guarded service is rejected with 401, + and an authenticated one succeeds — on `/api/rpc/*`, not only on `/api/openapi.json`. +- [ ] NEGATIVE: a type fixture asserts the credential context cannot be supplied to a client built + without the contribution. +- [ ] NEGATIVE: a test asserts the server-only contribution variant throws when constructed in a + browser-like environment. +- [ ] Docs show one authenticated typed-client call end to end, replacing the `curl`-only example. +- [ ] `gate:` `deno task check`, `deno task test`, and `deno task publish:dry-run` pass. + +## Boundaries + +- **Not here: the scaffold protecting `/api` by default.** The generated service template ships with + no `auth` option and a framework test codifies it + (`packages/service/tests/auth/define-service-auth_test.ts:11-22`) — that defect, the `plugin add + auth` starter surface, and the CORS `origin:'*'` default all belong to the **auth pack**, not to + this issue. +- **Not here: making plugin services guardable.** `createPluginService` has no `auth` option and + never calls `withAuthn`/`withAuthz` + (`packages/plugin/src/service/presentation/create-plugin-service.ts`) — auth pack. +- **Not here: the discarded `Set-Cookie` / `outputStructure: 'detailed'` defect** or the + unauthenticated `POST /api/v1/auth/signout` — auth pack. +- Do **not** duplicate **#884** (organization-aware identity and authorization policy contracts) — + no `tenantId` is added to `Principal`, `AuthSession`, or any contract here; the contribution + context is merely extensible enough that #884 can add one later. +- Do **not** duplicate **#885** (auth conformance/mocking/scaffold test kit) — the two negative + gates above are this issue's own proof, not the kit. +- Do **not** duplicate **#872** (auth capability discovery) or **#942** (auth v1 frontend). +- Do **not** duplicate **#1243** (`session list --stream-url` pins a dead localhost port) — replacing + the CLI's transport here will touch that file; fix the URL under #1243 and reference it. +- Do **not** implement the server-side authorizer that consumes `policy.scopes` — auth pack. +- Do **not** add cookie/`credentials: 'include'` support until RFC-A Q1 is answered. + +## Docs/consumer proof + +`docs/site/identity-access/how-to/add-authentication.md` and `docs/site/services-sdk/sdk.md` gain the +first authenticated typed-client example in the repo's history, and +`docs/site/tutorials/workspace/05-route-authz.md:248-258` swaps its `curl` for that example. Consumer +proof is that NetScript's own CLI stops hand-rolling `fetch` against the auth service — the deleted +`auth-session-client.ts` request/parse code is the adoption evidence, and the two negative gates +prove the credential is actually load-bearing rather than decorative. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Sourced from +`research/repo-audit/auth.md` (G1, and §9's ordering that names auth as the first dogfood) and +`research/repo-audit/services-sdk.md` (S5); the absence of an `authClient` symbol and of any oRPC +`.meta()` usage was re-verified by grep against worktree baseline `fac9e339042c`. No GitHub mutation +was performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T1-06-second-non-auth-contribution.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T1-06-second-non-auth-contribution.md new file mode 100644 index 0000000000..4287d0825e --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T1-06-second-non-auth-contribution.md @@ -0,0 +1,130 @@ +# feat(sdk): trace-context propagation is hardcoded inside the link — re-express it as the second, non-auth contribution that proves the seam is general — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T1-06 · **Proposed milestone:** `0.0.7` (post-rename-shift "Typed seams + +generation", SYNTHESIS §5.3) · **Labels:** `type:feat` `area:sdk` `area:telemetry` `priority:p1` +`status:triage` · **Depends on:** T1-01 (RFC-A ratification), T1-02 (the chain), T1-05 (first +dogfood) + +## Summary + +A contribution seam validated by one consumer encodes that consumer's shape. Auth (T1-05) is +credential-shaped; without a structurally different second consumer, RFC-A's claim that the chain is +general is unproven. Trace-context propagation is the right second consumer because it is already +hard-coded inside `createHttpClientLink` behind a boolean, with a per-call override field and +existing regression coverage — so re-expressing it as a contribution is a **migration, not an +addition**, and the negative test therefore has teeth: remove the contribution and `traceparent` must +disappear from the wire. It also removes the framework's last private fast lane through the link, so +NetScript's own default path goes through the public composition path rather than beside it. + +## Evidence + +- Corpus: `research/external/orpc.md` §1.5, §5 (the "header/trace/tenant contribution from a plugin" + row), §7 item 1; `research/repo-audit/services-sdk.md` §1.3, §2.3 (losing the client span is the + documented cost of the current escape hatch), §3.1. +- Source at baseline `fac9e339042c` (re-verified for this draft): + - `packages/sdk/src/client/http-client-link.ts:82-101` — the `headers` callback: `Content-Type`, + then `traceparent`/`tracestate` from `options.context.traceHeaders` if present, else from + `getTraceHeaders()`. Gated by the `propagateTraceContext` boolean. + - `packages/sdk/src/client/service-client.ts:41-49,55-64` — `propagateTraceContext = true` default, + threaded into the link along with `getTraceHeaders`. + - `packages/sdk/src/ports/service-client.ts:149-155` — `ServiceClientContext.traceHeaders` is the + per-call override, i.e. this concern already exercises the exact "typed per-call context" axis + the envelope claims to generalise. + - `packages/sdk/src/client/http-client-link.ts:127+` — the custom `fetch` opens the CLIENT span + with `rpc.system=orpc` / `server.address` attributes; this is the behaviour a hand-rolled client + silently loses today. + - `packages/sdk/src/presets/define-services.ts:106-116` — `propagateTraceContext` is forwarded from + L3, so the migration must preserve the L3 surface too. +- Rejected alternative, with its own citations: the AI/streams header contribution + (`getStreamsAuth()` → `{ Authorization: 'Bearer ' + STREAMS_SECRET }`, + `packages/plugin-streams-core/src/application/stream-url-resolver.ts:136-150`, consumed at + `packages/fresh/src/runtime/streams/create-stream-db.ts:111` and + `packages/fresh/src/runtime/ai/stream-proxy.ts:162`). + +## Current surface + +Trace propagation works and is not a defect — it is a *hard-code*. Two of its three axes +(header authorship, per-call typed override) are exactly the axes RFC-A's envelope exists to +generalise, and the third (server-side context resolution) is the environment boundary. Meanwhile +the only other credential-shaped header seam in the repo, `getStreamsAuth()`, was built entirely +outside the typed client with raw `fetch`, because there was nowhere else to put it. + +## Target contract + +1. **`traceContextContribution()`** ships from `@netscript/sdk` (or `@netscript/telemetry`, per the + layering decision recorded in the implementing PR) as an `SdkClientContribution` declaring + `headerKeys: ['traceparent', 'tracestate']` and per-call context + `{ trace?: { traceparent?: string; tracestate?: string } | null }`. +2. **It is composed by default.** `createServiceClient` prepends it when `propagateTraceContext` is + not `false`, so the shipped default behaviour and the L3 `defineServices` surface are unchanged. +3. **The link body no longer authors trace headers.** After this issue, + `createHttpClientLink` contains no `traceparent` literal; `ServiceClientContext.traceHeaders` + becomes a `@deprecated` alias forwarding to the contribution's context field for one minor. +4. **It carries no secret and needs no server-only variant for header authorship**, which is + precisely what makes it a generality proof rather than a second credential test. +5. **The CLIENT span stays attached to the framework default chain**, so composing extra + contributions cannot silently drop it. + +### Why this consumer, and not the AI/streams headers + +Both candidates were evaluated (RFC-A §9). `getStreamsAuth()` is attractive because it is a real, +shipped, out-of-band header that today lives outside the typed client. It was rejected as the +*second* consumer for three reasons: it is still a **credential** (a process-global shared secret), +so it re-tests auth's axis rather than a new one; it is entangled with the streams/SSE transport and +with #1329's envelope work, so a failure would not distinguish "the seam is wrong" from "streams is +wrong"; and it is purely additive, so a decorative seam could pass its test. Trace context fails +loudly if the seam is decorative, because its behaviour already exists and must survive the move. The +streams/AI header contribution remains a good **third** consumer once the streams envelope settles. + +## Acceptance + +- [ ] `traceContextContribution()` ships as an `SdkClientContribution` declaring its header keys and + per-call context. +- [ ] `createHttpClientLink` contains no trace-header authorship; the contribution is the only + producer. +- [ ] `createServiceClient` and `defineServices` behave identically to today when + `propagateTraceContext` is unset or `true`. +- [ ] The per-call trace override continues to work through the contribution's context field. +- [ ] The CLIENT span is still emitted with `rpc.system=orpc` and `server.address` when other + contributions are composed. +- [ ] NEGATIVE: with the contribution removed from the chain, a request carries no `traceparent` and + no `tracestate` — asserted on the wire, not on the options object. +- [ ] NEGATIVE: with `propagateTraceContext: false`, no trace header is sent (pins today's behaviour). +- [ ] NEGATIVE: a test asserts the auth contribution and the trace contribution compose in either + order without either header being lost. +- [ ] NEGATIVE: a type fixture asserts the composed per-call context is the intersection of both + contributions' declared contexts. +- [ ] The contribution declares no credential and requires no server-only module for header + authorship. +- [ ] `gate:` `deno task check`, `deno task test`, and `deno task publish:dry-run` pass. + +## Boundaries + +- Do **not** migrate `getStreamsAuth()` onto the chain here — it is the deliberately deferred third + consumer, and it depends on the streams envelope work. +- Do **not** duplicate **#1329** (`fix(streams)`: documented SSE consumer shape differs from the wire + protocol and does not specify the standard event/OTEL envelope, `0.0.5`, p0) — the streams + telemetry envelope is its scope. +- Do **not** change OTEL span names, attributes, or the tracer identity — telemetry semantics are out + of scope; this issue only moves *who authors the headers*. +- Do **not** add a tenancy or session context field "while we are here" — #884 owns tenancy. +- Do **not** implement the auth contribution here — T1-05. +- Do **not** open the chain here — T1-02. +- Do **not** duplicate the observability/runtime-truth work in the T4 pack (saga span call-sites, + child liveness, E2E span assertions). + +## Docs/consumer proof + +The SDK reference documents trace propagation as a *contribution* — the first place a reader sees the +same mechanism used twice, by two unrelated concerns, which is the whole adoption argument. Consumer +proof is a diff: `http-client-link.ts` loses its trace block, `@netscript/sdk`'s public surface gains +one small value, and the two-contribution composition example in the docs is the one an app author +copies. If the negative gate ("remove it and the header disappears") cannot be written, the seam is +decorative and RFC-A's ratification should be reconsidered rather than the test weakened. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Consumer choice and its +rejected alternative are recorded in `rfcs/RFC-A-sdk-client-composition.md` §9; sourced from +`research/external/orpc.md` §5/§7 and `research/repo-audit/services-sdk.md` §1.3/§2.3, with all cited +lines re-verified against worktree baseline `fac9e339042c`. No GitHub mutation was performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-01-route-slice-generator.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-01-route-slice-generator.md new file mode 100644 index 0000000000..54a5251aaf --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-01-route-slice-generator.md @@ -0,0 +1,145 @@ +# feat(cli): no verb generates a resource route slice — the typed contract, cache-first loader and withResource page must be hand-copied from init — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T2-01 · **Proposed milestone:** 0.0.7 (new "Typed seams + generation" cut per the +Stage-E rename-shift; if the train is not shifted, `Backlog / Triage`) · **Labels:** `type:feat` +`area:cli` `area:fresh` `area:fresh-ui` `priority:p1` `status:triage` · **Depends on:** T2-03 +(app-root targeting — hard blocker, this generator inherits the wrong write root without it); +T2-02 (query/invalidation module the generated island binds to); T1-01 / RFC-A **only** for the +optional contribution-inclusion clause below + +## Summary + +Every element of the canonical resource slice exists in `packages/fresh` and is demonstrated once, +as a frozen `init` template for the single scaffolded example service — but **no re-runnable verb +emits it**. A user who adds a resource must hand-transcribe the example: typed route contract, +`definePage` root, route-local `(_components)`/`(_islands)`/`(_shared)` layout, cache-first loader, +`QueryIsland`, deferred partial. The measured consequence is that agents do not transcribe it: the +Wave-6 `rickylabs/loom` run received the registry, `/design`, `AGENTS.md` and `WEB-LAYER.md` and +still hand-rolled tables, direct service calls and a 676-line island (#1333 body). Generation, not +more prose, is the remaining lever. + +## Evidence + +- `research/repo-audit/mcp-cli.md` §4.2 — element-by-element table: `defineRouteContract`, + `withResource`, cache-first loader, form slice, stream slice, `State` extension and + `router.ts`/`appRoutes` registration are all **framework ✅ / generated ❌**. +- `research/repo-audit/web-layer.md` §2.1 — verified in-worktree: `withResource`, `withForm`, + `withParams`/`withPathParams`/`withSearchParams`, `withRouteContract`, `withStreaming`, + `definePartial` have **zero occurrences** in `packages/cli/src/kernel/assets/app/`. +- `research/external/eis-chat.md` §11 (S1–S12) and §12 — the twelve conventions a + product-grade NetScript app invents in app space because the scaffold does not emit them. +- Repo, verified at `fac9e339042c`: framework surface present — + `packages/fresh/src/application/route/mod.ts:69` (`bindRoutePattern`), `:94` + (`defineRouteContract`); sidecar convention `packages/fresh/src/application/route/manifest.ts:44` + (`isRouteContractSidecar`), `:53` (`isRouteHelperDirectoryName`); + `packages/fresh/src/application/builders/define-page/builder/state.ts:55` (`withResource`). +- Generator surface absent: `packages/cli/src/kernel/application/ui/web-scaffold.ts:15-42` is the + entire page generator (see T2-04); `packages/cli/src/public/features/generate/generate-group.ts` + registers only `aspire`, `runtime-schemas`, `plugins`. +- Issues: #1333 (default-app modernization), #1335 (conformance inventory), #946 (plugin frontend + convention generation), #1208/#1210 (docs for these APIs). + +## Current surface + +`netscript ui:add page --island` writes three files, none of which loads data (T2-04). +`netscript service add --with-client` writes one app-side module (T2-02). Nothing in between +exists: there is no verb that takes a contract procedure and emits a working screen. The reference +shape lives only in `packages/cli/src/kernel/assets/app/routes/examples/service/` + +`(_shared)/service-showcase.ts.template` + `(_islands)/ServiceShowcaseLab.tsx.template` + +`routes/partials/examples/service-summary.tsx.template`, rendered once by `init` and never again. +Route ids in the one generator are hand-derived dotted strings +(`web-scaffold.ts:20`) with no link to `apps//.generated/routes.ts`, and the generated manifest +is only seeded by `init` and regenerated by the Vite plugin — there is no `generate routes` verb, so +a CI/agent flow that never runs Vite has a stale typed route surface +(`packages/cli/src/kernel/application/scaffold/writers/app-route-seeds.ts`; +`packages/fresh/src/application/route/manifest.ts:280-292`). + +## Target contract + +One composable generator, addressed at an app (T2-03), that emits a **complete, type-checking, +runnable** resource slice and can be re-run: + +1. **Core (always):** `.route.ts` sidecar declaring the route contract via + `defineRouteContract` (path/search schemas typed, not `Record`); `index.tsx` + containing a `definePage()` root **with zero presentational JSX** — `withRouteContract`/ + `withRoute`, `withResource` for the shared read, `withLayer` per region, `withLayout` slots, + `withMeta`; route-local `(_components)/-view.tsx` holding all markup; + `(_shared)/-loaders.ts` holding the contract-derived cache-first loader + (`createNetScriptQueryClient` → `fetchQuery` → `dehydrateQueryClient` → `cachedAt`); + `(_islands)/Island.tsx` binding `QueryIsland` + `useIslandQuery` to the **generated** + query factory from T2-02 (`clientKey`, not a string literal) and passing + `initialDataUpdatedAt` (T2-07); registration in `router.ts`/`appRoutes` derived from the + generated manifest, not an inline `createRouteReference`. +2. **Optional flags, composable and independently testable:** `--form` (a `withForm` region with a + Zod schema whose messages are user-facing copy, `csrf: true`, `redirectTo` through the typed + route map, `spanName`, and a component reading `firstFieldError`), `--partial` (a deferred + `withLayer` + a `routes/partials/...` partial whose name is derived, not a matching string + literal), `--stream` (a `@netscript/fresh/streams` consumer). +3. **State:** when a slice needs request-scoped state, the generator extends the app's + `utils.ts` `State` type instead of leaving `Record`. +4. **Components:** all markup composes app-owned Fresh-UI primitives from + `components/ui/mod.ts` — the generator never emits raw Tailwind for something the registry ships. +5. **Ownership comments:** each emitted route-local directory carries a one-line header stating what + belongs in it (`(_components)` = markup only, `(_islands)` = hydration only, `(_shared)` = + loaders/types, `(_lib)` = route-local pure helpers), so the vocabulary survives the first edit. +6. **Generator semantics match the rest of the CLI:** `--dry-run`, `--force`, and a + content-compare `written`/`skipped` result like `generate runtime-schemas` + (`packages/cli/src/public/features/generate/runtime-schemas/generate-runtime-schemas.ts:44-77`) + — not the current hard refuse at `web-scaffold.ts:60`. +7. **Optional contribution inclusion (RFC-A-gated):** when installed plugins contribute route/SDK + surfaces, the generator includes them. This clause and **only** this clause depends on T1-01; + the core generator ships without it and must not be sequenced behind the RFC. + +## Acceptance + +- [ ] A single documented verb emits the core slice listed in Target contract §1 for a named + contract procedure. +- [ ] The emitted slice type-checks and lints inside a freshly scaffolded project with no manual + edits. +- [ ] The emitted page file contains no presentational JSX outside `withLayout` slots. +- [ ] The emitted island binds the generated query factory's `clientKey`, not a string-literal key. +- [ ] The emitted route registers through `router.ts`/`appRoutes` derived from the generated + manifest, not an inline `createRouteReference`. +- [ ] `--form`, `--partial` and `--stream` each add exactly their own files and are independently + re-runnable. +- [ ] The verb supports `--dry-run` and `--force` and reports `written`/`skipped` on re-run. +- [ ] A second run with no input change writes zero files and exits 0. +- [ ] Negative test: generation into a project whose contract lacks the named procedure fails + non-zero with the missing procedure named, and writes no files. +- [ ] Negative test: a golden test fails if any emitted file contains `any`, a raw `fetch(` call, + a hand-written query-key array literal, or manual `JSON.parse` of a service response. +- [ ] Negative test: removing the app-root resolution (T2-03) makes the generator fail loudly + rather than write outside `apps//`. +- [ ] gate: `deno task e2e:cli run scaffold.runtime --cleanup` covers generating a slice and + type-checking the app that contains it. + +## Boundaries + +- **#1333** owns the *default app's* content and its acceptance; this issue owns the **re-runnable + verb**. Do not restate #1333's ten boxes here, and do not close #1333 with this PR. +- **#1335** owns the repo-wide generated-surface inventory; this issue adds one generator to it. +- **#946** owns plugin-contributed frontend convention generation; contribution inclusion here is + the consumer side of that seam, gated on T1-01. +- **T2-02** owns the client/query/invalidation module; this generator consumes it and must not + emit its own client wiring. +- **T2-04** owns fixing the existing `ui:add page` emission; if the owner prefers one command, T2-04 + becomes this generator's `--minimal` mode — that consolidation decision is stated, not assumed. +- **#1208/#1210** own the docs for `withResource`/`withForm`/partials; this issue ships examples, + not the manual. +- Not in scope: the missing `generate routes` verb (mcp-cli.md §4.5 C2) beyond whatever the slice + registration needs; a service-side command slice (T3). + +## Docs/consumer proof + +Adoption is proved by a generated project, not by prose: the `scaffold.runtime` E2E generates a +slice and type-checks it; the app `AGENTS.md`/`WEB-LAYER.md` reference the verb as the first step of +the one-screen path; and a consumer-shaped check re-derives the eis-chat conventions S1–S4/S7 from +generated output rather than from `docs/design/BUILD-CONTRACT.md` in a downstream repo +(`research/external/eis-chat.md` §11). + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Drafted from +`research/repo-audit/mcp-cli.md` §4.2/§5, `research/repo-audit/web-layer.md` §2.1, +`research/external/eis-chat.md` §11–12; all repo claims re-verified against worktree +`fac9e339042c`. No GitHub mutation performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-02-sdk-query-invalidation-generator.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-02-sdk-query-invalidation-generator.md new file mode 100644 index 0000000000..6c61c4cb4b --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-02-sdk-query-invalidation-generator.md @@ -0,0 +1,126 @@ +# feat(cli): app-side client/query wiring is a one-shot template with hardcoded names, colliding 'service' cache keys and a no-op invalidation — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T2-02 · **Proposed milestone:** 0.0.7 (new "Typed seams + generation" cut per the +Stage-E rename-shift; if the train is not shifted, `Backlog / Triage`) · **Labels:** `type:feat` +`area:cli` `area:sdk` `area:contracts` `priority:p1` `status:triage` · **Depends on:** T1-01 / +RFC-A **only** for including installed plugin SDK contributions; the manifest-derived generator for +first-party services is independent and must not be sequenced behind the RFC + +## Summary + +`netscript service add --with-client` renders one 27-line template that is wrong in three +structural ways at once: every service exports the same `exampleService*` symbols, every service's +cache keys live under the literal resource string `'service'`, and the generated invalidation +constant addresses a key prefix that no generated query ever produces — so the flagship showcase's +"Invalidate list cache" button and its optimistic `onSettled` reconciliation are **silent no-ops**. +There is no verb at all for the second service. Priority note: the `'service'` collision and the +dead invalidation are runtime-correctness defects shipped in generated user code; they are kept +inside this generator issue because fixing them without changing the generator would only re-emit +them, but the owner may split them out as a separate p0 fix. + +## Evidence + +- `research/repo-audit/services-sdk.md` §4.1 (a)–(d) and gap register S6, S7, S15, S16 — all rated + High/Medium with citations; §7 states **no open issue covers S6/S7**. +- `research/repo-audit/mcp-cli.md` §4.1 — "no CLI verb emits `apps//lib/.ts` for a + second service"; the generated `bridgeInvalidation` pair is hand-written, so a renamed procedure + fails at runtime, not at type-check. +- Repo, verified at `fac9e339042c`: + - `packages/cli/src/kernel/assets/app/lib/example-service.ts.template:8-27` — exports + `exampleServiceName`, `exampleServiceRouterName`, `exampleServiceContract`, + `exampleServiceListInvalidation`, `exampleServiceClient`, `exampleServiceQueries`; the factory + group is literally `createQueryFactories({ service: { … } }).service`. + - `packages/sdk/src/query/query-factory.ts:41-46` (`createQueryFactory(resource, …)`), `:54-58` + (`invalidate` uses `[resource]`), `:147` (`queryKey: [resource, action, { input }]`), `:174-178` + (`clientKey`), `:218` (`createQueryFactories` passes the **object key** as `resource`). + - `packages/sdk/src/query-client/key-bridge.ts:19-23,32-37` — `bridgeInvalidation(resource, + action)` returns `{ queryKey: [resource, action] }`, i.e. `['users','list']` for the default + service name, while the real keys start with `'service'`. + - Consumed at + `packages/cli/src/kernel/assets/app/routes/examples/(_islands)/ServiceShowcaseLab.memory.tsx.template:85` + (`onSettled` after an optimistic mutation) and `:115` (an "Invalidate list cache" button). + - `packages/cli/src/kernel/adapters/service/client-scaffolder.ts:9-21,45-49` — the single + template render; `packages/cli/src/public/features/services/add/add-service.ts:69-79`; + flag declared at `add-service-command.ts:37`. +- Verified negative: grep for `bridgeInvalidation|invalidateQueries` under `packages/sdk/tests` and + `packages/cli/e2e/src` returns nothing (`services-sdk.md` §4.1 (c)). + +## Current surface + +One template, one service, one shot. Two services ⇒ two files exporting identical symbol names ⇒ +any module importing both must alias every symbol; both services' `list` actions share the cache +key `['service','list',…]` in **both** tiers (server keys via +`packages/sdk/src/ports/query-key.ts`, client keys via `query-factory.ts:143`), so +`factory.invalidate()` invalidates every service at once and a cache read can be served another +service's payload. `defineServices()` — the advertised L3 preset — appears on no scaffolded path +(`services-sdk.md` S16). + +## Target contract + +A `generate`-family verb (name to lock in implementation; `generate sdk` / `contract sync` are the +candidates from `mcp-cli.md` §5) that regenerates the app-side data layer from the contract +manifest: + +1. **Names derive from the service/contract**, never from `example*`: `Client`, + `Queries`, `Contract`, one module per service at + `apps//lib/.ts`. Two services never collide. +2. **Resource keys derive from the service/router name**, not the literal `'service'`. Cross-service + collision becomes impossible by construction, and `factory.invalidate()` is scoped to one + service. +3. **Invalidation is generated, not hand-written**: a per-procedure invalidation map derived from + the same contract that produced the keys, so a renamed procedure is a **type error**, not a + runtime no-op. The showcase's two call sites consume the generated map. +4. **Idempotent and drift-reporting**, matching `generate runtime-schemas`: content-compare with + `written`/`skipped`, `--dry-run`, `--force`; a second run is byte-identical. +5. **Integrates with the existing verbs**: `service add --with-client` and `service generate` call + the same generator rather than rendering a template, so there is one code path. +6. **Installed contributions (RFC-A-gated, T1-01):** plugin-contributed SDK/query surfaces are + included in the generated module when the contribution contract exists. Without T1-01 the + generator emits first-party services only — that is a shipping configuration, not a blocked one. +7. **The L1/L2 vs L3 (`defineServices`) choice is decided and documented once** in the generator, so + the generated path and the docs teach the same dialect (S16/S17). + +## Acceptance + +- [ ] A documented verb regenerates `apps//lib/.ts` for every service in the manifest. +- [ ] Generated export names are derived from the service name; a two-service project imports both + modules with no aliasing. +- [ ] Generated query-factory resource keys are per-service; a two-service fixture proves the + `list` keys differ in both cache tiers. +- [ ] The generated invalidation map is contract-derived and a renamed procedure fails `deno check`. +- [ ] The showcase island's invalidation call sites use the generated map and actually invalidate. +- [ ] Re-running with no input change writes zero files; output is byte-identical. +- [ ] `--dry-run` reports the plan and writes nothing; `--force` rewrites unchanged files. +- [ ] Negative test: a fixture asserting `bridgeInvalidation`-style keys that do not match the + factory keys fails the suite (the S6 regression is locked out). +- [ ] Negative test: a two-service fixture whose factories share a resource key fails the suite. +- [ ] Negative test: generating for a service with no contract exits non-zero and writes no files. +- [ ] gate: `deno task e2e:cli run scaffold.runtime --cleanup` adds a second service and proves both + generated modules type-check and their caches do not collide. + +## Boundaries + +- **T1-01 / RFC-A** owns the `SdkClientContribution` contract and the reopened oRPC seams + (headers/interceptors/plugins/link/context/typed errors). Do not design that contract here. +- **#1333** owns making the default app idiomatic; this issue owns the generator it calls. +- **#1335** owns the conformance inventory. +- **T2-01** owns the route slice that consumes these factories; it must not emit client wiring. +- **#1245** owns island query *type* gaps; the `clientKey → queryKey` convergence landed in #1265. +- Not in scope: `AbortSignal` forwarding (S8), nested-router factories (S9), `port`/`timeout` + dead options (S11) — those are SDK-surface items for T1, referenced here so they are not + re-filed. + +## Docs/consumer proof + +A generated two-service project is the proof: both modules import cleanly, `deno check` passes, the +invalidation button changes observable cache state in the runtime E2E, and the services-SDK docs +page teaches exactly the dialect the generator emits (one dialect, per T5). The eis-chat +cache-key-collision note (`research/external/eis-chat.md` §3, `lib/channel-service.ts:12-24`, +"factory-group name is the cache-key prefix and must be unique per router") becomes a generated +invariant instead of app-space folklore. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Drafted from +`research/repo-audit/services-sdk.md` §4.1 + S6/S7/S15/S16 and `research/repo-audit/mcp-cli.md` +§4.1/§5; all repo claims re-verified against worktree `fac9e339042c`. No GitHub mutation performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-03-ui-app-root-targeting.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-03-ui-app-root-targeting.md new file mode 100644 index 0000000000..eddaded10e --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-03-ui-app-root-targeting.md @@ -0,0 +1,107 @@ +# fix(cli): every ui:* command writes to the workspace root instead of apps/, and the E2E gate asserts the wrong root — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T2-03 · **Proposed milestone:** 0.0.7 (new "Typed seams + generation" cut per the +Stage-E rename-shift; if the train is not shifted, `0.0.6`) · **Labels:** `type:fix` `area:cli` +`area:fresh-ui` `area:docs` `priority:p1` `status:triage` · **Depends on:** none — this is the +blocker T2-01 and T2-04 sit on top of + +## Summary + +`init` writes the Fresh app to `apps//` and installs the UI registry **there**, but every +`ui:*` command resolves its write root through `resolveProjectRoot`, which walks up to the +**workspace** root. So `netscript ui:add data-table` in a scaffolded project copies components into +`/components/ui/` — outside any Fresh app, invisible to the app's barrel and to the +Vite route generator. There is no `--app` flag to correct it, yet the docs already document one. +The repo's own E2E gate passes the workspace root and then asserts the copied paths relative to it, +so CI is green **because it encodes the defect**. + +## Evidence + +- `research/repo-audit/mcp-cli.md` §4.4 (titled "App-targeting seam — BROKEN TODAY") and §3 + preamble; `research/repo-audit/web-layer.md` §8 secondary notes. +- Repo, verified at `fac9e339042c`: + - `packages/cli/src/public/features/root/public-command-dependencies.ts:195-197` — + `resolveProjectRoot` = `findDeployProjectRoot(host.cwd())`. + - `packages/cli/src/kernel/adapters/config/deploy-config.ts:59-84` — `findProjectRoot` returns the + first ancestor containing `netscript.config.ts`, `dotnet/AppHost/appsettings.json`, **or a + `deno.json` with a `workspace` array**, i.e. always the workspace root. + - `packages/cli/src/public/features/ui/add/add-ui-command.ts:54-57` — every `ui:add` path + (`page`, `island`, registry item) uses `requireProjectRoot(dependencies.resolveProjectRoot, …)`; + `:38-52` is the full option list and contains **no `--app`**. + - `packages/cli/src/kernel/application/scaffold/writers/write-app-files.ts:182-185` — `init` + installs the registry with `projectRoot: appDir`, i.e. `apps//`. + - `packages/cli/src/kernel/application/ui/web-scaffold.ts:15-42` — `ui:add page` writes + `/routes//…`, so a scaffolded project gets `/routes/…`. + - Gate encoding the wrong root: + `packages/cli/e2e/src/application/gates/scaffold/ui-ai-gates.ts:23-24` passes + `--project-root context.project.projectRoot`, and `:95-102` asserts + `islands/ui/McpUiWidget.tsx`, `lib/ai/render-ui.tsx`, `assets/styles.css` relative to that same + root. + - Docs that document a flag which does not exist / a path the command does not write: + `docs/site/web-layer/how-to/build-a-desktop-frontend.md:31` + (`netscript ui:add desktop --app dashboard`); + `docs/site/web-layer/how-to/customize-fresh-ui.md:26` and `:256` ("component files go to + `apps/dashboard/components/ui/`"). + +## Current surface + +Two resolution rules for one artifact: `init` installs into the app, `ui:*` installs into the +workspace. Nothing warns. A user who follows the published how-to gets files in a directory the +Fresh app never reads, and re-running `ui:update` compares drift against that same wrong tree. +`ui:add page` compounds it: the emitted route lands outside the app so the Vite route generator +never sees it and `appRoutes` never gains the entry. + +## Target contract + +1. **`ui:*` commands resolve an app root, not the workspace root.** Resolution order: explicit + `--app ` (or `--project-root` pointing at an app) → single Fresh app workspace member → + error listing the candidates when more than one exists. The error names the flag to use. +2. **`--app ` exists on every `ui:*` command** (`ui:init`, `ui:add`, `ui:list`, `ui:update`, + `ui:remove`) with one shared implementation, and is documented in `--help`. +3. **Running inside `apps//` works with no flag.** +4. **The E2E gate is corrected, not preserved**: `ui-ai-gates.ts` targets the app and asserts the + app-relative paths. The corrected assertions must fail against today's behavior. +5. **The docs stop describing a flag that does not exist**: the two how-to pages are updated to the + shipped surface in the same change, and their commands are copy-runnable. +6. **The public input type describes the public CLI**: `UiAddCommandInput` + (`packages/cli/src/public/features/ui/add/add-ui-input.ts`) gains the fields the action already + accepts (`route`, `island`, `query`, and the new `app`). + +## Acceptance + +- [ ] `ui:*` commands write into the resolved Fresh app, never the workspace root. +- [ ] `--app ` is accepted by every `ui:*` command and documented in `--help`. +- [ ] Running a `ui:*` command from inside `apps//` needs no flag. +- [ ] A multi-app workspace with no `--app` fails non-zero and lists the candidate apps. +- [ ] `UiAddCommandInput` declares every option the command accepts. +- [ ] The two how-to pages match the shipped flags and paths. +- [ ] Negative test: a regression test asserts that no `ui:*` command writes to the workspace root + when an app member exists — this test must fail on the pre-fix build. +- [ ] `ui-ai-gates.ts` asserts app-relative paths and its assertions fail against the pre-fix + behavior. +- [ ] gate: `deno task e2e:cli run scaffold.runtime --cleanup` proves a `ui:add` item lands in + `apps//components/ui/` and is reachable from the app's barrel. + +## Boundaries + +- **#1333** owns the default app's content and dynamic app naming; this issue owns *where the CLI + writes*. Do not fold the app-name derivation into this fix. +- **#1335** owns the conformance inventory that will record the corrected paths. +- **T2-01/T2-04** consume this seam; they must not each invent their own app resolution. +- **#1328 (CLOSED)** owned generated quality-gate coverage — do not reopen it. +- Not in scope: `ui:add page`'s emitted *content* (T2-04), the missing `--force`/`--dry-run` on + `ui:add page` (T2-01/T2-04), or `netscript-dev`'s stale `version('1.0.0')` + (`mcp-cli.md` §4.5 C3). + +## Docs/consumer proof + +A scaffolded project is the proof: run every documented `ui:*` command from the published how-tos +verbatim and show the files landing where the how-to says they land, with the app barrel and the +generated route manifest picking them up. The corrected `ui-ai-gates.ts` is the standing regression +proof that the wrong root cannot come back green. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Drafted from +`research/repo-audit/mcp-cli.md` §4.4 and `research/repo-audit/web-layer.md` §8; every path and line +re-verified against worktree `fac9e339042c`. No GitHub mutation performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-04-ui-add-page-data-screen-triad.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-04-ui-add-page-data-screen-triad.md new file mode 100644 index 0000000000..943a84e356 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-04-ui-add-page-data-screen-triad.md @@ -0,0 +1,113 @@ +# fix(cli): ui:add page --island emits a useSignal counter and an empty queryLoaders object instead of the advertised data-screen triad — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T2-04 · **Proposed milestone:** 0.0.7 (new "Typed seams + generation" cut per the +Stage-E rename-shift; if the train is not shifted, `0.0.6`) · **Labels:** `type:fix` `area:cli` +`area:fresh` `priority:p1` `status:triage` · **Depends on:** T2-03 (app-root targeting), T2-01 +(the slice contract this command is the minimal mode of), T2-02 (the query factory the island binds) + +## Summary + +`ui:add` describes itself as scaffolding "the Fresh page + island + query-loader triad for a data +screen", and the generated app `AGENTS.md` tells every coding agent to reach for it first. What it +emits is a page with a `() => ({})` layer loader, an island that is a `useSignal(0)` counter button, +and a file containing `export const queryLoaders = {} as const;`. The one command the framework +points agents at produces a counter where a data screen was promised — which is the measurable +mechanism behind agents hand-rolling their own screens. + +## Evidence + +- `research/repo-audit/web-layer.md` §8 (full emission breakdown) and gap register item 9; + `research/repo-audit/mcp-cli.md` §3.5. +- Repo, verified at `fac9e339042c`, `packages/cli/src/kernel/application/ui/web-scaffold.ts`: + - `:15-42` — `scaffoldUiPage` writes exactly three files; the page's only layer is + `.withLayer('', () => , () => ({}))` with no loader, no `withResource`, no query. + - `:34` + `:66-68` — the island content is `signalIslandTemplate`, i.e. + `const count = useSignal(0); return ;`. + - `:37` — the third file is literally + `export const queryLoaders = {} as const;`. + - `:51` — `ui:add island --query` emits `
Name
`: a provider + around a static div, no `useQuery`, no key, no factory. + - `:60` — any pre-existing target file is a hard refuse; there is no `--force` and no `--dry-run`, + unlike every other generator (`install-plugin-command.ts` has both). + - `:20` — the route id is a hand-derived dotted string with no link to the generated manifest, and + the page declares `createRouteReference` inline instead of registering in `router.ts`. +- Advertised behavior it contradicts: + `packages/cli/src/public/features/ui/add/add-ui-command.ts:26-28` (the description quoted above) + and `packages/cli/src/kernel/templates/app/agent-conventions.ts:137-139` (the three commands + agents are told to use; line 137 promises "a typed Fresh route, a colocated hydrating island, and + its query-loader seam"). +- Behavioral consequence measured in the field: #1333 body (Wave-6 `rickylabs/loom` built routes + with hand-rolled tables and a 676-line island); #1090 holds the observational box "an agent + building a data screen runs `ui:add` or records why not". + +## Current surface + +Three files, none of which loads data. Two island conventions from one command: `ui:add page +--island` writes to `routes//(_islands)/`, `ui:add island` writes to top-level `islands/`. +`UiAddCommandInput` (`add-ui-input.ts`) omits `route`, `island` and `query`, so the exported public +type under-describes the public CLI. + +## Target contract + +`ui:add page --island` emits a **working data screen** — the minimal composable core of the +T2-01 slice, not a second generator: + +1. The page uses the typed route surface (registered through `router.ts`/`appRoutes`, not an inline + `createRouteReference`) and a real layer loader. +2. The `(_shared)` file contains a cache-first loader bound to a real query factory and returning + `cachedAt`; it is not an empty object. +3. The island is a `QueryIsland` + `useIslandQuery` bound to the factory's `clientKey`, seeded with + the loader's data and `initialDataUpdatedAt` (T2-07) — not a counter. +4. `ui:add island --query` emits an island that actually issues a query. +5. When the target app has no service/contract to bind, the command **says so and names the verb to + run first** rather than emitting a placeholder that type-checks and does nothing. +6. Island placement is one documented convention, with the divergence between the two commands + resolved explicitly. +7. `--force` and `--dry-run` exist, matching the rest of the CLI. +8. `UiAddCommandInput` declares every accepted option. + +## Acceptance + +- [ ] `ui:add page --island` emits a page, loader and island that fetch and render real + contract-derived data in a scaffolded project. +- [ ] The emitted island contains no `useSignal` counter and the emitted `(_shared)` file contains + no empty `queryLoaders` object. +- [ ] `ui:add island --query` emits an island that issues a query with a factory-derived key. +- [ ] The emitted page registers its route through `router.ts`/`appRoutes`. +- [ ] Island placement follows one documented convention across both commands. +- [ ] `--force` and `--dry-run` are supported and documented. +- [ ] `UiAddCommandInput` declares `route`, `island`, `query` (and `app`, per T2-03). +- [ ] Running the command in an app with no bindable contract exits non-zero with the prerequisite + verb named, and writes no files. +- [ ] Negative test: a golden test fails if the emitted island imports `@preact/signals`' `useSignal` + as its only behavior, or if the emitted loader module exports an empty object. +- [ ] Negative test: the command's own `--help` text is asserted against the emitted file set, so + the description and the emission cannot diverge again. +- [ ] gate: `deno task e2e:cli run scaffold.runtime --cleanup` scaffolds a page via this command and + type-checks the app containing it. + +## Boundaries + +- **T2-01** owns the full slice generator (`--form`/`--partial`/`--stream`, route-local + `(_components)`, ownership comments). If the owner consolidates, this issue is that generator's + minimal mode — state the decision, do not ship two generators. +- **T2-03** owns where the files land; do not re-fix app resolution here. +- **T2-02** owns the query factory this island binds; do not emit ad-hoc client wiring. +- **#1333** owns the default app's own routes; this issue changes the *generator*, not the shipped + example routes. +- **#1090** owns the observational measurement of whether agents adopt the command — do not add an + agent-run box to this issue's acceptance. +- **#1102/#1197** own MCP/agent discovery; this is not a discovery fix. + +## Docs/consumer proof + +The proof is a generated screen, not a paragraph: `scaffold.runtime` runs the command and +type-checks the result, the app `AGENTS.md` example matches the real emission, and the Web Layer +how-to that names the triad shows the actual generated files. #1090 separately observes whether an +unfamiliar agent reaches for the command — that measurement stays on #1090. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Drafted from +`research/repo-audit/web-layer.md` §8 and `research/repo-audit/mcp-cli.md` §3.5; every line +re-verified against worktree `fac9e339042c`. No GitHub mutation performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-05-design-registry-sync-gate.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-05-design-registry-sync-gate.md new file mode 100644 index 0000000000..e04f690021 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-05-design-registry-sync-gate.md @@ -0,0 +1,98 @@ +# fix(scaffold): the generated /design/components gallery lists 50 of 66 registry items — the whole AI collection is invisible and no gate compares them — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T2-05 · **Proposed milestone:** 0.0.6 (small, self-contained, and a stated +prerequisite for #1333's `/design` acceptance; drafted inside the new-0.0.7 pack because it belongs +to the generation pillar, but it does **not** depend on the generator train and should not wait for +it) · **Labels:** `type:fix` `area:cli` `area:fresh-ui` `priority:p1` `status:triage` · +**Depends on:** none + +## Summary + +The scaffolded `/design/components` gallery reads a hand-copied snapshot of the `@netscript/fresh-ui` +registry that declares 50 items while the live manifest has 66. The 16 missing entries are the +entire `ai` collection plus `donut` and `dropzone`. `netscript ui:add` / `ui:list` resolve against +the **live** manifest, so the CLI can install components the generated "living design reference" +says do not exist — and the generated `AGENTS.md` points coding agents at exactly that gallery. No +task, test or CI step compares the two, so the snapshot re-rots on the next registry addition. + +## Evidence + +- `research/repo-audit/scaffold-doctrine.md` §2.1 (D1) and `research/repo-audit/web-layer.md` §7.1 — + both computed the same 16-item diff. +- Repo, verified at `fac9e339042c`: + - `packages/cli/src/kernel/assets/app/routes/(design)/design/(_shared)/registry.ts.template:1-4` + self-describes as a snapshot "regenerate when the registry changes"; `:28` declares + `total: 50`. + - Live manifest: `deno eval` on `packages/fresh-ui/registry.manifest.ts` → + `items 66 collections 8 0.1.0`; collections are + `foundation, ai, forms-core, surface-core, feedback-core, layout-foundations, dashboard-blocks, + desktop`. + - Missing from the snapshot: `avatar`, `citation-chip`, `code-block`, `model-selector`, + `tool-call-card`, `chart-block`, `donut`, `prompt-input`, `message`, `markdown`, + `command-palette`, `search`, `dropzone`, `chat-render`, `mcp-ui-widget`, `render-ui`. + - The CLI reads the live manifest: + `packages/cli/src/kernel/application/ui/registry.ts` imports `freshUiRegistryManifest` from + `@netscript/fresh-ui` and selects items from it. + - The only existing drift test — `packages/fresh-ui/tests/registry-doc-drift.test.ts:4-18` — + compares `registry.ts` JSDoc **collection names** against the manifest and never looks at the + CLI snapshot. + - Agent-facing pointer: `packages/cli/src/kernel/templates/app/agent-conventions.ts:37` routes + agents to `/design/composition`. + +## Current surface + +Two sources of truth for one catalog: `packages/fresh-ui/registry.manifest.ts` (authoritative, read +by the CLI) and the CLI's hand-copied `registry.ts.template` (read by the generated gallery). They +disagree by 16 items and by construction will disagree again after the next registry change. + +## Target contract + +1. The generated `/design/components` catalog is **derived from the manifest**, not hand-copied — + either generated at scaffold time from `freshUiRegistryManifest` or checked against it by a gate + that fails on any difference in item names, kinds, collections, `layer`, or the declared `total`. +2. The gate runs in CI on the same lane as the other scaffold checks and names the drifting items in + its failure output. +3. The gate is symmetric: adding a registry item without updating the generated catalog fails, and + listing a catalog item that the manifest does not have fails. +4. `registryMeta.version`/`total` cannot silently disagree with the manifest. + +## Acceptance + +- [ ] The generated `/design/components` catalog contains all 66 current registry items, including + the full `ai` collection. +- [ ] The catalog's item names, kinds, layers and collection membership match the manifest exactly. +- [ ] `registryMeta.total` and `registryMeta.version` are derived from the manifest. +- [ ] A drift gate compares the generated catalog against `freshUiRegistryManifest` and names the + differing items on failure. +- [ ] Negative test: adding a fixture item to the manifest without regenerating the catalog fails + the gate. +- [ ] Negative test: removing an item from the manifest while the catalog still lists it fails the + gate. +- [ ] gate: the drift gate runs in CI on every change to `packages/fresh-ui/registry.manifest.ts` or + the CLI design assets. + +## Boundaries + +- **#1333** owns the acceptance item "`/design` and `/design/composition` are named and linked as + the living design/component reference" — this issue makes that reference *true* and must not + restate #1333's other boxes or close it. +- **#1335** owns the repo-wide conformance inventory; this is one row of it, filed separately + because it is a concrete mechanical defect with a gate. +- **#946 / #922** own plugin-contributed UI; contributed items are out of scope until that seam + exists. +- Not in scope: the `packages/fresh-ui` exclusion from root `check`/`lint` + (`web-layer.md` §11.3) — that belongs to the T6 quality pack; and the token pipeline + (`tokens:check`), which already has a gate. + +## Docs/consumer proof + +A scaffolded project whose `/design/components` page lists every installable item is the proof: a +reader can pick any name from the gallery, run `netscript ui:add `, and get it. The negative +gate is the durable proof that the gallery cannot drift back. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Drafted from +`research/repo-audit/scaffold-doctrine.md` §2.1/D1 and `research/repo-audit/web-layer.md` §7.1; +the 66-item count and the 16-item diff re-verified against worktree `fac9e339042c`. No GitHub +mutation performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-06-crud-example-route-alias.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-06-crud-example-route-alias.md new file mode 100644 index 0000000000..f342846128 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-06-crud-example-route-alias.md @@ -0,0 +1,88 @@ +# fix(scaffold): appRoutes.crudExample aliases serviceExample so /examples/crud is unreachable, and a template test asserts the alias — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T2-06 · **Proposed milestone:** 0.0.6 (a one-line generated-output defect plus a test +correction; drafted inside the new-0.0.7 pack for topical grouping but independent of the generator +train) · **Labels:** `type:fix` `area:cli` `area:fresh` `priority:p1` `status:triage` · +**Depends on:** none + +## Summary + +The generated app's route alias map points `crudExample` at the service-example route. Both example +cards on the home page and the examples index therefore navigate to `/examples/`, and +the CRUD example route that the scaffold also emits is unreachable from the generated UI. The defect +is locked in by a template test that asserts the aliasing line verbatim, so any correct fix fails +the suite first — the test must change with the template. + +## Evidence + +Repo, verified at `fac9e339042c`: + +- `packages/cli/src/kernel/assets/app/router.ts.template:33-34`: + ```ts + serviceExample: routes.examples.serviceExample, + crudExample: routes.examples.serviceExample, // same target + ``` +- `packages/cli/src/kernel/assets/app/routes/examples/crud.tsx.template:6` binds the page to + `routes.examples.crud.$route` — a real, distinct route. +- Link sites that consume the wrong alias: + `packages/cli/src/kernel/assets/app/routes/examples/index.tsx.template:15` and + `packages/cli/src/kernel/assets/app/routes/index.tsx.template:24`, both + `href: appRoutes.crudExample.href()`. +- The bug is asserted as expected output: + `packages/cli/src/kernel/templates/app/route-templates_test.ts:76` — + `assertStringIncludes(output, 'crudExample: routes.examples.serviceExample,');` +- Corpus: `research/repo-audit/web-layer.md` §10 and gap-register item 3; `§13` records that no + board issue covers it. + +## Current surface + +`appRoutes` exposes two names for one route. The generated app ships a "CRUD" card that does not +lead to the CRUD page, and the CRUD page has no inbound link. A regression test enforces the alias, +which is why the defect survived template edits. + +## Target contract + +1. `appRoutes.crudExample` resolves to the CRUD route (`routes.examples.crud`), so every generated + link reaches the page it names. +2. `route-templates_test.ts` asserts the corrected alias, and the assertion is written so that a + future alias collapse fails rather than passes. +3. A structural check makes the class of defect visible, not just this instance: no two distinct + `appRoutes` keys may resolve to the same route target unless the duplication is explicit and + commented. +4. Adjacent naming/IA note recorded, not fixed here: `routes/examples/crud.tsx.template` renders + three hard-coded records with no create/update/delete, while the real CRUD flow lives in + `ServiceShowcaseLab` on the service-example route. Renaming or reworking that example is a + separate product decision (see Boundaries). + +## Acceptance + +- [ ] `appRoutes.crudExample` targets the CRUD route in the generated `router.ts`. +- [ ] Both generated link sites navigate to `/examples/crud` in a scaffolded project. +- [ ] `route-templates_test.ts` asserts the corrected mapping and no longer asserts the alias. +- [ ] Negative test: a check fails when two `appRoutes` keys resolve to the same route target + without an explicit annotation. +- [ ] gate: `deno task e2e:cli run scaffold.runtime --cleanup` type-checks the generated app with + the corrected router. + +## Boundaries + +- **#1333** owns whether the example routes are redesigned at all; this issue only fixes the alias + so the *existing* example is reachable. Do not fold the "is `crud.tsx` really CRUD?" product + question into this fix — record it as an amendment to #1333 instead. +- **#1335** owns the conformance inventory that would have caught this class. +- **T2-01** owns route registration for *newly generated* slices; this is the shipped template. +- Not in scope: the layer→partial stringly-typed binding (`web-layer.md` §2.3) and the inline + `createRouteReference` in `ui:add page` (T2-04). + +## Docs/consumer proof + +A scaffolded project where clicking the "CRUD" card lands on `/examples/crud` is the proof; the +corrected template test plus the duplicate-target check keep it true. No docs page currently claims +otherwise, so no docs change is required — if one is found during implementation, it changes in the +same PR. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Drafted from +`research/repo-audit/web-layer.md` §10; every line re-verified against worktree `fac9e339042c`. +No GitHub mutation performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-07-cachedat-initialdataupdatedat-wiring.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-07-cachedat-initialdataupdatedat-wiring.md new file mode 100644 index 0000000000..2d751a7984 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.7-typed-seams-generation/T2-07-cachedat-initialdataupdatedat-wiring.md @@ -0,0 +1,95 @@ +# fix(scaffold): the canonical island never passes initialDataUpdatedAt, so the loader's cachedAt is computed, displayed and discarded — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T2-07 · **Proposed milestone:** 0.0.6 (one template line plus a regression test and a +migration note; drafted inside the new-0.0.7 pack because T2-01/T2-04 must emit the same wiring, but +it does not depend on them) · **Labels:** `type:fix` `area:cli` `area:fresh` `area:docs` +`priority:p2` `status:triage` · **Depends on:** none (T2-01 and T2-04 must inherit the fixed shape) + +## Summary + +PR #1265 added `initialDataUpdatedAt` to `IslandQueryOptions` specifically so a server-rendered +snapshot keeps its real cache age across hydration. The generated app's canonical island computes +`cachedAt` in its loader, passes it through as a prop — and then uses it only as a display label, +never as `initialDataUpdatedAt`. The differentiator seam ships unexercised in the one example the +scaffold designates as canonical, so first paint tells TanStack the snapshot is fresh as of +hydration. The most polished downstream consumer made the same mistake independently, which is +evidence the seam is undiscoverable rather than unwanted. + +## Evidence + +- `research/repo-audit/web-layer.md` §4.2 and gap-register item 11; + `research/external/eis-chat.md` §6 and §11 S10. +- Repo, verified at `fac9e339042c`: + - `packages/fresh/src/application/query/query-types.ts:135-136` — the option exists: + "Timestamp when the server loaded `initialData`, used to preserve its cache age." + - `packages/cli/src/kernel/assets/app/routes/examples/(_shared)/service-showcase.ts.template:67` + computes `const cachedAt = Date.now();` and `:77` returns it. + - `packages/cli/src/kernel/assets/app/routes/examples/(_islands)/ServiceShowcaseLab.tsx.template:43-49` + calls `useQuery({ queryKey, queryFn, initialData: props.initialList, + staleTime: 15_000 })` — **no `initialDataUpdatedAt`**; `:105` uses `props.cachedAt` only to + render a "Cached at" stat. +- Downstream consumer evidence (`research/external/eis-chat.md` §6): `cachedAt` is threaded through + three files (`routes/skills/index.tsx:49` → `(_components)/skills-view.tsx:40-48` → + `islands/SkillsPanel.tsx:34`) and then discarded at `SkillsPanel.tsx:74` + (`cachedAt: _cachedAt`). + +## Current surface + +The seam exists, is documented in the type, and is used nowhere: not in the scaffold, not in the +reference consumer. Nothing fails when it is omitted, so the omission is invisible. + +## Target contract + +1. The canonical generated island passes `initialDataUpdatedAt: props.cachedAt` alongside + `initialData`, so the hydrated cache entry carries its true server age and `staleTime` behaves as + documented. +2. Both showcase variants (`ServiceShowcaseLab.tsx.template` and the memory variant) use the same + shape, so the two generated paths teach one dialect. +3. A regression test asserts that any generated island seeded with `initialData` from a loader that + returns `cachedAt` also passes `initialDataUpdatedAt` — the assertion is on the generated output, + not on prose. +4. T2-01/T2-04 emit the same wiring by construction. +5. **One migration note** for consumers upgrading from beta-era pins tells them to (a) consume a + threaded `cachedAt` into `initialDataUpdatedAt` instead of dropping it, and (b) delete the + `clientKey → queryKey` `as unknown as` casts that #1265 made unnecessary. The cast half is + #1245's remnant scope; this issue contributes the `initialDataUpdatedAt` half and links to it + rather than duplicating it. + +## Acceptance + +- [ ] The generated canonical island passes `initialDataUpdatedAt` from the loader's `cachedAt`. +- [ ] Both showcase island variants use the same option shape. +- [ ] A regression test asserts the generated island passes `initialDataUpdatedAt` whenever it + passes loader-seeded `initialData`. +- [ ] Negative test: removing `initialDataUpdatedAt` from the template fails that test. +- [ ] A migration note documents consuming `cachedAt` into `initialDataUpdatedAt` for apps upgrading + from beta-era pins. +- [ ] gate: `deno task e2e:cli run scaffold.runtime --cleanup` type-checks the generated app with + the corrected island. + +## Boundaries + +- **#1245** owns the remaining island-query type work — the `getIslandQueryClient()` `@throws` + JSDoc that documents a guard the body does not implement + (`packages/fresh/src/application/query/query-client.ts:26-27` vs its body), the `clientKey` + asymmetry, and the consumer note about deleting the six copied casts. #1245 is ~75% landed via + #1265 and is proposed for **rescope, not re-implementation**; do not re-file its scope here and do + not close it with this PR. +- **#1333** owns the default app's broader modernization; this is one line inside it that can land + independently. +- **T2-01/T2-04** own the generators; this issue fixes the shipped example they should mirror. +- Not in scope: the duplicate `useLiveQuery` exports and `IslandLiveQueryResult.details` + (`web-layer.md` §4.4), or any change to `packages/fresh` source — this is scaffold + docs only. + +## Docs/consumer proof + +The proof is behavioral: in a scaffolded project, a page rendered from a server snapshot older than +`staleTime` refetches immediately after hydration, and one rendered from a fresh snapshot does not. +The Web Layer query/cache-first documentation shows the same three lines the scaffold emits, and the +migration note gives a beta-era consumer an exact diff to apply. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Drafted from +`research/repo-audit/web-layer.md` §4.2 and `research/external/eis-chat.md` §6/S10; every line +re-verified against worktree `fac9e339042c`. No GitHub mutation performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T3-02-service-layout-child.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T3-02-service-layout-child.md new file mode 100644 index 0000000000..69c0e71383 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T3-02-service-layout-child.md @@ -0,0 +1,126 @@ +# fix(scaffold/service): generated services have no internal layering — the v1 router template teaches ORM-in-handler and `deno task test` fails on an empty tree — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T3-02 · **Proposed milestone:** 0.0.8 (post-shift "Runtime truth + service slice") · +**Labels:** `type:fix` `area:cli` `area:service` `area:docs` `priority:p1` `status:triage` · +**Depends on:** none + +Part of #1335. + +## Summary + +A scaffolded service has exactly one internal subdivision — `src/routers/` — and no guidance +anywhere about where business logic, repositories, external clients, or domain types belong. The +generated `v1` router therefore embeds Prisma query construction, a sort-field allow-list, and +pagination arithmetic directly in the oRPC handler, and `service add-handler` appends every new +procedure into that same file. That is the handler-as-god-object shape the Architecture Doctrine +forbids for framework code, generated as the example every consumer copies. The same generated +`deno.json` declares `test: deno test -A src/` against a tree that contains no test module, so +`deno task test` exits 1 on a freshly scaffolded service. + +## Evidence + +- `packages/cli/src/kernel/adapters/service/scaffolder.ts:39-102` — the scaffolder creates exactly + three directories (`services//`, `src/`, `src/routers/`) and four files (`deno.json`, + `src/main.ts`, `src/router.ts`, `src/routers/health.ts`, `src/routers/v1.ts`). No `domain/`, + `application/`, `ports/`, `adapters/`, `tests/`, or `README.md`. +- `packages/cli/src/kernel/assets/service/routers/v1.ts.template:12-53` — `SORTABLE_FIELDS`, the + delegate accessor, `orderBy()`, `skip`/`take` math and `Promise.all([findMany, count])` all live + inside `list.handler`. +- `packages/cli/src/kernel/templates/service/generate-service-deno-json.ts:65` — + `test: 'deno test -A src/'`. Executed check on a directory containing only a non-test module: + `error: No test modules found`, exit code **1**. +- `packages/cli/src/public/features/services/add-handler/add-service-handler.ts:29-60` — the command + resolves `services//src/routers/.ts` and appends into it; + `packages/cli/src/kernel/adapters/service/router-source.ts:26-35` shows the emitted stub is a bare + `.handler(async ({ input }) => { void input; throw new Error('Not implemented: …'); })` with no + seam to place logic anywhere else. +- Doctrine vocabulary the framework holds itself to but never generates: + `docs/architecture/doctrine/05-folder-structure.md` (role table + strict layering direction) and + `docs/architecture/doctrine/06-archetypes.md:50-62` (minimum viable shapes). Both chapters are + scoped to `packages/`/`plugins/`; there is no consumer-facing equivalent. +- `grep` of `docs/site/services-sdk/services.md` for `internal/|src/routers|folder` returns nothing. + The only structure documentation is the file tree at + `docs/site/services-sdk/how-to/add-a-service.md:77-88`, which matches the generator and stops there. +- Corpus: `research/repo-audit/services-sdk.md` §5.2, §5.3 (S18, S19); + `research/preplan-package.md` ("Service-layout child under #1335"). + +## Current surface + +``` +services// +├── deno.json # tasks: check | dev | start | test (test → src/, no test modules) +└── src/ + ├── main.ts # defineService(router, {...}) + ├── router.ts # { v1: { : {...V1, health} } } + └── routers/ + ├── v1.ts # ORM + pagination + sort policy inline in handlers + └── health.ts +``` + +Contracts live in a separate versioned workspace (`contracts/versions/v/.contract.ts`), +which is the one boundary the scaffold does teach. + +## Target contract + +A **collapsible** internal vocabulary for generated services, derived from the doctrine but stated +for consumers, and generated by the CLI rather than described in prose: + +| Folder | Role in a generated service | Collapses into | +| --- | --- | --- | +| `src/domain/` | Entity types, invariants, pure policy (sortable fields, state machines). No IO. | `src/` for a single-entity service | +| `src/application/` | Use-cases the router calls. One file per operation or per entity. | `src/routers/.ts` only when a service has one trivial entity | +| `src/ports/` | Interfaces the service needs (repository, clock, outbound client). | `src/application/` | +| `src/adapters/` | One file per technology (`prisma--repository.ts`, `http--client.ts`). | — (never collapses once a second technology exists) | +| `src/routers/` | Thin oRPC adapter: bind contract procedure → use-case, map errors. **No ORM, no pagination math, no policy.** | — | +| `src/auth/` | Service-local authenticator/authorizer wiring over `@netscript/service/auth`. | `src/main.ts` | +| `tests/` | Cross-module suites; single-module tests colocated as `*_test.ts`. | colocated only | + +Rules: the router is always thin; `domain`/`ports`/`application` may collapse **upward** for a small +service and the decision table says exactly when; `adapters` never collapses into `routers`. The +generator emits the collapsed shape for a single-entity service and the expanded shape once a service +has two or more entities, and `service add-handler` writes the use-case into `src/application/` and a +binding line into the router instead of appending a logic-bearing stub to `routers/.ts`. A +migration path documents how an existing generated service moves from the collapsed to the expanded +shape without touching its contract. + +## Acceptance + +- [ ] A consumer-facing service-layout page documents the vocabulary and the collapse rules. +- [ ] The page carries a decision table stating when each folder collapses and when it must not. +- [ ] The generated single-entity service keeps the collapsed shape and still compiles. +- [ ] A generated two-entity service emits `domain/`, `application/`, `ports/`, `adapters/`. +- [ ] The generated `v1` router contains no ORM call, pagination math, or sort policy. +- [ ] `service add-handler` writes a use-case file and a thin router binding. +- [ ] A generated service ships at least one test module and `deno task test` exits 0. +- [ ] Negative: a router template containing a `db.`/delegate call fails a scaffold golden test. +- [ ] Negative: `deno test -A src/` never runs against a tree with no test module. +- [ ] The generated multi-entity project passes check, lint, fmt and test with no `any` and no `as unknown as`. +- [ ] A migration note tells existing projects how to expand a collapsed service. +- [ ] Scaffold golden fixtures pin the emitted tree for both shapes. + +## Boundaries + +Do not duplicate: **#1335** (this is a child of that umbrella — the umbrella owns the repo-wide +conformance inventory, this issue owns only the service internal layout); **#1333** (default *app* +frontend idiomacy and dynamic app naming — frontend tree, not service tree); **#1263** (generated +by-id handler returns 500 instead of a defined 404 — an error-mapping defect in the same template, +fixed independently); **#1328** (CLOSED — scaffold-owned quality gate implementation; do not reopen); +**#829** (official *plugins* ship compile-able `./services` entrypoints — plugin-owned services, not +generated ones); **#979**/**#980** (Aspire port pinning); **T3-03** (what a command *is*; this issue +decides only where it lives). This issue does **not** change `@netscript/service` runtime behavior, +the contracts workspace layout, or the doctrine chapters that govern `packages/`/`plugins/`. + +## Docs/consumer proof + +A services-sdk page that a stranger can follow to place a repository, a use-case and a router binding +without reading framework source; the generated two-entity project as the executable example; and the +scaffold runtime E2E proving the emitted tree matches the documented tree. Adoption is proven when the +generated router contains no ORM call — i.e. the anti-pattern is no longer copyable. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Pack T3 of the Fable-5 +remediation plan (`SYNTHESIS.md` §4); sources `research/repo-audit/services-sdk.md` S18/S19 and the +service-layout child named in `research/preplan-package.md`. `research/repo-audit/services-sdk.md` §7 +records that no open issue covers S18; re-checked against `research/github-board-open.md` §7. Draft +only; no board mutation performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T3-03-command-kit-implementation.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T3-03-command-kit-implementation.md new file mode 100644 index 0000000000..ec99b3b632 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T3-03-command-kit-implementation.md @@ -0,0 +1,111 @@ +# feat(service): no supported way to write a production command — transaction, expected version, idempotency, audit and outbox are hand-rolled in every generated app — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T3-03 · **Proposed milestone:** 0.0.8 (post-shift "Runtime truth + service slice") · +**Labels:** `type:feat` `area:service` `area:database` `area:cli` `area:telemetry` `priority:p1` +`status:triage` · **Depends on:** T3-01 (RFC-B ratified), T3-02 (where the command file lands) + +## Summary + +Everything a consumer needs to change business state correctly exists in the framework and nothing +composes it: `withTransaction` has zero callers, idempotency lives only inside the worker runtime, +and there is no outbox, audit row, expected-version condition, or command span anywhere. Generated +services write state with a bare ORM call, so the first production requirement a product hits — +"do not apply this twice, record why it changed, and tell the rest of the system" — is invented per +app. This issue implements the seam ratified by RFC-B and proves it by generating a representative +**non-CRUD** command that writes business state, an audit row, an outbox row and an idempotent +receipt atomically. + +## Evidence + +- `packages/database/mod.ts:128` — `withTransaction` exported, documented at + `docs/site/reference/database/index.md:52`, **zero callers** across `packages/`, `plugins/`, + `docs/`. `$transaction` appears in no scaffold template and no services-sdk page. +- `packages/cli/src/kernel/assets/service/routers/v1.ts.template:25-53` — generated writes are + single delegate calls with no transaction and no version condition. +- `packages/plugin-workers-core/src/ports/worker-idempotency-port.ts` — `claim`/`markApplied`/ + `release` exists for worker deliveries, KV-backed + (`src/stores/kv-worker-idempotency-store.ts`), and cannot join a database transaction. +- `grep -rln "outbox"` over `packages/`, `plugins/`, `docs/` → no match; no audit-record primitive. +- `packages/telemetry/src/attributes/helpers.ts` — job/saga/execution attribute helpers exist, no + command vocabulary; `src/attributes/spans.ts:45-46` — `rpc.client`/`rpc.server` only. +- `packages/contracts/src/application/contract-primitives.ts:21-52` — the six shared error codes + contain no conflict code, so optimistic-concurrency failure has no declared representation. +- Wave-6: R3 (billing, the only GO-grade run) owns its entire command layer in app space and needed + two evaluator `FAIL_FIX` rounds before scheduled state actually advanced; its relational + `trigger_events`/`job_execution_history` projections were empty while authority lived in KV. R2 + built an app-owned retry/compensation executor and never adopted `plugin-sagas` + (`research/wave-6-runs.md` §R2/§R3, X14). + +## Current surface + +A handler receives `ctx.db`, `ctx.principal` and `ctx.traceHeaders` +(`packages/service/src/builder/service-builder-impl.ts:259-282`) and is on its own from there. There +is no unit-of-work object, no receipt store, no side-record buffer, no relay, and no way to express +"apply only if the row is still at version N" other than a read-modify-write race. `IsolationLevel` +and `TransactionOptions` exist (`packages/database/ports/database-client.ts:59-77`) and are never +selected. Multi-commit work has an owner (`sagaCompensate`, +`packages/plugin-sagas-core/src/public/mod.ts:43`); single-commit work has none. + +## Target contract + +RFC-B's kit, shipped as ratified: a `UnitOfWorkPort` with published `UnitOfWorkCapabilities` and a +Prisma adapter over the existing `withTransaction`; `expectVersion` lowering to a conditional update +with an affected-row check and a **typed** conflict error added to the shared error map; an +idempotency receipt keyed by `(scope, commandName, idempotencyKey)` and guarded by a canonical +request hash, replaying the stored response instead of repeating the effect and rejecting key reuse +under a different hash; buffered `audit()` and `publish()` writes flushed inside the same commit; a +generated outbox relay worker job that delivers at-least-once with a dedupe key; a `command` span +plus `createCommandAttributes` carrying the same `correlationId` written into the audit and outbox +rows; and a fault-injection conformance suite with named seams. Stores that cannot provide +same-commit side records refuse composition at build time with a message naming the store, rather +than degrading silently. + +The generation half: the CLI emits one representative non-CRUD command in the generated project — an +operation that is not create/read/update/delete on a single row, that carries an idempotency key, +that asserts an expected version, and that commits business state + audit + outbox + receipt in one +transaction — plus its relay job and its tests. + +## Acceptance + +- [ ] `@netscript/service` exposes the command kit on a documented subpath with `deno doc --lint` clean. +- [ ] A Prisma unit-of-work adapter delegates to `withTransaction` and selects an isolation level. +- [ ] `UnitOfWorkCapabilities` is public and read by composition, not by prose. +- [ ] The shared error map declares the concurrency-conflict code used by `expectVersion`. +- [ ] A version mismatch returns a typed, contract-declared error, never a 500. +- [ ] A replayed idempotency key returns the first response and produces no second effect. +- [ ] Key reuse with a different request hash returns a typed conflict. +- [ ] Audit and outbox rows are absent after a fault injected before commit. +- [ ] The generated relay redelivers an unpublished outbox row after a publish fault. +- [ ] The `command` span carries the documented attributes and the audit/outbox `correlationId`. +- [ ] A command requiring same-commit side records refuses to compose on a KV-only store. +- [ ] The generated project ships a non-CRUD command using the kit end to end. +- [ ] Removing the kit from that command makes the conformance suite fail. +- [ ] The generated project passes check, lint, fmt and test with no `any` and no `as unknown as`. +- [ ] `gate:e2e` — scaffold runtime E2E exercises the command and observes the relay publish. + +## Boundaries + +Do not duplicate: **T3-01/RFC-B** (this implements it; it does not re-ratify it); **T3-02** (service +folder placement); **#742** (saga definition versioning); **#884**/**#885** (org-aware authorization +contracts and the auth conformance kit — the kit consumes `Principal` and never decides +authorization); **#1332** (DB-schema-first docs); **#1263** (defined-404 defect in the CRUD template); +**#1326**/**#1329** (stream producer durability and SSE envelope — the relay may publish to a stream +but does not change stream semantics); **#1280** (backing-service health, blocked upstream); **#1278** +(type-soundness inventory — this issue must satisfy it, not restate it). Explicit non-goals: no +billing/ledger/money vocabulary, no event-sourcing runtime, no audit taxonomy, no ORM abstraction, no +distributed-transaction emulation, and no exactly-once delivery claim. + +## Docs/consumer proof + +A services-sdk how-to that replaces the hand-rolled pattern with the kit and type-checks against the +published export map; the generated non-CRUD command as the executable reference; and a measured +Wave-7 row showing an unfamiliar agent either using the generated command path or recording an +explicit rejection. The negative proof is the load-bearing one: deleting the kit from the generated +command must turn the suite red. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Pack T3 of the Fable-5 +remediation plan (`SYNTHESIS.md` §4); source item **H** in `research/preplan-package.md`, evidence +from `research/wave-6-runs.md` and `research/repo-audit/services-sdk.md`. Checked against +`research/github-board-open.md` §7 — no existing owner. Draft only; no board mutation performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T3-04-outbound-webhook-recipe.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T3-04-outbound-webhook-recipe.md new file mode 100644 index 0000000000..0783f35578 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T3-04-outbound-webhook-recipe.md @@ -0,0 +1,101 @@ +# docs(orchestration-runtime): outbound webhook delivery has no recipe or template — triggers ship inbound ingress only, so every product hand-rolls signing, retry and dead-lettering — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T3-04 · **Proposed milestone:** 0.0.8 (post-shift "Runtime truth + service slice") · +**Labels:** `type:docs` `area:docs` `area:plugins` `area:cli` `priority:p2` `status:triage` · +**Depends on:** T3-03 (soft — the outbox relay is the recipe's preferred delivery substrate; the +recipe can ship without it with a stated at-most-once caveat) + +## Summary + +NetScript receives webhooks well and says nothing about sending them. `defineWebhook` plus the +ingress/verifier seam is a first-class inbound capability with a tutorial chapter, but there is no +guidance, template, or primitive for *outbound* delivery — signing a payload, retrying with backoff, +capping attempts, dead-lettering, and letting a receiver deduplicate. Agent-built products therefore +invent it: the Wave-6 workflow builder wrote `fire-webhook` jobs by hand and the billing run +simulated outbound calls entirely. This was adjudicated **not a framework defect** — the capability +is composable from `plugin-workers` today — so the fix is a first-party **recipe plus a generated +worker template**, not a new plugin or a new primitive. + +## Evidence + +- Inbound is complete: `packages/plugin-triggers-core/README.md:7-12,60-97,142-145` + (`defineWebhook`, `TriggerIngressPort`, `WebhookVerifierPort`, HMAC verification, `enqueueJob`); + config surface `packages/config/src/domain/schemas/netscript-config-schema.ts:85,107-117` + (`type: 'webhook'`, `basePath: '/api/v1/webhooks'`); + docs `docs/site/orchestration-runtime/how-to/author-a-plugin.md:206,223,235` and the tutorial + chapter `docs/site/tutorials/storefront/05-shipping-webhook.md` — all inbound. +- Outbound is absent: `grep -rn "outbound"` filtered to webhook/delivery over `packages/`, + `plugins/`, `docs/site` returns nothing; `grep -rln "deliverWebhook|WebhookDelivery|webhook-delivery"` + returns nothing. +- The composable parts already exist: `defineJob` / `defineJobHandler` / `createJobTools` + (`packages/plugin-workers-core/mod.ts:17-24`), delivery idempotency + (`packages/plugin-workers-core/src/ports/worker-idempotency-port.ts`), and a dead-letter vocabulary + (`packages/queue/mod.ts:70-72` — `DeadLetterReason`, `DeadLetterRecord`, `DeadLetterStorePort`). +- Wave-6: R2 hand-built `fire-webhook` jobs, which failed before token validation because the worker + had no service endpoint (`research/wave-6-runs.md` §R2 D2); R3 shipped "simulated webhooks" + (`research/wave-6-runs.md` §R3 product shape). +- Adjudication: `SYNTHESIS.md` §3.5 — "Not a framework defect. Disposition: first-party **recipe + + worker template** (docs + generation), not a new plugin; p2". + +## Current surface + +A consumer who needs to notify a third party has: `defineJob` for the background execution, +`enqueueJob` from a trigger or a service, KV/queue-backed retry inside the worker runtime, a +dead-letter store, and `crypto.subtle` for HMAC. What they do not have is any page, example, or +generated file telling them how to assemble those into a delivery that a receiver can trust and +deduplicate — nor a statement of which guarantees the assembly does and does not provide. + +## Target contract + +1. **A recipe page** under the orchestration-runtime how-tos: outbound webhook delivery, covering the + endpoint registry (consumer-owned rows: url, secret reference, active flag), the signed request + shape (timestamp + payload HMAC in a documented header, receiver-side verification snippet), retry + with capped exponential backoff and a maximum attempt count, dead-lettering through the existing + `DeadLetterStorePort` vocabulary, a stable per-delivery id the receiver deduplicates on, and an + explicit guarantee statement: **at-least-once, never exactly-once**. +2. **A generated worker template** the CLI can emit — one `defineJob` delivery job plus its handler, + parameterized by endpoint lookup, that reads its target from injected configuration rather than a + hardcoded URL (the exact failure R2 hit), records attempt/outcome, and dead-letters on exhaustion. +3. **The outbox seam.** When the command kit (T3-03) is present, the recipe's canonical source of + deliveries is the outbox relay, so a webhook is announced only for state that actually committed. + Without it, the recipe states plainly that enqueueing from inside a request is at-most-once. +4. **Non-goals stated on the page:** this is not a plugin, not a delivery SLA, not a subscription + management API, and not a receiver-side framework. + +## Acceptance + +- [ ] An outbound-webhook how-to exists under the orchestration-runtime section. +- [ ] The page documents signing, retry/backoff caps, dead-lettering and receiver deduplication. +- [ ] The page states at-least-once delivery and explicitly disclaims exactly-once. +- [ ] The page's code samples type-check against published entrypoints. +- [ ] A generated delivery worker template ships and reads its endpoint from injected config. +- [ ] The template dead-letters after the configured maximum attempts. +- [ ] Negative: a template with a hardcoded endpoint URL fails a scaffold golden test. +- [ ] Negative: a delivery that exhausts retries is provably in the dead-letter store, not dropped. +- [ ] Tests cover signature generation, backoff schedule, attempt cap and dead-letter routing. +- [ ] The page cross-links inbound `defineWebhook` and names it as the opposite direction. +- [ ] No new package or plugin is created by this issue. + +## Boundaries + +Do not duplicate: **#1329** (streams SSE envelope + OTEL propagation); **#1325** (triggers Redis +adapter glue); **#554**/**#555** (`TriggerDlqPort` contract route and `DeadLetterStore` CLI/contract +API — this recipe *uses* the dead-letter vocabulary and must not define a second one); **#1208** +(tutorial phase 1) and **#1210** (per-API web-layer deep dives) — this is an orchestration-runtime +how-to, not a tutorial rewrite or a page-builder page; **#742** (saga versioning); **#878** +(enterprise auth audit events). Explicitly out of scope: inbound webhook behavior, a webhooks plugin, +a subscription/endpoint management API, and any change to `plugin-triggers` runtime semantics. + +## Docs/consumer proof + +The recipe is proven by the generated template compiling and running in the scaffold runtime E2E, by +its samples type-checking against the published export map, and by a Wave-7 row recording whether an +unfamiliar agent used the recipe or hand-rolled delivery again. If an agent still hand-rolls it, the +recipe failed and the disposition (docs vs generation vs primitive) is re-opened with that evidence. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Pack T3 of the Fable-5 +remediation plan; disposition adjudicated in `SYNTHESIS.md` §3.5 from the verify-before-filing list in +`research/preplan-package.md`. Checked against `research/github-board-open.md` §7 — no existing owner +for outbound webhook delivery. Draft only; no board mutation performed. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-01-saga-publication-receipts.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-01-saga-publication-receipts.md new file mode 100644 index 0000000000..45fb66d613 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-01-saga-publication-receipts.md @@ -0,0 +1,124 @@ +# fix(sagas): publish receipts are discardable and the publisher silently falls back to 127.0.0.1:8092 — jobs report success while the saga never starts — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T4-01 · **Proposed milestone:** 0.0.8 (new — "Runtime truth + service slice", per +SYNTHESIS §5.3 rename-shift) · **Labels:** `type:fix` `area:plugins` `area:aspire` `area:docs` +`priority:p0` `status:triage` · **Depends on:** none (sequenced with T4-06 for port resolution and +T4-08 for the detecting gate) + +## Summary + +`SagaPublisherResult` is a well-designed non-throwing discriminated union, but it is not a checked +result: `await publisher.publish(m)` type-checks with the receipt thrown away, and the first-party +sample job the scaffold writes into every new project does exactly that. Composed with the +publisher's silent `http://127.0.0.1:8092` endpoint fallback, an unreachable `sagas-api` produces a +rejected receipt that nobody reads, so the worker job returns `createSuccessResult(...)` while the +saga never starts — no log, no telemetry event, no failed job. The identical code is embedded +verbatim in the canonical documentation with the comment "a typed receipt comes back". This is the +highest-severity composed defect in the runtime audit and no open issue owns it. + +## Evidence + +- Corpus: `research/repo-audit/runtime-plugins.md` §2.2 (silent-drop chain), §1.4 (fixed-port + fallback), §1.5 (discovery-key asymmetry), §8 ledger row 2; `SYNTHESIS.md` §1.4, §2 "Runtime + correctness", §6 (T4 pack, "saga receipt p0" — no existing owner). +- `plugins/sagas/src/runtime/saga-publisher.ts:295-307` — `resolveServiceUrl()` ends + `?? \`http://127.0.0.1:${SAGAS_API_DEFAULT_PORT}\``; `plugins/sagas/src/constants.ts:11` sets + `SAGAS_API_DEFAULT_PORT = 8092`. Called from `saga-publisher.ts:164`. +- `packages/plugin-sagas-core/src/integration/publisher/saga-publisher-port.ts:24-46` — the + `SagaPublisherReceipt | SagaPublisherRejected` union. Nothing forces a caller to discriminate it. +- `plugins/workers/src/cli/official-sample-configuration.ts:393-403` — the scaffold-emitted job: + `await sagaPublisher.publish({ type: 'UserSettingsCreated', payload: { userId } });` followed + directly by `createSuccessResult({ userId, settingsCreated: true, source: 'scaffold-sample' })`. +- `docs/site/durable-workflows/sagas.md:418` — the same body embedded in a `tabbedCode` block + labelled "verbatim from the scaffold", carrying the comment + `// This is the message the saga below consumes — a typed receipt comes back.` +- `packages/sdk/src/discovery/service-url.ts:55-61` builds `services____http__0` with the raw + hyphen; `packages/aspire/src/application/build-vite-env-var-name.ts:50-66` normalizes every + non-alphanumeric to `_`. Only one form can match what Aspire exports for `sagas-api`; if the + server form is normalized too, every discovery lookup misses and drops to the 8092 fallback. +- Contrast (the correct pattern already in-repo): + `packages/plugin-streams-core/src/application/stream-url-resolver.ts:99-133` throws a diagnostic + error rather than falling back to a port. +- Adjacent open issues: #1326 (streams producer, p0), #1329 (SSE envelope, p0), #1325 (triggers KV + adapter), #979 (plugin resources pin host ports, unmilestoned). + +## Current surface + +`HttpSagaPublisher.publish()` never throws: non-2xx becomes `rejectedResult(..., retryable = status +∈ {408,409,425,429,500,502,503,504})`, a transport throw becomes `rejectedResult(..., +isRetryable(cause))` (`saga-publisher.ts:106-134`). `traceparent`/`tracestate` are propagated +(`:280-289`). Endpoint resolution tries `baseUrl`, `services__sagas-api__https__0`, +`services__sagas-api__http__0`, `SAGAS_API_URL`, `NETSCRIPT_SAGAS_URL`, then the 8092 literal. The +same literal appears in `plugins/sagas/src/cli/adapters/runtime-api-client.ts:27` and +`plugins/sagas/src/e2e/probes/probe-context.ts:3`. Since #1211 (`0b11ca47a`) scaffold ports are +allocated from 49152–65535 (`packages/cli/src/kernel/domain/scaffold/default-port-allocation.ts:4-7`), +so 8092 is not a plausible default in a generated project — it is a guess that connects to nothing, +or worse, to another workspace's saga API. + +## Target contract + +1. **Publication result is non-ignorable.** Either `publish()` returns a type the compiler forces a + caller to discriminate before the value is dropped, or the port gains an explicit + throw-on-rejection entry point and a repo gate rejects a bare `await publisher.publish(...)` + whose result is unused. The chosen mechanism is stated once in the port's doc comment. +2. **No silent endpoint guessing under an AppHost.** When an Aspire environment is detected (any + `services__*` key present, or an explicit `NETSCRIPT_ASPIRE` marker), a failure to resolve the + sagas endpoint raises a diagnostic error naming each source tried and the exact env key expected + — the `stream-url-resolver.ts` pattern. Outside an AppHost, a fixed default is permitted only + when it is explicitly configured, never as a trailing `??`. +3. **Discovery keys are decided, not assumed.** One documented normalization for hyphenated + resource names, applied identically on the server and browser paths, with a test that records + what Aspire actually exports. +4. **The shipped sample models the correct pattern.** The scaffold's `create-user-settings` job + discriminates the receipt, logs/telemeters the rejection, and fails the job on a non-retryable + rejection. +5. **Correlation is provable from durable state.** For an accepted publish, the correlation key + written to the saga store and the `netscript.correlation.id` on the emitted spans agree, and that + agreement is asserted — not inferred from logs. + +## Acceptance + +- [ ] Discarding a saga publish result fails type-check or a named lint/quality gate. +- [ ] The publisher raises a diagnostic error instead of falling back to `127.0.0.1:8092` when an + AppHost environment is detected. +- [ ] The endpoint-resolution error names every source tried and the exact env key expected. +- [ ] Hyphenated service-discovery keys use one documented normalization on server and browser + paths, with a test recording what Aspire exports for `sagas-api`. +- [ ] The scaffold sample job discriminates the receipt and fails the job on a non-retryable + rejection. +- [ ] `docs/site/durable-workflows/sagas.md` embeds the fixed sample and a docs test proves the + embedded code matches the scaffold source. +- [ ] A negative test proves an unreachable sagas-api makes the sample job fail rather than succeed. +- [ ] A negative test proves removing the endpoint-resolution error path fails a gate rather than + silently restoring a fixed-port default. +- [ ] Tests cover restart, duplicate publish, out-of-order delivery, and sagas-api unavailable. +- [ ] Correlation is proven from persisted saga state plus an OTEL trace, not from log text. + +## Boundaries + +- **#1326** owns `DurableStreamProducer` reconnect/buffer bounds; **#1329** owns the versioned SSE + envelope. Do not re-file or absorb either — streams receipts (`upsert()`/`delete()` return `void`, + `packages/plugin-streams-core/src/application/create-durable-stream.ts:167,205`) are named here + only as context. +- **#1325** owns the triggers glue KV-adapter omission. **#979** (+ **#980**) own removing pinned + host ports and the E2E/docs prerequisites for endpoint resolution; T4-06 sequences behind #979 and + covers the *contribution declarations*. This issue covers only the sagas publisher hot path. +- **#1280** (blocked upstream) owns backing-service health checks. Not in scope. +- **Saga OOM remains a verify-first investigation row** (SYNTHESIS §3.6 / `preplan-package.md` + §Verify-before-filing). It is deliberately **not** part of this issue and must not be folded in; + it needs a repro on the current canary before anything is filed. +- Saga compensation semantics (no prior-step rollback, unpersisted compensation state) are separate + runtime rows and are not fixed here. + +## Docs/consumer proof + +`docs/site/durable-workflows/sagas.md` and the scaffold source are proven identical by an executed +docs test, so the "verbatim from the scaffold" claim becomes checkable rather than asserted. A +freshly scaffolded project with `sagas-api` stopped shows the sample job in a failed state in the +workers API/CLI listing and an error span in the Aspire dashboard — the consumer-visible difference +between this fix and today's green-job-no-saga behavior. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Drafted from the Stage-B +runtime-plugins audit; all code claims re-verified against worktree baseline `fac9e339042c`. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-02-plugin-child-liveness-contract.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-02-plugin-child-liveness-contract.md new file mode 100644 index 0000000000..d8266d6295 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-02-plugin-child-liveness-contract.md @@ -0,0 +1,132 @@ +# fix(plugins): every declareHealthChecks returns only the API resource — a crash-looping background child leaves the declared health surface green — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T4-02 · **Proposed milestone:** 0.0.8 (new — "Runtime truth + service slice") · +**Labels:** `type:fix` `area:plugins` `area:aspire` `area:cli` `priority:p1` `status:triage` · +**Depends on:** T4-06 (health URLs must resolve real allocated ports), T4-08 (the gate that consumes +this contract) + +## Summary + +Each first-party plugin contributes an API resource **and** a background child, but every +`declareHealthChecks()` implementation returns exactly one entry — always the API resource. The +background children (`workers-combined`, `sagas-runner`, `trigger-processor`) contribute no health +signal at all, and the liveness state that already exists in-process (`Worker.healthStatus`, the +listener supervisor's `restartCount`/`lastError`, the saga supervisor snapshot) is wired to nothing +Aspire, `plugin doctor`, MCP, or the dashboard reads. The consequence is the framework's most +expensive failure mode: a child that crash-loops on every start leaves the entire declared health +surface green. NetScript needs one declared child-liveness contract before any of that state can be +surfaced. + +## Evidence + +- Corpus: `research/repo-audit/runtime-plugins.md` §5.1/§5.2 ("Highest leverage item in the audit: + it is the *detector* whose absence lets the other defects ship"), §1.2, §3.2; + `research/repo-audit/observability-aspire.md` §1.8, GAP-5; `SYNTHESIS.md` §1.4, §6 (T4 pack, + "plugin child liveness" — no existing owner). +- `plugins/workers/src/aspire/workers-contribution.ts:77-86` — `declareHealthChecks()` returns one + entry for `WORKERS_API_RESOURCE`. The background child is registered at `:55-61` + (`addDenoBackground(WORKERS_COMBINED_RESOURCE, …)`) and has none. +- `plugins/sagas/src/aspire/sagas-contribution.ts:145-153` — one entry, `sagas-api`. The runner is + registered at `:122-128` with none. +- `plugins/triggers/src/aspire/triggers-contribution.ts:147-155` — one entry, triggers API. The + processor has none. +- `plugins/streams/src/aspire/streams-contribution.ts:48-55` — one entry at a hardcoded + `http://localhost:4437/health`. +- Liveness state that exists and is unread: `plugins/workers/worker/worker.ts:140` + (`get healthStatus()`), `plugins/workers/worker/listener-supervisor.ts:75-83` (`snapshot()` with + `status`, `healthy`, `restartCount`, `lastError`). +- Asymmetric child health today: the **sagas** glue stub serves `/health` mapping the supervisor + snapshot to 200/503 + (`plugins/sagas/src/adapter/resources/glue/runtime.stub.ts:19-30`) and the generated AppHost does + probe it (`packages/cli/src/kernel/templates/aspire/helpers/register/generate-register-background.ts:79-86`, + `isSagasBackgroundResource`). The **triggers** and **workers** glue stubs are bare + `await startCombinedProcess();` shims with no HTTP surface at all + (`plugins/triggers/src/adapter/resources/glue/runtime.stub.ts:16,19`; + `plugins/workers/src/adapter/resources/glue/runtime.stub.ts:21,24`), and + `generate-register-background.ts:90` special-cases triggers for something other than health. +- The declaration seam itself is unread in production: `composeAppHost` calls only + `contribution.contribute(...)` (`packages/aspire/src/application/compose-apphost.ts:47`), and + `composeAppHost` has no production caller — the real wiring is the CLI pipeline at + `packages/cli/src/public/features/plugins/install/install-plugin.ts:502-512`. +- Adjacent: #1325 (triggers crash-loop that this blindness let ship), #1280 (`status:blocked`). + +## Current surface + +Three sources of truth, none joined. (a) `declareHealthChecks()` — declarative, API-only, and read +by nothing outside tests. (b) The generated AppHost — probes app/service resources via +`withHttpHealthCheck` and, for sagas only, the background child. (c) In-process supervisor state — +accurate, structured, and never leaves the process. `netscript plugin doctor`, the MCP tool surface, +and the dashboard therefore all report "the API answered", which is orthogonal to whether the child +that does the work is alive. + +## Target contract + +1. **A declared child state vocabulary.** Every plugin background child reports one of a closed set + (proposed: `starting | ready | degraded | crash-looping | stopped | failed`) with, at minimum: + process/child state, **registry readiness** (the generated registry module loaded and every entry + registered), **dependency readiness** (KV/DB/upstream API reachable), `restartCount`, and + `lastFatalError` (message + timestamp, redacted of secrets). +2. **One transport, generated.** Every KV-backed first-party background runtime serves the contract + over the same route shape the sagas glue already uses, and the AppHost generator probes each + child by rule — not by an `isSagasBackgroundResource`-style name special case. +3. **`declareHealthChecks()` becomes truthful or is removed.** Either the declaration seam is read + by the production pipeline (and then declares children too), or it is deleted so plugin authors + scaffolded from `new-plugin-use-case.ts:524-529` stop writing declarations nothing consumes. One + source of truth, decided explicitly. +4. **Crash-loop and wrapper-alive/child-dead are explicit, not inferred.** `netscript plugin doctor`, + the MCP health/doctor tools, and the dashboard each render a distinct, named state for + "API resource healthy, background child dead" and for "child restarting above threshold" — never + a single aggregate green. + +## Acceptance + +- [ ] Every first-party plugin background child reports the declared child state vocabulary. +- [ ] The AppHost generator probes every background child by rule, with no per-plugin name special + case. +- [ ] Child readiness includes registry load and dependency reachability, not just process + liveness. +- [ ] `restartCount` and the last fatal error are exposed on the child health payload. +- [ ] `plugin doctor`, the MCP health surface, and the dashboard each render "child dead, API alive" + as its own state. +- [ ] A crash-looping child moves to a `crash-looping` state within a documented threshold rather + than flapping between ready and failed. +- [ ] `declareHealthChecks()` is either consumed by the production pipeline or removed, and the + plugin-authoring scaffold matches that decision. +- [ ] A negative test proves a deliberately crash-looping background child turns the health surface + red. +- [ ] A negative test proves an API resource healthy with its child stopped is reported as degraded, + not healthy. +- [ ] Tests cover startup race, registry load failure, dependency-unavailable, restart storm, and + clean shutdown. + +## Boundaries + +- **#1280 is a different problem and is blocked upstream.** It covers *backing* services (Postgres, + Redis/Garnet, Deno KV) whose images serve no health route and for which Aspire's TypeScript + AppHost cannot register custom checks. This issue covers **NetScript-authored plugin children**, + which run our own Deno entrypoint and can serve whatever route we generate. Do not re-litigate + #1280, do not mark it satisfied by this work, and do not propose generated probes against backing + images here. +- **#1325** owns the triggers KV-adapter omission itself (the defect); this issue owns the detector + contract. **T4-08** owns wiring the E2E gate to that contract. Neither substitutes for the other. +- **#828 / #512 / #937** (process-manager PM-B supervised-child helper, PM-1 process-graph state + vocabulary, doctor frontend five-state taxonomy) own bare-metal supervision and the frontend + doctor taxonomy. Reuse their vocabulary where it fits, but do not re-file their scope or block on + the process-manager epic — this contract must ship under Aspire-hosted plugins first. +- **#734 / #429-#431** (dashboard panel contributions) consume this surface; they do not define it. +- Backing-service health, plugin discovery of third-party factories (#1093), and the streams + durability decision (T4-03) are out of scope. + +## Docs/consumer proof + +`docs/site/orchestration-runtime/**` gains one page describing the child state vocabulary and what +each state means operationally, generated from the exported type rather than hand-written. Consumer +proof: on a scaffolded project, stopping a background child makes `netscript plugin doctor` exit +non-zero and names the child — reproducible in the installed-consumer smoke (#1343) without reading +container logs. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. All code claims re-verified +against worktree baseline `fac9e339042c`; the sagas-child-probes-but-others-do-not asymmetry was +found during this verification and is newer than the Stage-B audit text. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-03-streams-durable-storage-semantics.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-03-streams-durable-storage-semantics.md new file mode 100644 index 0000000000..3f15478ec1 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-03-streams-durable-storage-semantics.md @@ -0,0 +1,117 @@ +# fix(streams): STREAMS_DATA_DIR is set by nothing the framework generates, so "durable streams" is always in-memory — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T4-03 · **Proposed milestone:** 0.0.8 (new — "Runtime truth + service slice") · +**Labels:** `type:fix` `area:plugins` `area:aspire` `area:docs` `priority:p1` `status:triage` · +**Depends on:** none · **Coordinate with:** #1326, #1329 (plan as a trio; do not merge scopes) + +## Summary + +The streams service selects file-backed storage only when `STREAMS_DATA_DIR` is set; unset means +in-memory, non-durable, and the service warns once and then reports healthy anyway. `STREAMS_DATA_DIR` +appears in exactly five files repo-wide, all of them the streams service itself, its test, its README +and one docs paragraph — no scaffold, no Aspire contribution, no generated AppHost, and no template +ever sets it. The default every user gets is therefore a plugin named "durable streams" whose data +does not survive a restart, backed by an upstream `DurableStreamTestServer`. Worse, the documented +escape hatch is itself unproven: a measured wave-4 run found file-backed mode silently swallowing +producer writes. The roadmap must decide between durable-by-default and honestly-ephemeral; it +cannot keep shipping the name without the property. + +## Evidence + +- Corpus: `research/repo-audit/runtime-plugins.md` §4.2, §8 ledger row 7; `research/wave-4.md` §2.2 + — "`STREAMS_DATA_DIR` file-backed mode **silently swallows all producer writes**; `flush()` after + upsert does the same; `flush()` resolves either way"; `SYNTHESIS.md` §1.4, §6 (T4 pack, "stream + durability" — no existing owner). +- `plugins/streams/services/src/main.ts:36-40` — reads `STREAMS_DATA_DIR`, calls + `describeStorageDurability(dataDir)`, `console.warn`s when not durable, then starts. +- `plugins/streams/services/src/durability.ts:16-29` — unset or empty ⇒ + `{ durable: false, message: 'Streams service storage is non-durable (in-memory)…' }`. +- `plugins/streams/services/src/main.ts:50-53` — the backing store is + `new DurableStreamTestServer({ port, host: '127.0.0.1', dataDir })`, i.e. a *test* server used as + the product substrate. +- Repo-wide grep for `STREAMS_DATA_DIR` (excluding `.llm/`, `.git/`) returns five files only: + `docs/site/durable-workflows/streams.md:287`, `plugins/streams/README.md:20-22`, + `plugins/streams/services/src/durability.ts`, `.../durability_test.ts`, `.../main.ts`. **Zero hits + in `packages/cli/`, zero in any template or generated AppHost.** +- `plugins/streams/src/aspire/streams-contribution.ts:41-44` — `declareEnv()` sets only + `DURABLE_STREAMS_URL`, so even the (unread) declaration seam never mentions persistence. +- Health check ignores durability: + `plugins/streams/services/src/main.ts:58-70` registers `healthChecks.custom('durable-streams-server', …)` + which probes upstream reachability only. +- Adjacent open issues: #1326 (p0, producer never reconnects), #1329 (p0, SSE envelope), #1280 + (blocked, backing-service health), #431 (dashboard streams panel). + +## Current surface + +Durability is opt-in via an env var that no generated artifact sets, so it is effectively +unreachable for the scaffold user; the only documentation of it is a prose callout telling the user +to set it themselves. The service reports healthy in both modes and advertises no storage mode on +its health payload, so neither the dashboard nor `plugin doctor` can tell an operator which one they +are running. On the durable branch there is no restart proof anywhere in the repo: `durability_test.ts` +asserts only the two message strings. + +## Target contract + +The generated AppHost must take exactly one of two positions, recorded in the plugin manifest and +enforced by a gate: + +- **(A) Durable by default.** The AppHost mounts an explicit persistent volume/data directory for + the streams resource, sets the corresponding service configuration, and proves that events written + before a restart are readable after it. Retention, corruption behavior (unreadable/partial data + dir), and backup/restore expectations are documented as part of the contract. +- **(B) Explicitly ephemeral.** The service, its resource name, its README, the docs page and the + package's own description state that stream storage is in-process and non-durable; every "durable" + claim is corrected; and any durable mode is gated behind an explicitly-opted, restart-proven + configuration rather than an undocumented env var. + +In both branches: the storage mode appears on the health/status payload; the service refuses to +report a durability property it has not proven; and the wave-4 write-swallowing symptom on the +file-backed path is reproduced and fixed (or the path is removed) before that path is advertised. + +## Acceptance + +- [ ] The generated AppHost either provisions persistent stream storage or declares the service + ephemeral — with the choice recorded in the plugin manifest. +- [ ] Events written before a service restart are readable after it, proven by an executed test + (branch A) or explicitly refused as a claim (branch B). +- [ ] The health/status payload reports the active storage mode. +- [ ] The file-backed write path is proven to deliver producer writes, or is removed rather than + documented. +- [ ] Retention, data-dir corruption, and backup/restore expectations are documented for the shipped + branch. +- [ ] Every "durable" claim in `plugins/streams/README.md`, `docs/site/durable-workflows/streams.md` + and package metadata matches the shipped branch. +- [ ] A negative test proves an unwritable or missing data directory fails startup or degrades + health, rather than silently falling back to memory. +- [ ] A negative test proves the service cannot report a durability property it has not proven. +- [ ] `DurableStreamTestServer` is either replaced as the product substrate or its use is recorded + as an accepted, named architecture-debt entry with an exit condition. + +## Boundaries + +- **#1326 owns producer reconnect, buffer bounds, readiness and shutdown semantics.** This issue must + not change `DurableStreamProducer` connect/retry behavior. **#1329 owns the versioned SSE event + envelope**; storage-mode fields do not enter the wire envelope without #1329's schema. + Plan all three together; land them as separate PRs with separate closing keywords. +- **#1280** (blocked upstream) owns backing-service health checks; the streams service is + NetScript-authored and is not covered by that block. +- **T4-06** owns the hardcoded `4437` in the streams contribution and the generated consumer stub; + do not fix ports here. +- **#431** (dashboard streams panel) consumes the storage-mode signal; it does not define it. +- Choosing an alternative durable-stream backend, or introducing a new persistence plugin archetype, + is out of scope — if branch A cannot be met with the current substrate, the outcome is branch B + plus a follow-up, not an unplanned backend swap. + +## Docs/consumer proof + +`docs/site/durable-workflows/streams.md` states one storage story that matches the shipped branch, +with the restart behavior demonstrated rather than described. Consumer proof: on a scaffolded +project, restart the streams resource and re-read a stream — branch A shows the prior events, +branch B shows an empty stream *and* a status payload that said so before the restart. Either +outcome is checkable by an unfamiliar agent without reading plugin source. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Grep counts and file:line +citations re-verified against worktree baseline `fac9e339042c`; the file-backed write-swallowing +symptom is a wave-4 measurement carried forward, not re-measured in this planning run. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-04-saga-compensation-telemetry-callsites.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-04-saga-compensation-telemetry-callsites.md new file mode 100644 index 0000000000..cb71af90c5 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-04-saga-compensation-telemetry-callsites.md @@ -0,0 +1,124 @@ +# fix(sagas): 5 of 6 saga span factories have zero callers — compensation emits no span and deleting the saga telemetry surface keeps CI green — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T4-04 · **Proposed milestone:** 0.0.8 (new — "Runtime truth + service slice") · +**Labels:** `type:fix` `area:plugins` `area:telemetry` `priority:p1` `status:triage` · +**Depends on:** none (T4-08 covers the child/streams legs of the same gate; the saga-span assertion +lands here) + +## Summary + +`plugin-sagas-core` defines six saga span factories. Exactly one — `startHandleSpan` — has a +production caller. The five cascade factories, including `saga.cascade.compensate`, are defined, +typed, attributed and never emitted, and `SagaCompensator` takes no instrumentation dependency at +all, so a compensation executes inside its parent's context invisibly. The saga attribute set also +omits `netscript.correlation.id`, the cross-seam convention every other plane emits, so saga spans +cannot join the correlation assertion the Flow-B gate already runs. No E2E gate asserts any `saga.*` +span, which is the structural reason this survived: the whole saga observability surface could be +deleted today and every gate would stay green. + +## Evidence + +- Corpus: `research/repo-audit/observability-aspire.md` GAP-1, GAP-2, GAP-3, GAP-4, §3 table, + §5 items 1-3; `SYNTHESIS.md` §1.4, §6 (T4 pack, "saga-span call-sites" — no existing owner). +- `packages/plugin-sagas-core/src/telemetry/instrumentation.ts` — six factories at `:183` + (`SagaSpanNames.HANDLE`), `:193` (`CASCADE_SEND`), `:207` (`CASCADE_SCHEDULE`), `:218` + (`CASCADE_SPAWN`), `:229` (`CASCADE_COMPENSATE`), `:240` (`CASCADE_COMPLETE`). +- Repo-wide `grep -rn "startCascade" --include=*.ts` returns **only the five definition lines in + that file** — no caller, no test. +- The single live caller: `packages/plugin-sagas-core/src/runtime/saga-engine.ts:274` + (`startHandleSpan`). +- `packages/plugin-sagas-core/src/runtime/saga-compensator.ts:35-51` — + `SagaCompensatorOptions = Readonly<{ id?: string; clock: SagaClockPort }>`; the constructor stores + only `#clock`. No instrumentation, no span. +- `packages/plugin-sagas-core/src/telemetry/attributes.ts:28,52` — the only correlation key is + `SAGA_CORRELATION_KEY: 'netscript.saga.correlation_key'`. The cross-seam convention + `CORRELATION_ID: 'netscript.correlation.id'` + (`packages/telemetry/src/domain/telemetry-convention.ts:52`) is absent from the saga plane, while + streams, workers and triggers all emit it. +- The tracer is live, not unwired: `plugins/sagas/src/runtime/saga-supervisor.ts:199-206` + (`withDefaultTelemetry()` → `createSagaTelemetry()`), flowing through `create-saga-runtime.ts:93,102`. + Only the call sites are missing. +- No gate asserts a saga span: + `packages/cli/e2e/src/application/gates/scaffold/validate-flow-b-traces.ts` asserts `trigger.*`, + `queue.*`, `job.execute`, `flow-b.callback`, `rpc.client`, `stream.subscribe` only; the saga E2E + gates (`packages/cli/e2e/src/domain/cli-surface.ts:131-133`) are health/list/instances probes. +- Known interaction: nested cascaded compensation throws "deferred to phase 7d" + (`saga-compensator.ts:103-107`). + +## Current surface + +An operator or agent debugging a failed distributed transaction sees `saga.handle` spans and then +nothing. Which steps unwound, why, and how many are unobservable in traces and must be reconstructed +from logs — precisely the expensive fallback #1197 measured agents taking. The attributes needed +(`SagaAttributes.COMPENSATION_REASON`, `COMPENSATION_CASCADE_SIZE`) already exist and are never +populated. Because no assertion covers the plane, a regression is indistinguishable from the status +quo. + +## Target contract + +1. **Every cascade kind emits its span at dispatch.** `send`, `schedule`, `spawn`, `compensate` and + `complete` each start their existing factory span at the real dispatch site (the + `SagaBusBridge` cascade path), parented to the `saga.handle` span of the message that produced + the cascade. +2. **`SagaCompensator` is instrumented.** It accepts a `SagaInstrumentation` dependency and opens + `saga.cascade.compensate` around the handler, populating reason and cascade size; the "no handler + registered" outcome is recorded on the span rather than being silently dropped. +3. **Saga spans carry `netscript.correlation.id`.** The cross-seam correlation attribute is added to + the saga attribute set alongside the existing domain-level `SAGA_CORRELATION_KEY` — the two are + distinct keys with distinct meanings, documented as such. +4. **The gate makes the plane non-deletable.** `validate-flow-b-traces.ts` gains a saga leg + asserting the `saga.handle` → `saga.cascade.compensate` parent edge and a single correlation id + shared with the existing seven boundary spans. +5. **Deferred behavior stays honest.** Nested cascaded compensation continues to fail loudly; the + span records the deferral rather than implying a traced unwind. + +## Acceptance + +- [ ] Each of the five cascade span factories has a production call site at its dispatch point. +- [ ] `SagaCompensator` accepts instrumentation and emits `saga.cascade.compensate` around the + handler. +- [ ] `COMPENSATION_REASON` and `COMPENSATION_CASCADE_SIZE` are populated on emitted compensation + spans. +- [ ] `netscript.correlation.id` is emitted on saga spans and documented as distinct from + `netscript.saga.correlation_key`. +- [ ] The Flow-B validator asserts the `saga.handle` → `saga.cascade.compensate` parent edge. +- [ ] The Flow-B validator asserts one correlation id shared across the saga leg and the existing + boundary spans. +- [ ] A negative test proves removing any cascade span call site turns the gate red. +- [ ] A negative test proves a compensation with no registered handler is visible in traces rather + than silent. +- [ ] The nested-compensation deferral is recorded on the span and documented, not implied as + supported. +- [ ] `packages/plugin-sagas-core/README.md` documents every emitted span name and its attributes. + +## Boundaries + +- **T4-08** owns extending the merge-readiness E2E to probe background children and streams health, + and owns the `'compensating'`-status defect. The **saga span assertions land here**, in the Flow-B + validator; do not duplicate them in T4-08's scope. +- **#1329** owns the stream SSE/OTEL envelope; correlation on the stream wire is its scope, not this + one's. +- **#418 / #413 / #557** (dashboard Live Flow, `TelemetryQueryPort`, seam-event flow plane) **depend + on** this work — a Live Flow view cannot render a compensation leg that emits no spans — but they + do not own it and must not be re-filed here. +- **#1197 / #1090** own agent adoption measurement of the observability surface. Not this issue. +- Saga compensation *semantics* — no prior-step rollback (`saga-compensator.ts:57`), compensation + state never persisted (`saga-bus-bridge.ts:44-52`, `:220-227`), and the asymmetric + missing-handler failure (`saga-compensator.ts:57-68` vs `:116-120`) — are separate runtime rows + and are **not** fixed by adding telemetry. Do not let span work be mistaken for fixing them. +- GAP-6 (`VALIDATE_TRACES_SCRIPT` dead stringified validator and its lost OTLP-endpoint assertion) + is adjacent tooling debt, not this issue. + +## Docs/consumer proof + +`docs/site/observability/telemetry.md:31-32,82` currently describes a navigable saga sub-tree that +does not exist; after this work the caption becomes true and is proven by the gate rather than +softened. `packages/plugin-sagas-core/README.md` — which today contains no mention of telemetry, +tracing, spans or observability — documents the six span names. Consumer proof: on a scaffolded +project, force a saga compensation and follow `request → command → saga → compensation` in the +Aspire dashboard in one trace, with no log reading. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Zero-caller counts and +`SagaCompensatorOptions` shape re-verified against worktree baseline `fac9e339042c`. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-05-documented-caveat-defects.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-05-documented-caveat-defects.md new file mode 100644 index 0000000000..3c204d3e08 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-05-documented-caveat-defects.md @@ -0,0 +1,112 @@ +# fix(plugins): two defects shipped as documented caveats — WORKER_CONCURRENCY never reaches the runtime, and @netscript/plugin-streams root exports always throw — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T4-05 · **Proposed milestone:** 0.0.8 (new — "Runtime truth + service slice") · +**Labels:** `type:fix` `area:plugins` `area:docs` `area:aspire` `priority:p2` `status:triage` · +**Depends on:** none + +## Summary + +Two live defects were resolved by writing prose that documents the broken behavior instead of fixing +it. (1) Aspire metadata and the generated `.env` emit `WORKER_CONCURRENCY`; the workers runtime reads +`WORKERS_CONCURRENCY` and defaults to `1`, so the declared concurrency of `2` is never applied — and +five separate documentation passages tell the reader to work around it. (2) The package a user +installs by name, `@netscript/plugin-streams`, re-exports `defineStreamProducer`/`defineStreamConsumer` +whose runtime operations always throw, with the docs pointing users at a differently-named package +for the API that works. Both are cheap to fix and both currently teach that a documented caveat is a +closed defect. + +## Evidence + +- Corpus: `research/repo-audit/runtime-plugins.md` §3.3, §4.3, §8 ledger rows 10-11, and the + cross-cutting observation naming commit `26b851529` ("docs: reframe architectural debt caveats as + design boundaries") as the pattern; `SYNTHESIS.md` §6 (T4 pack, "env-name mismatches" — no + existing owner). + +**(1) Concurrency name mismatch.** + +- Emitters (singular): `plugins/workers/src/aspire/workers-contribution.ts:59` + (`concurrencyEnvVar: 'WORKER_CONCURRENCY'`) and `:72` (`WORKER_CONCURRENCY: '2'`); + `plugins/workers/scaffold.plugin.json:43`; + `packages/cli/src/kernel/adapters/windows/environment/env-file-content.ts:228,235`; + `packages/aspire/tests/_fixtures/appsettings.json:161`. +- Reader (plural): `plugins/workers/bin/runtime.ts:96` and `:140` — + `parseInt(Deno.env.get('WORKERS_CONCURRENCY') ?? '1')`. +- Tests already disagree with the emitters: + `packages/cli/src/kernel/templates/aspire/helpers/tests/generators-background-app_test.ts:162` + asserts `withEnvironment('WORKERS_CONCURRENCY', …)`. +- Documented as a caveat in five places: `docs/site/orchestration-runtime/how-to/deploy.md:202`; + `docs/site/orchestration-runtime/how-to/author-a-plugin.md:137`; + `docs/site/background-processing/workers.md:237`; + `docs/site/background-processing/how-to/tune-worker-runtime.md:210-227`; + `docs/site/tutorials/erp-sync/04-queue-and-cron.md:112-115`. + +**(2) Always-throwing streams root exports.** + +- `plugins/streams/src/public/stream-api.ts:19-38` — `StreamUnsupportedOperationError` and + `unsupportedStreamOperation(...)`; the manifest-layer producer/consumer handles reject or throw it. +- Re-exported from the package root at `plugins/streams/src/public/mod.ts:4-5,13` and `:87-88`. +- Documented rather than fixed at `docs/site/durable-workflows/streams.md:126-133` ("they fail loud, + by design"), redirecting users to `@netscript/plugin-streams-core`. + +## Current surface + +A user who follows the Aspire metadata gets concurrency 1 while the dashboard shows a declared 2; +the only way to get the declared value is to set an env var by hand that no generated artifact +writes. A user who installs `@netscript/plugin-streams` and imports its advertised producer/consumer +helpers gets a compiling program that throws at first use. In both cases the documentation is +accurate about the breakage, which has made the breakage durable. + +## Target contract + +1. **One concurrency name.** The emitted metadata name and the runtime-read name are the same + identifier. Whichever name is chosen, the other is accepted for one deprecation window with a + warning, and a test fails if metadata and runtime ever diverge again. +2. **The five caveat passages are deleted, not reworded.** Each doc location states the single + correct variable with no "but the runtime honors…" clause. +3. **The streams root surface is truthful.** Either the manifest-layer helpers gain a working + implementation over the core runtime, or they are removed from the package root export map and + the package documents that runtime primitives live in `@netscript/plugin-streams-core`. A + compiling import that always throws at runtime is not an acceptable third option. +4. **A general rule is recorded.** The remediation program adopts and cites the rule that a + documented caveat is not a closed defect (`runtime-plugins.md` §8), so this class stops + reappearing. + +## Acceptance + +- [ ] The Aspire-emitted concurrency env name and the workers runtime read name are identical. +- [ ] A test fails if the emitted metadata name and the runtime-read name diverge. +- [ ] The generated `.env` writer emits the canonical name. +- [ ] The declared concurrency value takes effect in a scaffolded project without manual env edits. +- [ ] All five documentation passages state one variable with no workaround clause. +- [ ] `@netscript/plugin-streams` root exports either work at runtime or are removed from the export + map. +- [ ] `docs/site/durable-workflows/streams.md:126-133` matches the shipped export surface. +- [ ] A negative test proves an always-throwing runtime helper cannot be exported from a plugin + package root. +- [ ] An export-drift or doc-lint gate fails if either caveat wording is reintroduced. + +## Boundaries + +- **T4-03** owns the streams durability decision and **#1329** owns the SSE envelope; this issue + touches only the root export map and the concurrency name. +- **T4-06** owns the hardcoded ports in the same contribution files; keep the two changes in + separate PRs so each has an independent verdict. +- **#1093** owns third-party plugin discovery; **#829** owns official plugins shipping compile-able + `./services` entrypoints. Neither is re-filed here. +- **#1278** (type soundness, epic-of-record) owns public-surface type unsoundness generally; the + always-throwing export is a *runtime* honesty defect, referenced there at most as an example. +- Renaming the `@netscript/plugin-streams` package, or merging it with `plugin-streams-core`, is out + of scope — that is a publish-surface change requiring an RFC. + +## Docs/consumer proof + +The five caveat passages disappear from the docs site, and a docs test asserts that the +worked-around variable name no longer appears with workaround phrasing. Consumer proof: a scaffolded +project started from generated metadata runs the declared worker concurrency, verifiable from the +worker telemetry attribute `netscript.worker.concurrency` +(`packages/telemetry/src/attributes/worker.ts:6`) without setting any env var by hand. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. All emitter/reader line +citations and the five documentation locations re-verified against worktree baseline `fac9e339042c`. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-06-prerandomization-port-hardcodes.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-06-prerandomization-port-hardcodes.md new file mode 100644 index 0000000000..ec445530fe --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-06-prerandomization-port-hardcodes.md @@ -0,0 +1,114 @@ +# fix(plugins): sagas/triggers/streams contributions and the generated browser consumer stub still publish pre-randomization ports — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T4-06 · **Proposed milestone:** 0.0.8 (new — "Runtime truth + service slice") · +**Labels:** `type:fix` `area:plugins` `area:aspire` `area:cli` `priority:p2` `status:triage` · +**Depends on:** #979 prerequisite (1) — gates resolve endpoints instead of hardcoding +`127.0.0.1:`; sequenced with T4-01 (publisher fallback) and T4-02 (health URLs) + +## Summary + +Since #1211 the scaffolder allocates listener ports from the IANA dynamic range 49152–65535, but +three of four plugin Aspire contributions still bake `8092`/`8093`/`4437` into the URLs and health +probes they publish, each after correctly allocating a port through `ctx.port(...)`. The generated +browser consumer stub does the same: it passes a literal `http://localhost:4437` as `baseUrl`, which +bypasses the stream URL resolver's throw-on-missing behavior entirely. These are currently masked +because the declaration seam is unread in production — which means fixing the seam without fixing +these values would ship broken health checks on day one. + +## Evidence + +- Corpus: `research/repo-audit/runtime-plugins.md` §1.3 (table of hardcoded values), §1.4 (sibling + occurrences), §8 ledger row 9; `SYNTHESIS.md` §6 (T4 pack, "stub port hardcodes ... feeds #979"). +- Allocation range: `packages/cli/src/kernel/domain/scaffold/default-port-allocation.ts:4-7` + (49152–65535, landed by #1211 / `0b11ca47a`). +- Contributions that allocate then publish a literal: + - `plugins/sagas/src/aspire/sagas-contribution.ts:112` allocates via + `ctx.port(SAGAS_API_SERVICE_NAME, SAGAS_API_DEFAULT_PORT)`; `:135` publishes + `SAGAS_API_URL: 'http://localhost:8092'`; `:146` probes `http://localhost:8092/health` with + `_ctx` unused. + - `plugins/triggers/src/aspire/triggers-contribution.ts:139` publishes + `TRIGGERS_API_URL: 'http://localhost:8093'`; `:149` probes `http://localhost:8093/health` with + `_ctx` unused. + - `plugins/streams/src/aspire/streams-contribution.ts:43` publishes + `DURABLE_STREAMS_URL: 'http://localhost:4437'` as a bare string rather than an `EnvSource`; + `:51` probes `http://localhost:4437/health` with `_ctx` unused. + - Correct counter-example: `plugins/workers/src/aspire/workers-contribution.ts:71` + (`{ kind: 'resource', resource: WORKERS_API_RESOURCE, key: 'url' }`) and `:78-84` reading the + allocated port back through `ctx.port(...)`. +- Generated browser code: + `plugins/streams/src/adapter/resources/consumer/consumer.stub.ts:42` — + `url: buildStreamUrl('%%STREAM_PATH%%', options.baseUrl ?? 'http://localhost:4437')`. Because the + literal is passed as `baseUrl`, the resolver at + `packages/plugin-streams-core/src/application/stream-url-resolver.ts:99-133` — which is built to + throw a diagnostic error rather than guess — never fires. +- Sibling literals in shipped (non-test) code: + `plugins/sagas/src/cli/adapters/runtime-api-client.ts:27`, + `plugins/workers/src/cli/adapters/runtime-api-client.ts:27`, + `plugins/{sagas,workers,triggers,auth}/streams/factory.ts` (`baseUrl ?? 'http://localhost:4437'`), + `plugins/{sagas,workers,streams}/src/e2e/probes/probe-context.ts`, + `plugins/workers/test-api.ts:13`, `plugins/triggers/tests/e2e/webhooks_helpers.ts:3`. +- Owner for the prerequisite work: **#979** (unmilestoned, p2) plus **#980**. + +## Current surface + +Each contribution allocates through `ctx.port(name, DEFAULT)` inside `contribute()` and then +publishes a URL that assumes the fallback was taken. Two workspaces that both install plugins would +also collide on the fixed ports, which is #979's framing. The blast radius is currently limited only +because `composeAppHost` never runs in production +(`packages/aspire/src/application/compose-apphost.ts:47`) and the declarations are unread — an +accidental safety net, not a design. + +## Target contract + +1. **Nothing publishes a port it did not allocate.** Every env value and health URL a contribution + declares is derived from the resource endpoint or from the same `ctx.port(...)` call used at + registration — `workers-contribution.ts` is the reference shape. +2. **Streams publishes an `EnvSource`, not a string.** `DURABLE_STREAMS_URL` becomes a + `{ kind: 'resource' }` source like every other resource URL. +3. **The generated consumer stub resolves, never guesses.** The emitted browser module calls the + stream URL resolver and surfaces its diagnostic error; the literal default is removed so a + missing endpoint fails loudly in the browser path exactly as it does on the server path. +4. **Fixed-port literals in shipped CLI clients and probes are resolved or explicitly named as + dev-only defaults** with a single documented constant, not scattered magic numbers. + +## Acceptance + +- [ ] No plugin contribution publishes an env URL or health URL containing a literal port. +- [ ] `DURABLE_STREAMS_URL` is declared as a resource-derived `EnvSource`. +- [ ] The generated consumer stub resolves the streams URL and propagates the resolver's diagnostic + error instead of defaulting to `localhost:4437`. +- [ ] Fixed-port literals remaining in shipped CLI clients and probes resolve from one documented + constant or from service discovery. +- [ ] A negative test fails if any plugin contribution reintroduces a literal port in a declared env + or health value. +- [ ] A negative test proves the generated browser consumer errors diagnostically when no stream + endpoint is configured. +- [ ] A scaffolded project on randomized ports resolves every plugin endpoint with no manual + configuration. + +## Boundaries + +- **#979 owns the prerequisites and must land first**: the `scaffold.runtime` gates must resolve each + plugin resource endpoint from Aspire instead of live-probing `127.0.0.1:8091–8094` (including the + `--allow-net` grant passed to the generated project), and ~20 tutorial/explanation passages under + `docs/site/**` must stop `curl`-ing those ports. **Do not re-file that work here**, and do not drop + `Port` from scaffolder-written plugin entries in this issue — that is #979's "Then" step. **#980** + owns the same defect for `netscript service add`. +- **T4-01** owns the sagas publisher's `127.0.0.1:8092` fallback (a runtime hot path, p0); this issue + owns the declared contribution values and the generated consumer stub. +- **T4-02** owns the child health contract; it consumes correct URLs from this issue. +- **T4-05** owns the concurrency env-name mismatch in the same files; separate PRs. +- The dead `declareEnv`/`declareHealthChecks` seam question (whether `composeAppHost` becomes the + production path or is deleted) is decided in **T4-02**, not here. + +## Docs/consumer proof + +Docs stop teaching fixed plugin ports (coordinated with #979's docs leg) and teach reading the +endpoint the dashboard reports. Consumer proof: two scaffolded projects run simultaneously with +plugins installed and neither collides nor requires a manual port edit — the concrete symptom #979 +records. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. All literals and their +line numbers re-verified against worktree baseline `fac9e339042c`. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-07-verify-background-service-references.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-07-verify-background-service-references.md new file mode 100644 index 0000000000..b183ee768e --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-07-verify-background-service-references.md @@ -0,0 +1,111 @@ +# verify(aspire): re-test the wave-6 claim that BackgroundProcessors.*.ServiceReferences is parsed but never injected — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T4-07 · **Proposed milestone:** 0.0.8 (new — "Runtime truth + service slice") · +**Labels:** `type:test` `area:aspire` `area:cli` `priority:p1` `status:triage` · +**Depends on:** none (verify-first; converts to a fix or to a regression test depending on the +repro) + +## Summary + +A wave-6 run reported that `BackgroundProcessors..ServiceReferences` is parsed from +appsettings but never injected into the background resource, so a background child cannot discover +the services it declares a dependency on. The board sweep found **no owner issue** for this claim. +Re-reading the generator at baseline `fac9e339042c`, the injection **does exist** — the emitted +AppHost writes `services____http__0` for every declared reference, and the registration order +places background processors after services and plugins. This issue therefore opens as a +verification, not a fix: reproduce or refute the claim on the current published canary before any +implementation is scheduled. + +## Evidence + +- Corpus: `SYNTHESIS.md` §3.1 — the `ServiceReferences`-parsed-but-never-injected row is adjudicated + a framework-generation defect *class* but flagged "no board owner found by the board sweep → new + issue draft, **flagged verify-on-current-canary first**"; `research/preplan-package.md` + §Verify-before-filing (the run's standing rule that unverified wave observations are repro'd, not + filed as defects); `research/repo-audit/runtime-plugins.md` §1.5 (the discovery-key asymmetry that + is the most plausible surviving mechanism). +- **Counter-evidence at baseline (this is the load-bearing finding):** + - `packages/cli/src/kernel/templates/aspire/helpers/register/generate-register-background.ts:178-197` + — for every entry in `entry.ServiceReferences`, the generator emits + `const Endpoint = await _services.get('')?.getEndpoint('http');` followed by + `await .withEnvironment('services____http__0', Endpoint);`. `:199-217` does the + same for `PluginReferences` against `_plugins`. + - `packages/cli/src/kernel/assets/generated/aspire/helpers/generate-index-1.ts.template:56,59,62,65` + — registration order is `registerServices` → `registerPlugins` → `wireServiceReferences` → + `registerBackgroundProcessors`, so the `services`/`plugins` maps are populated before background + registration reads them. + - `packages/cli/src/kernel/adapters/plugin/appsettings-entry-builders.ts:77-79` — the background + entry only carries `ServiceReferences` when `options.serviceReferences` is non-empty. + - Parsing exists as claimed: + `packages/cli/src/kernel/adapters/config/deploy-config-background.ts:117` + (`serviceReferences: raw?.ServiceReferences ?? []`), + `packages/cli/src/kernel/adapters/config/deploy-config-types.ts:30,87`. +- **Most plausible surviving mechanism if the symptom reproduces:** the emitted key uses the raw + resource name (`services__sagas-api__http__0`) while the browser-side builder normalizes every + non-alphanumeric character to `_` (`packages/aspire/src/application/build-vite-env-var-name.ts:50-66`, + `workers-api` → `workers_api`). Consumers read via + `packages/sdk/src/discovery/service-url.ts:55-61`, which also uses the raw name. If Aspire itself + exports the normalized form for a hyphenated resource, the injected key and the read key differ + and the consumer silently falls through to a fixed-port default. +- Related consumer of that fallthrough: `plugins/sagas/src/runtime/saga-publisher.ts:295-307` + (owned by T4-01). + +## Current surface + +At baseline the generator, the appsettings writer, the parser and the registration order all appear +correct. The wave-6 observation was made against a published canary on a generated project, not +against this source, so either (a) the observation predates a fix, (b) the symptom was a +name-normalization mismatch misattributed to missing injection, or (c) a path exists in which +`serviceReferences` is never written onto the background entry (for example a plugin install that +does not pass them). Nothing on the board distinguishes these. + +## Target contract + +The repository must be able to answer "does a background child receive its declared service +endpoints?" mechanically rather than by re-reading generator source. Concretely: a scaffolded +project with a background processor declaring `ServiceReferences` starts with the corresponding +`services__*` env vars present in the child's environment, the consumer-side resolver finds them +under the exact key it reads, and a regression test pins both halves — the emitted key and the read +key — so the two can never drift apart silently again. + +## Acceptance + +- [ ] Reproduce or refute the claim on the current published canary, attaching the project's + `appsettings.json` and the generated `register-background.mts`. +- [ ] Record the exact env keys present in a running background child's environment for a declared + service reference. +- [ ] Record what Aspire exports for a hyphenated resource name (raw vs underscore-normalized). +- [ ] If refuted, land a regression test pinning the emitted key and the consumer-read key, and + close the row as not-reproducible with the evidence attached. +- [ ] If reproduced, name the failing mechanism at file:line before any fix is proposed. +- [ ] A negative test proves a background child whose declared service reference is unresolvable + fails or degrades visibly rather than starting with a missing env var. +- [ ] The result is recorded on the roadmap's verify-first ledger either way. + +## Boundaries + +- **T4-01** owns the sagas publisher fallback and the server/browser discovery-key normalization + decision; if this verification confirms the normalization mismatch, the fix belongs there, and + this issue closes with the evidence rather than duplicating it. +- **#1325** owns the triggers background runtime crash-loop; **T4-02** owns child health. A missing + service reference is a distinct symptom from a dead child. +- **#979 / #980** own host-port pinning. Not this issue. +- **#1343** owns the installed-consumer smoke; use it as the repro vehicle, do not re-file it. +- **#511 / #529** (process-manager deploy-target wiring) touch adjacent config keys for bare-metal + targets; out of scope. +- This issue must not be converted into an implementation issue until its first acceptance box is + satisfied. A wave-era observation is not a defect until it is reproduced on shipped artifacts. + +## Docs/consumer proof + +Whichever way the verification lands, `docs/site/orchestration-runtime/**` gains one accurate +statement of how a background processor discovers a service it references, with the env key spelled +exactly as emitted. Consumer proof: an unfamiliar agent can confirm the wiring from the generated +`register-background.mts` plus one dashboard env inspection, without reading `packages/cli` source. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. The counter-evidence above +was produced by reading the generator at worktree baseline `fac9e339042c` during this drafting pass; +it contradicts the wave-6 R2 claim as stated and is the reason this draft is verify-first rather +than a defect. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-08-e2e-child-and-status-truth-gates.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-08-e2e-child-and-status-truth-gates.md new file mode 100644 index 0000000000..d58342ad23 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T4-08-e2e-child-and-status-truth-gates.md @@ -0,0 +1,123 @@ +# test(e2e): the merge-readiness gate probes API health only — background children and streams go unverified, and a saga stuck compensating is reported COMPENSATED — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T4-08 · **Proposed milestone:** 0.0.8 (new — "Runtime truth + service slice") · +**Labels:** `type:test` `area:tooling` `area:plugins` `area:telemetry` `priority:p1` +`status:triage` `gate:e2e` · **Depends on:** T4-02 (the child liveness contract this gate asserts), +T4-06 (endpoint resolution), #979 prerequisite (1) + +## Summary + +`scaffold.runtime` — the command whose verdict decides merge readiness — probes exactly six API +endpoints and never looks at a background child or at streams. That blind spot is the direct reason +#1325 (a trigger processor that crash-loops on every start) could ship with a fully green health +surface. The same "green wrapper over a false state" pattern exists inside the saga engine: there is +no `'compensated'` status, `'compensating'` is counted terminal, and telemetry maps `'compensating'` +to the `COMPENSATED` outcome — so an instance whose compensation merely *started* is +indistinguishable from one that finished and from one that failed. This issue makes the gate see the +children and makes the terminal status tell the truth. + +## Evidence + +- Corpus: `research/repo-audit/runtime-plugins.md` §5.2 ("This is precisely why #1325 shipped"), + §6.5, §8 ledger row 1; `research/repo-audit/observability-aspire.md` GAP-3; + `SYNTHESIS.md` §2 "Harness/evaluation failure", §6 (T4 pack, "E2E child/span gates" — no existing + owner). +- `.llm/tools/e2e/scaffold-e2e-test.ts:1238-1277` — `#exerciseApis()` probes, in order: workers + `/health`, sagas `/health/live`, sagas `/health/ready`, triggers `/health`, auth `/health/live`, + auth `/health/ready`, auth session. **No background resource appears.** Called once from `:701`. +- Streams appears exactly once in that file — `:843`, as a plugin to add + (`{ id: 'plugin-add-streams', kind: 'stream', name: 'streams' }`). It is never health-probed. +- Children that exist and are unprobed: `workers-combined` + (`plugins/workers/src/aspire/workers-contribution.ts:55-61`), `trigger-processor` + (`plugins/triggers/src/aspire/triggers-contribution.ts`, registered before `:131`), and — the one + exception — the sagas runner, which the generated AppHost does probe via a name special case + (`packages/cli/src/kernel/templates/aspire/helpers/register/generate-register-background.ts:79-86`). +- Status-truth defect: + - `packages/plugin-sagas-core/src/domain/constants.ts:18-25` — + `SAGA_INSTANCE_STATUSES = ['pending','running','completed','failed','compensating','cancelled']`; + there is **no** `'compensated'`. + - `packages/plugin-sagas-core/src/runtime/saga-engine.ts:476-478` — any `compensate` cascade writes + `'compensating'`. + - `:489-491` — `isTerminalStatus()` counts `'compensating'` as terminal. + - `:494-497` — `telemetryOutcomeFromStatus()` maps `'compensating'` → `SagaTelemetryOutcomes.COMPENSATED`. + - Consumed at `:283` and `:298`. +- Owner issues in the failure chain: #1325 (the defect this blindness let ship), #1326/#1329 + (streams), #979 (gate port-resolution prerequisites), #1343 (installed-consumer smoke). + +## Current surface + +The merge-readiness verdict answers "did four API processes answer HTTP?" and is read as "the +generated runtime works". Background children, which are where the durable work actually happens, +contribute nothing to it; streams contributes nothing at all. On the saga side, a persisted status +of `'compensating'` is simultaneously (a) treated as final, (b) reported to telemetry as a completed +compensation, and (c) the only state available for an in-flight or failed compensation — three +different truths behind one string, none of them checked by a gate. + +## Target contract + +1. **Every first-party background child is probed by the gate.** The `scaffold.runtime` suite + installs each KV-backed first-party background runtime and proves each reaches a real ready state + as defined by T4-02's child contract — not merely that its process was spawned. +2. **Streams is health-probed.** The streams resource participates in the gate with the same + readiness bar as the other plugins. +3. **The gate is endpoint-resolving.** Probes resolve each resource endpoint from Aspire rather than + from fixed `127.0.0.1:` addresses (#979 prerequisite), so the gate keeps working after + T4-06. +4. **Saga terminal status is honest.** `'compensated'` is added to the status union; `'compensating'` + becomes non-terminal; a failed compensation is distinguishable from a completed one; and + telemetry reports `COMPENSATED` only for a compensation that finished. Persisted instances in the + old encoding are migrated or read-compatible, with the strategy stated. +5. **The gate fails when the seam is removed.** Each new assertion has a paired negative test, per + the run's rule that a green wrapper is not proof. + +## Acceptance + +- [ ] The `scaffold.runtime` suite probes every first-party background child for real readiness. +- [ ] The streams resource is health-probed by the suite. +- [ ] Gate probes resolve endpoints from Aspire rather than from hardcoded `127.0.0.1:` + addresses. +- [ ] `'compensated'` exists in the saga instance status union and `'compensating'` is no longer + terminal. +- [ ] Telemetry reports `COMPENSATED` only for a compensation that completed. +- [ ] A failed compensation is distinguishable from a completed one in persisted state and in + telemetry. +- [ ] Persisted instances written under the old status encoding are migrated or read-compatibly + handled. +- [ ] A negative test proves a deliberately crash-looping background child turns the suite red. +- [ ] A negative test proves removing the streams probe turns the suite red. +- [ ] A negative test proves an instance stuck in compensation is not reported as compensated. +- [ ] `gate:` `deno task e2e:cli run scaffold.runtime --cleanup` passes with the new assertions on a + clean scaffold. + +## Boundaries + +- **T4-04 owns the saga span assertions** (`saga.handle` → `saga.cascade.compensate` parent edge and + the shared correlation id) in `validate-flow-b-traces.ts`. Do not duplicate them here; this issue + covers child/streams readiness and the persisted-status truth. +- **#1325 owns the triggers KV-adapter fix.** This issue builds the detector that would have caught + it; do not fix or close #1325 from here, and do not treat a green new gate as evidence #1325 is + fixed. +- **T4-02 defines the child state vocabulary**; this issue asserts it. If T4-02 slips, the gate + asserts process-plus-route readiness as an interim bar and says so. +- **#979 / #980** own removing pinned host ports and the docs `curl` passages; this issue only + consumes #979's prerequisite (1). +- **#1343** owns the installed-consumer smoke against a published canary; **#1163** owns the + milestone-run verification; **#542 / #910 / #426** are other epics' E2E gates. None are re-filed. +- **#1280** (blocked) covers backing-service health; this gate does not attempt to probe Postgres, + Redis or Deno KV. +- Deleting `VALIDATE_TRACES_SCRIPT` and recovering its lost OTLP-endpoint assertion is adjacent + tooling debt, not in scope. + +## Docs/consumer proof + +`AGENTS.md` and the CLI E2E documentation describe the suite's coverage accurately once children and +streams are included, so "the `scaffold.runtime` verdict" stops overstating what it proves. Saga +status vocabulary is documented in `docs/site/durable-workflows/sagas.md` with the distinction +between requested, in-flight, completed and failed compensation. Consumer proof: an operator reading +the saga instance list can tell a finished compensation from a stuck one without opening traces. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Probe list, streams +single-occurrence count and the `saga-engine.ts` status/telemetry line numbers re-verified against +worktree baseline `fac9e339042c`. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T7-01-wave7-measured-adoption-smoke.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T7-01-wave7-measured-adoption-smoke.md new file mode 100644 index 0000000000..e85414d15a --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/T7-01-wave7-measured-adoption-smoke.md @@ -0,0 +1,66 @@ +# verify(0.0.8): Wave-7 measured adoption smoke — arms A/B/C prove the generated path changes agent behavior — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** T7-01 · **Proposed milestone:** 0.0.8 (exit gate) · **Labels:** `type:test` +`area:agentic` `area:cli` `priority:p1` `status:triage` · **Depends on:** T2-01, T2-02, T4-01, +TA-01 (the surfaces under measurement), #1197 (measurement method), #1090 (observational boxes) + +## Summary + +The remediation program's thesis — generation makes the idiomatic path the easiest path — is +falsifiable exactly once the 0.0.7 generators and 0.0.8 runtime-truth surfaces exist in a +published canary. This issue owns the one measured unfamiliar-agent smoke that tests it, as the +exit gate of 0.0.8. It consumes, and must not duplicate, the existing measurement chain +(#1102/#1201 capability, #1197 re-measurement, #1090 observational boxes). + +## Evidence + +- Six consecutive measured runs with zero MCP/doctor/otel adoption (#1197 body; corpus + `research/wave-5-6-plans.md` #17). +- Wave-6 R3's only-GO run is confounded by the supervisor-enforced init gate, model, and canary + (`research/wave-6-runs.md` #7) — the strongest untested causal lever in the corpus. +- Full design: `fable-5-remediation-plan/WAVE7-AND-AGENT-ADOPTION.md` (this run). + +## Current surface + +Wave-6 harness (brief v3 + PLAN-WAVE6): capability-map rows, executed-command census, +contamination rules. No arm structure; no generated-slice measurement rows (the verbs do not +exist yet); wrapper-exit gates only. + +## Target contract + +Three arms on identical brief/budget: A = post-0.0.8 canary, no init enforcement; B = post-0.0.8, +R3-style enforced init gate; C = pre-remediation 0.0.6 canary (control). Two runs per arm, +different frontier models, blind gap-audit scoring on the Wave-6 rubric. New measured rows: +generated-slice verb usage (used / rejected-with-reason / silent = harness failure), consumer +no-`any` gate verdict, runtime-truth probes (receipt handling, child liveness, stream restart) +read from persisted state + one correlated trace. + +## Acceptance + +- [ ] Arm design (A/B/C, 2 runs each) executed on published canaries with pinned versions recorded +- [ ] Generated-slice usage measured per run: used or explicitly rejected; zero silent rows +- [ ] Consumer-side type-escape gate executed against every product (not framework exports) +- [ ] Runtime-truth rows proved from persisted state + correlated OTEL, not wrapper exits +- [ ] Arm A vs C delta reported on frontend-composition and runtime-truth capability rows +- [ ] Negative path: a run that skips a generated verb without recording rejection is scored as a + harness failure, not silently excused +- [ ] Verdict + confirmed residual gaps filed as issues (not folded into harness thickening) +- [ ] [post-merge] The verdict comment on this issue names the canary versions and links raw + measurement artifacts + +## Boundaries + +#1102/#1201 own MCP retrieval/corpus capability; #1197 owns the re-measurement method and +extraction script; #1090 owns the wave-five observational boxes (this issue may satisfy them and +says so per box, but does not absorb them). Harness texts live in `.llm/harness/` + the owner's +wave folders, not on the board. This issue does not script product choices. + +## Docs/consumer proof + +The published verdict is the program's go/no-go for advancing the train past remediation +(`MILESTONE-TRAIN.md` §5). A GO here is the evidence base for any future "0.1.0" claim. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Supervisor-authored +(Fable 5 · high); design in `WAVE7-AND-AGENT-ADOPTION.md`. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-01-scaffold-protects-api-by-default.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-01-scaffold-protects-api-by-default.md new file mode 100644 index 0000000000..ca179ee987 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-01-scaffold-protects-api-by-default.md @@ -0,0 +1,100 @@ +# fix(scaffold): the generated service ships `/api` unprotected and a framework test codifies public API routes as correct — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** TA-01 · **Proposed milestone:** 0.0.8 (post-shift "Runtime truth + service slice", SYNTHESIS §5.3) · **Labels:** `type:fix` `area:cli` `area:service` `area:auth` `priority:p0` `status:triage` · **Depends on:** TA-02 (a plugin-composable authenticator the generated service can use), T1-05 (SDK client-side credential seam) + +## Summary + +`netscript service add` emits a `defineService(...)` call with no `auth` key, so every scaffolded +service serves `/api/rpc/*` and `/api/*` to any unauthenticated caller. The framework does not +treat this as a defect: `packages/service/tests/auth/define-service-auth_test.ts` contains +`Deno.test('defineService without auth leaves api routes public')`, which asserts `200` on +`/api/openapi.json` and therefore locks the insecure default in place as a regression test. A +product-grade meta-framework must not generate an open API surface by default, and must not carry a +test that fails when the default is fixed. + +## Evidence + +- Corpus: `research/repo-audit/auth.md` §0 (in-repo reproduction of "left RPC unauthenticated"), + §3.2, gap **G3**. +- `packages/cli/src/kernel/assets/service/main.ts.template:14-23` — `defineService(router, { name, + version, port, db, openapi, debug })`; no `auth`, no commented stub, no TODO. +- `packages/service/tests/auth/define-service-auth_test.ts:11-23` — the test that codifies the + public default; the adjacent test at `:25` proves `auth` works when supplied. +- `packages/service/src/presets/define-service.ts:268-273` — `if (options.auth) { builder.withAuthn(...) }`; + auth is opt-in and silently absent otherwise. +- `packages/service/src/auth/auth-middleware.ts:19,22` — `DEFAULT_PROTECTED_PREFIXES = ['/api']`, + `DEFAULT_ANONYMOUS_PREFIXES = ['/health']`; the guard already covers exactly the right surface + when it is enabled. +- `grep -rl auth packages/cli/src/kernel/assets/` returns no asset under `assets/service/` or + `assets/app/routes/` — no signin route, no `_middleware.ts`, no session helper is generated + (`auth.md` §3.2). + +## Current surface + +A generated workspace produces: a service whose entire `/api` tree is anonymous; an app client +(`packages/cli/src/kernel/assets/app/lib/example-service.ts.template:16-20`) that cannot send a +credential even if the service were guarded (`auth.md` §2, gap G1); and a `packages/service` test +suite that asserts the anonymous behaviour is correct. The server-side guard +(`createAuthnMiddleware` / `createAuthzMiddleware` / `createScopeAuthorizer`, all fail-closed — +`auth.md` §1.1) is complete and unused on the generated path. + +## Target contract + +1. `defineService` treats a missing `auth` option as an **error at build time**, not as "public". + Public exposure requires an explicit, greppable opt-out — `auth: 'public'` (or + `auth: { public: true, reason: string }`) — recorded in the generated file so an auditor can find + every unguarded service with one grep. +2. `netscript service add` emits the opt-in form by default: an authenticator wired from + `@netscript/plugin-auth`'s remote session-verifying `AuthenticatorPort` (TA-02) plus a + `createScopeAuthorizer` rule set covering the generated router's routes, with `/health` left + anonymous by the existing defaults. +3. `packages/service/tests/auth/define-service-auth_test.ts:11-23` is **replaced**, not deleted: the + new test asserts that an unconfigured `defineService` refuses to start (or starts with `/api` + returning 401), and a separate test asserts that the explicit opt-out restores the anonymous + surface. +4. The generated app's client attaches the credential through the T1-05 seam; the scaffolded call + path is authenticated end to end without a hand-written `fetch`. +5. Migration: the change is breaking for existing generated projects. Ship a codemod note plus a + startup error message that names the exact opt-out to add. + +## Acceptance + +- [ ] `defineService` without an `auth` key fails to start with an actionable error naming the opt-out. +- [ ] An explicit public opt-out is required, is present in generated source, and is greppable. +- [ ] `netscript service add` emits a guarded service that boots and answers `/health` anonymously. +- [ ] The generated app calls its own guarded service successfully with no hand-written `fetch`. +- [ ] Negative test: an unauthenticated `GET /api/openapi.json` on a generated service returns 401. +- [ ] Negative test: an unauthenticated `POST /api/rpc/*` procedure call on a generated service returns 401. +- [ ] Negative test: a request with a valid credential but an insufficient scope returns 403. +- [ ] `defineService without auth leaves api routes public` is replaced by a test asserting the new default. +- [ ] gate: `deno task e2e:cli run scaffold.runtime --cleanup` green with the guarded default. +- [ ] The breaking change is recorded in the release notes with the exact opt-out snippet. + +## Boundaries + +- **Do not** implement the SDK credential seam here — **T1-05** owns `CreateServiceClientOptions` / + `ServiceClientContext` headers and per-call credential override (gap G1). This issue consumes it. +- **Do not** implement `PluginServiceConfig.auth` or the remote authenticator here — **TA-02** owns + both. +- **Do not** design organization-aware policy contracts — **#884** owns them. +- **Do not** build the auth conformance/mocking test kit — **#885** owns it; this issue adds only + the gates listed above. +- **Do not** add auth UI, signin pages, or a session widget — **#942** owns the auth v1 frontend. +- **Do not** change the plugin frontend procedure gateway — **#934** owns it. +- **Do not** re-file scaffold-conformance inventory work — **#1335** is the umbrella; **#1333** owns + frontend scaffold modernization. + +## Docs/consumer proof + +`docs/site/tutorials/workspace/05-route-authz.md:248-258` currently demonstrates an authenticated +call only with `curl -H 'authorization: Bearer read'` because no typed-client example can be +written (`auth.md` §2). Adoption is proven when that tutorial shows the generated client making an +authenticated call, and when `docs/site/reference/sdk/index.md` — which today contains zero +credential guidance — documents the opt-out and the guarded default. A generated project checked out +fresh must show the `auth` option in `services//src/main.ts` without the user editing anything. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Sourced from +`research/repo-audit/auth.md` gaps G3/G1; all cited line numbers re-verified against worktree +`fac9e339042c` on 2026-08-08. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-02-plugin-services-unguardable.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-02-plugin-services-unguardable.md new file mode 100644 index 0000000000..3c8fdbedaa --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-02-plugin-services-unguardable.md @@ -0,0 +1,110 @@ +# fix(plugin): `createPluginService` has no auth seam, so every first-party plugin API is unguardable by construction — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** TA-02 · **Proposed milestone:** 0.0.8 (post-shift "Runtime truth + service slice", SYNTHESIS §5.3) · **Labels:** `type:fix` `area:plugins` `area:auth` `area:service` `priority:p0` `status:triage` · **Depends on:** none (TA-04 improves the handler-side ergonomics but does not block) + +## Summary + +`PluginServiceConfig` exposes 16 knobs — cors, logger, openapi, docs, database, middleware, context, +rpc, health, raw routes, startup/shutdown hooks — and **no auth field**. `createPluginService` never +calls `withAuthn` or `withAuthz`. Consequently the five first-party plugin services (auth, workers, +sagas, streams, triggers) and every plugin produced by `netscript plugin new` publish +`/api/rpc/v1//*` and `/api/v1//*` with no possible guard short of forking the service +entrypoint. The auth plugin is the sharpest case: the service that owns identity is itself +unauthenticated (see TA-03a). + +## Evidence + +- Corpus: `research/repo-audit/auth.md` §3.1, gaps **G2** and **G8**; SYNTHESIS §2 + ("Plugin-composition failure"). +- `packages/plugin/src/service/presentation/create-plugin-service.ts:63-104` — the full + `PluginServiceConfig` interface; no `auth`, no `authn`, no `authz`, no `principal`. +- `packages/plugin/src/service/presentation/create-plugin-service.ts:137-194` — the fixed chain: + `createService → withCors → withLogger → withOpenAPI → withDocs → withDatabase → use(middleware) + → withContext → withRPC → withHealth → withServiceInfo → route → onStartup → onShutdown`. No auth + stage exists. +- `grep -rn 'withAuthn\|withAuthz' packages plugins` outside `packages/service/` returns **zero** + matches — no plugin, and no scaffolded service, calls the guard. +- Callers proven unguarded today: `plugins/auth/services/src/main.ts:70-84`, + `plugins/workers/services/src/main.ts`, `plugins/sagas/services/src/main.ts`, + `plugins/streams/services/src/main.ts`, `plugins/triggers/services/src/main.ts`, plus + `packages/cli/src/public/features/plugins/new/new-plugin-use-case.ts` (the generator for + third-party plugins). +- The guard that is missing already exists and is correct: `packages/service/src/auth/auth-middleware.ts:19,22` + (`DEFAULT_PROTECTED_PREFIXES = ['/api']`, `DEFAULT_ANONYMOUS_PREFIXES = ['/health']`), + fail-closed authz (`packages/service/src/auth/scope-authorizer.ts:41`). +- No remote session-verifying authenticator exists: the only `AuthenticatorPort` implementations are + `static-credential`, `trusted-header`, `kv-oauth`, `workos`, `better-auth` (`auth.md` §4.5), so a + service can only validate the auth plugin's sessions by embedding the backend, its KV/DB handles + and its provider secrets in-process. + +## Current surface + +A plugin author has exactly two options: publish an open API, or abandon `createPluginService` and +hand-assemble a `ServiceBuilder` — which discards the "mandated builder order" the factory exists to +enforce (`create-plugin-service.ts:109-112`). `createTrustedHeaderAuthenticator` reads +`x-authenticated-user`/`-scopes`/`-roles` (`packages/service/src/auth/trusted-header-authenticator.ts:20-54`) +but nothing in the repo emits those headers — the receiver exists, the sender does not +(`auth.md` §4.4, gap G14). + +## Target contract + +1. `PluginServiceConfig` gains `auth?: PluginServiceAuthConfig` carrying `{ authn, authz? }` with the + same shape `defineService` already accepts (`packages/service/src/presets/define-service.ts:268-273`), + applied by `createPluginService` **before** `withRPC` so route registration stays inside the guard — + the ordering `packages/service/src/builder/service-builder-impl.ts:435-436` already guarantees + (`installAuth()` at `:442` runs before `installDeferredRoutes()` at `:462`). +2. Deny-by-default posture: omitting `auth` is an explicit decision, not a default. The factory + requires either `auth` or a recorded `auth: 'public'` opt-out, mirroring TA-01 so the framework + has one rule for both generated and plugin services. +3. `@netscript/plugin-auth` exports `createAuthServiceAuthenticator({ serviceName, ... })` — an + `AuthenticatorPort` that verifies a request by calling the auth service's `GET /session` over the + typed client (T1-05 seam), so a guarded service needs no backend, no KV handle and no provider + secret. This is gap G8 and it is what makes the plugin composable at all. +4. The five first-party plugin services adopt the seam; each declares its own scope rules, and + `/health` stays anonymous. +5. `netscript plugin new` generates the guarded form. + +## Acceptance + +- [ ] `PluginServiceConfig.auth` exists and `createPluginService` applies it before `withRPC`. +- [ ] Omitting `auth` requires an explicit recorded public opt-out. +- [ ] `@netscript/plugin-auth` exports a remote session-verifying `AuthenticatorPort`. +- [ ] The remote authenticator needs no backend instance, KV handle, or provider secret. +- [ ] All five first-party plugin services declare an auth configuration or a recorded opt-out. +- [ ] `netscript plugin new` scaffolds a guarded plugin service. +- [ ] Negative test: an unauthenticated `POST /api/rpc/v1//*` on a guarded plugin service returns 401. +- [ ] Negative test: an unauthenticated `GET /api/v1//*` on a guarded plugin service returns 401. +- [ ] Negative test: a valid principal with an insufficient scope returns 403 on the same routes. +- [ ] Negative test: the guard cannot be bypassed by reordering config fields (order is factory-owned). +- [ ] A guard test fails if a future `createPluginService` change registers RPC routes before authn. +- [ ] gate: `deno task e2e:cli run scaffold.runtime --cleanup` green with guarded plugin services. + +## Boundaries + +- **Do not** build the deny-by-default *frontend procedure gateway* — **#934** owns the generated + per-procedure route table at `/api/plugins//`, its CSRF/origin checks and its threat + model. This issue guards the plugin's own service surface; #934 guards the browser-facing + projection of it. Align the deny-by-default vocabulary with #934, do not implement it here. +- **Do not** define organization/tenant-aware authorization contracts — **#884** owns them; the + authorizer added here stays scope/role-based and must remain adaptable to #884's model rather than + competing with it. +- **Do not** build the auth conformance/mocking test kit — **#885** owns it. +- **Do not** fix the auth service's own signout authentication defect — **TA-03a** owns it (this + issue supplies the seam it consumes). +- **Do not** change plugin discovery's hardcoded factory table — **#1093** owns it. +- **Do not** touch the CLI's hardcoded `localhost:4437` auth session URL — **#1243** owns it. + +## Docs/consumer proof + +`docs/site/explanation/plugin-system.md` and `docs/site/identity-access/auth.md` must show a plugin +service declaring `auth` and a non-auth plugin verifying sessions through +`createAuthServiceAuthenticator` without embedding a backend. Adoption is proven when a third-party +plugin fixture (not a first-party one) is guarded end to end in a test, and when +`docs/site/identity-access/how-to/add-authentication.md` can state, with a code reference, how to +protect a plugin API — a sentence it cannot write today. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Sourced from +`research/repo-audit/auth.md` gaps G2/G8/G14; all cited line numbers re-verified against worktree +`fac9e339042c` on 2026-08-08. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-03a-signout-accepts-arbitrary-session-id.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-03a-signout-accepts-arbitrary-session-id.md new file mode 100644 index 0000000000..1e5fd96024 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-03a-signout-accepts-arbitrary-session-id.md @@ -0,0 +1,91 @@ +# fix(auth): `POST /api/v1/auth/signout` revokes any session id an unauthenticated caller supplies — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** TA-03a · **Proposed milestone:** 0.0.8 (post-shift "Runtime truth + service slice", SYNTHESIS §5.3) · **Labels:** `type:fix` `area:auth` `area:plugins` `priority:p0` `status:triage` · **Depends on:** TA-02 (the auth service must be guardable before signout can require a principal) + +## Summary + +The auth plugin's `signout` handler takes `sessionId` from the **request body**, resolves the +backend, and calls `revokeSession(sessionId)` with no authentication and no ownership check. Any +caller who learns or guesses a session id can terminate that session; the auth service accepts the +request because `createPluginService` cannot guard it at all (TA-02). NetScript's own CLI exercises +this path with a raw `fetch` carrying no credential, which is how the hole stayed invisible. + +## Evidence + +- Corpus: `research/repo-audit/auth.md` §3.1, gap **G6**. +- `plugins/auth/services/src/routers/v1-handlers.ts:189-234` — `export async function signout(input: + SignoutInput, context: AuthServiceContext)`; `const sessionId = input.sessionId ?? await + backend.interactive?.getSessionId(...)`, then `await backend.sessions.revokeSession(sessionId)`. + There is no principal check anywhere in the function. +- `packages/plugin-auth-core/src/contracts/v1/auth.contract.ts:56-61` — `SignoutInput = Readonly<{ + sessionId?: string; everywhere?: boolean; redirectTo?: string }>`; the contract itself invites a + caller-supplied session id. +- `plugins/auth/services/src/main.ts:70-84` — the service is built by `createPluginService` with no + auth configuration, so no middleware ever populates a principal (TA-02 evidence). +- `packages/cli/src/public/features/plugins/auth/auth-session-client.ts:7-23` — first-party caller + using raw `fetch` with no credential (`auth.md` §0). +- The `everywhere` flag in the same input widens the blast radius: a global logout is reachable on + the same unauthenticated route. + +## Current surface + +`signout` has two modes. With `input.sessionId` present it revokes that id directly. With it absent +it falls back to `backend.interactive?.getSessionId(request)`, i.e. the cookie — the only +identity-bearing path, and it is the *optional* one. The handler emits a `session.revoked` audit +event and telemetry for the revocation it just performed, so the audit trail records a successful +revocation with no authenticated actor. + +## Target contract + +1. `signout` requires an authenticated principal. With TA-02's seam applied to + `plugins/auth/services/src/main.ts`, `/api/v1/auth/signout` and `/api/rpc/v1/auth/signout` are + inside `DEFAULT_PROTECTED_PREFIXES`. +2. Session ownership is enforced in the handler, not only at the middleware: the resolved session's + subject must equal the principal's subject, otherwise the call fails with the contract's + `UNAUTHORIZED` error and **no** revocation and **no** success audit record. +3. `everywhere: true` revokes only sessions belonging to the authenticated subject. +4. Caller-supplied `sessionId` is either removed from `SignoutInput` (preferred — the session comes + from the credential) or retained solely as a same-subject selector for multi-session sign-out. + Whichever is chosen, a foreign id can never be revoked. Removing the field is a contract-breaking + change and must be released as such. +5. The failure path is indistinguishable to the caller between "session does not exist" and "session + belongs to someone else" (no enumeration oracle). + +## Acceptance + +- [ ] `signout` rejects requests that carry no authenticated principal. +- [ ] `signout` rejects a `sessionId` whose subject differs from the principal's subject. +- [ ] `everywhere: true` revokes only sessions owned by the authenticated subject. +- [ ] A rejected signout emits no `session.revoked` audit event and no success telemetry. +- [ ] Negative test: unauthenticated `POST /api/v1/auth/signout` with a valid foreign `sessionId` returns 401 and the session stays active. +- [ ] Negative test: authenticated `POST /api/v1/auth/signout` with a foreign `sessionId` returns 401/403 and the session stays active. +- [ ] Negative test: `POST /api/rpc/v1/auth/signout` is guarded identically to the REST route. +- [ ] Negative test: unknown and foreign session ids produce the same response shape and status. +- [ ] The CLI's session commands send a credential and no longer rely on the unauthenticated path. +- [ ] Any `SignoutInput` contract change is recorded as breaking with a migration note. + +## Boundaries + +- **Do not** add the `PluginServiceConfig.auth` seam here — **TA-02** owns it; this issue consumes it + and is blocked until it lands. +- **Do not** fix the `Set-Cookie` discard on signin/callback — **TA-03b** owns it. +- **Do not** change the CORS default — **TA-03c** owns it. +- **Do not** rework the CLI's hardcoded `localhost:4437` session-stream URL — **#1243** owns it; + this issue only changes whether the CLI sends a credential. +- **Do not** build the auth conformance/mocking test kit — **#885** owns it. +- **Do not** add org-scoped revocation semantics — **#884** owns organization-aware policy. + +## Docs/consumer proof + +`docs/site/identity-access/how-to/add-authentication.md:243` advertises `POST /api/v1/auth/signout` +as "Revoke the current session and clear the session cookie" — "the current session" is exactly the +guarantee the code does not provide. Adoption is proven when that row is accurate, when the docs show +the authenticated call shape, and when `docs/site/identity-access/auth.md` documents the ownership +rule and the deliberate non-enumeration of foreign session ids. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Sourced from +`research/repo-audit/auth.md` gap G6; all cited line numbers re-verified against worktree +`fac9e339042c` on 2026-08-08. Split from the TA-03 cluster so each session-lifecycle defect carries +its own acceptance and negative tests. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-03b-orpc-signin-callback-discard-set-cookie.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-03b-orpc-signin-callback-discard-set-cookie.md new file mode 100644 index 0000000000..6f224a458c --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-03b-orpc-signin-callback-discard-set-cookie.md @@ -0,0 +1,103 @@ +# fix(auth): oRPC `signin`/`callback` discard the backend `Set-Cookie`, so the interactive browser flow can never establish a session — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** TA-03b · **Proposed milestone:** 0.0.8 (post-shift "Runtime truth + service slice", SYNTHESIS §5.3) · **Labels:** `type:fix` `area:auth` `area:contracts` `area:plugins` `priority:p0` `status:triage` · **Depends on:** none (TA-03c is required for the browser end of the same flow to work) + +## Summary + +`kv-oauth` returns real 302 responses whose `Set-Cookie` headers carry first the OAuth transaction id +and then the session id. Both auth handlers throw those responses away and keep only the `Location` +value. The contract makes this unfixable at the handler level: none of the five auth routes declares +`outputStructure: 'detailed'`, so no procedure in `authContractV1` can emit a response header at all. +The result is that the only interactive backend NetScript ships cannot complete a browser sign-in +through its own published API — the transaction cookie never reaches the browser, so the callback +fails with `oauth_cookie_missing`, and the session cookie never reaches the browser, so `GET /session` +and `GET /me` cannot see it afterwards. + +## Evidence + +- Corpus: `research/repo-audit/auth.md` §4.1, gap **G5**. +- `plugins/auth/services/src/routers/v1-handlers.ts:95-107` — `signin` calls `interactive.signIn(...)` + and keeps only `responseLocation(response)`; the `Response` object is discarded. +- `plugins/auth/services/src/routers/v1-handlers.ts:162-169` — `callback` builds + `{ completed, sessionId, redirectTo, subject }` from `result` and discards `result.response`. +- `plugins/auth/services/src/routers/v1-helpers.ts:82-84` — `responseLocation(response)` returns + `response.headers.get('location')`; there is no `Set-Cookie` reader anywhere in the helpers. +- `packages/plugin-auth-core/src/contracts/v1/auth.contract.ts:437-461` — all five routes are + `baseContract.route({ method, path }).input(...).output(...)`; **no** `outputStructure: 'detailed'` + on any of them. +- `packages/auth-kv-oauth/src/flow.ts:143` (transaction cookie on the signin redirect) and `:220-224` + (session cookie on the callback redirect) — the backend does emit both. +- `packages/auth-kv-oauth/src/flow.ts:155-159` — `handleCallback` throws `oauth_cookie_missing` + unless the txn cookie or an explicit `txn` parameter is present; `v1-handlers.ts:157-160` forwards + only `providerId`, `code` and `state`, never `txn`. +- The inbound half already works: `v1-helpers.ts:40-60` (`toRequest`) forwards inbound headers into + the backend `Request`, and `toAuthnRequest` (`:64-78`) reads the session cookie. The defect is + strictly the response half. +- The server-side write-back path exists and is unused by this surface: + `AuthnResult.setCookies` / `responseHeaders` applied by `applyAuthnResponse` + (`packages/service/src/auth/auth-middleware.ts:166-177`). + +## Current surface + +`signin` returns `{ started, providerId, redirectUrl, state }`; the caller is expected to follow +`redirectUrl` in a browser that never received the transaction cookie. `callback` returns +`{ completed, sessionId, redirectTo, subject }` — the session id is handed to the caller in the JSON +body, which is the only way it can be observed, and which is itself a credential leak into logs and +client state. Docs assert the opposite behaviour +(`docs/site/identity-access/how-to/add-authentication.md:243,273`). + +## Target contract + +1. `signin`, `callback` and `signout` declare `outputStructure: 'detailed'` in + `auth.contract.ts` so their handlers can return headers alongside the body. +2. The handlers propagate the backend response's `Set-Cookie` headers verbatim — including the + `__Host-` prefix, `Path=/`, `Secure`, `HttpOnly` and `SameSite` attributes the backend already + asserts (`packages/auth-kv-oauth/src/cookies.ts:105-121`) — without re-encoding or re-signing them. +3. `callback` forwards the `txn` parameter when present so the documented explicit-transaction path + works for non-cookie callers. +4. The session id stops being returned in the `callback` response body once the cookie path works, + or is retained only behind an explicit non-browser flag; either way the change is recorded as + contract-breaking. +5. `signout` returns the session-clearing `Set-Cookie` the docs already promise. + +## Acceptance + +- [ ] `signin`, `callback` and `signout` declare `outputStructure: 'detailed'`. +- [ ] The transaction `Set-Cookie` from `signIn` reaches the HTTP response unmodified. +- [ ] The session `Set-Cookie` from `handleCallback` reaches the HTTP response unmodified. +- [ ] `signout` emits a session-clearing `Set-Cookie`. +- [ ] `callback` forwards an explicit `txn` parameter to the backend when supplied. +- [ ] Cookie attributes (`__Host-`, `Path=/`, `Secure`, `HttpOnly`, `SameSite`) survive the round trip. +- [ ] Negative test: a `signin` → provider → `callback` round trip without the txn cookie still fails, and fails with `oauth_cookie_missing` rather than silently succeeding. +- [ ] Negative test: `GET /session` after a completed callback resolves the session **from the cookie only**, with no session id in the request body or query. +- [ ] Negative test: a cookie rewritten to drop `Secure` or `HttpOnly` is rejected by the backend's cookie-policy assertion. +- [ ] Tests cover both the REST (`/api/v1/auth/*`) and RPC (`/api/rpc/v1/auth/*`) projections of each route. +- [ ] Any contract change is released as breaking with a migration note. +- [ ] `docs/site/identity-access/how-to/add-authentication.md:243,273` are true against the shipped code. + +## Boundaries + +- **Do not** fix signout authentication/ownership — **TA-03a** owns it. +- **Do not** change the CORS default or decide the browser topology — **TA-03c** owns both; this + issue makes the server emit correct cookies, TA-03c makes a browser able to keep them. +- **Do not** add the SDK client-side credential seam — **T1-05** owns it. +- **Do not** build signin/callback UI — **#942** owns the auth v1 frontend. +- **Do not** re-open multi-backend routing — the single-active-backend boundary is recorded as + accepted debt (`.llm/harness/debt/arch-debt.md:1313`, `auth-single-active-backend-boundary`) and + **#874** owns the routing gap. +- **Do not** widen the contract for organization/tenant selectors — **#884** owns them. + +## Docs/consumer proof + +`docs/site/identity-access/how-to/add-authentication.md:243` ("Revoke the current session and clear +the session cookie") and `:273` ("After completing the browser sign-in, the session cookie is set") +are currently unverifiable claims. Adoption is proven when the documented `curl -c cookies.txt` / +`curl -b cookies.txt` sequence in that file executes end to end against a generated project, and when +the tutorial `docs/site/tutorials/workspace/02-auth.md` no longer needs to pass a session id by hand. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Sourced from +`research/repo-audit/auth.md` gap G5; all cited line numbers re-verified against worktree +`fac9e339042c` on 2026-08-08. Split from the TA-03 cluster so each session-lifecycle defect carries +its own acceptance and negative tests. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-03c-cors-wildcard-blocks-credentialed-calls.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-03c-cors-wildcard-blocks-credentialed-calls.md new file mode 100644 index 0000000000..6e3e1b3fa0 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-03c-cors-wildcard-blocks-credentialed-calls.md @@ -0,0 +1,93 @@ +# fix(service): the default CORS policy is `origin: '*'`, which makes credentialed browser calls impossible on every generated and plugin service — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** TA-03c · **Proposed milestone:** 0.0.8 (post-shift "Runtime truth + service slice", SYNTHESIS §5.3) · **Labels:** `type:fix` `area:service` `area:plugins` `area:auth` `priority:p1` `status:triage` · **Depends on:** none (blocks the browser half of TA-03b; pairs with T1-05) + +## Summary + +`ServiceBuilder.withCors` defaults to `{ origin: '*' }`, and `createPluginService` passes an +undefined `config.cors` straight into it, so every first-party plugin service and every generated +service answers with `Access-Control-Allow-Origin: *`. That header is spec-incompatible with +`credentials: 'include'`: a browser will refuse to send or store credentials against it. Combined +with cross-origin service discovery and the `__Host-` cookie prefix, browser cookie auth is +impossible today **even if** the SDK gained a credentials option — which is why this must be decided +before, not after, the client seam lands. + +## Evidence + +- Corpus: `research/repo-audit/auth.md` §3.4 and §4.2, gap **G7**. +- `packages/service/src/builder/service-builder-impl.ts:95-98` — `withCors(options?: CorsOptions)` + calls `this.app.use('*', cors(options ?? { origin: '*' }))`. +- `packages/plugin/src/service/presentation/create-plugin-service.ts:139-141` — `if + (config.enableCors !== false) { builder = builder.withCors(config.cors) }`; `config.cors` is + `undefined` unless a plugin supplies it. +- `plugins/auth/services/src/main.ts:70-84` — the auth service supplies no `cors`, so the identity + service itself answers `*`. +- `packages/sdk/src/discovery/service-url.ts:97-128` — `getServiceUrl` resolves the **browser** URL + from Aspire-injected `import.meta.env`, so the app on its own port calls `http://localhost:8094` + cross-origin. +- `packages/auth-kv-oauth/src/backend.ts:125` — the session cookie name defaults to + `__Host-ns_session`; `packages/auth-kv-oauth/src/cookies.ts:105-121` asserts `__Host-` requires + `Path=/`, no `Domain`, and `Secure`. A `__Host-` cookie set on `:8094` is never sent to `:8000`. +- There is no same-origin proxy, BFF route, or gateway anywhere in the scaffold (`auth.md` §4.2). + +## Current surface + +Three independent facts compose into "no browser session is possible": a wildcard CORS origin, a +cross-origin service URL handed to the browser, and an origin-locked cookie prefix. Each is +defensible alone; together they mean the only shipped interactive backend cannot authenticate a +browser through the generated app, and no SDK option can rescue it. + +## Target contract + +1. **Decide and record the topology.** Two supported shapes, one default: + - *same-origin BFF* — the Fresh app proxies `/api/*` to the service, cookies stay first-party, + `__Host-` remains valid; or + - *bearer* — the browser holds no cookie, the app forwards a token through the T1-05 client seam + and the service uses a bearer authenticator. + The decision is written into `docs/architecture/doctrine/` (or an `arch-debt.md` entry if + deferred) and the scaffold generates the chosen shape. +2. `withCors` no longer defaults to `origin: '*'`. The default is an explicit allowlist derived from + the generated app's origin(s); a wildcard requires an explicit opt-in and is rejected when + `credentials` is enabled. +3. `createPluginService` passes the workspace allowlist rather than `undefined`. +4. `getServiceUrl` gains (or documents) a same-origin mode so the browser path and the + server-to-server path can differ without the consumer hand-rolling URLs. + +## Acceptance + +- [ ] The browser auth topology is decided and recorded in a doctrine or `arch-debt.md` entry. +- [ ] `withCors` no longer defaults to `origin: '*'`. +- [ ] A generated workspace produces an explicit CORS allowlist covering its own app origin. +- [ ] Plugin services receive the workspace allowlist instead of `undefined`. +- [ ] The scaffold generates the chosen topology (same-origin route or bearer forwarding). +- [ ] Negative test: a wildcard origin combined with credentialed CORS is rejected at build time. +- [ ] Negative test: a request from an origin outside the allowlist is refused. +- [ ] Negative test: a `__Host-` session cookie issued by the chosen topology is accepted on a subsequent request from the generated app. +- [ ] Negative test: the pre-fix arrangement (wildcard + cross-origin + `__Host-`) is proven to drop the cookie, so the regression cannot return silently. +- [ ] gate: `deno task e2e:cli run scaffold.runtime --cleanup` green with the new default. +- [ ] The CORS default change is recorded as breaking with a migration note. + +## Boundaries + +- **Do not** add the SDK `credentials`/`headers` option — **T1-05** owns the client seam. This issue + exists so that option can work when it lands. +- **Do not** make the auth handlers emit cookies — **TA-03b** owns the response half. +- **Do not** build the plugin frontend procedure gateway or its CSRF/origin checks — **#934** owns + them; the allowlist here is the service-level policy, not the gateway. +- **Do not** change Aspire host-port pinning — **#979** (+ **#980**) own it; this issue must not + hardcode ports and should read origins from the workspace configuration. +- **Do not** add auth UI — **#942** owns it. + +## Docs/consumer proof + +`docs/site/identity-access/how-to/add-authentication.md` teaches a `curl`-based cookie round trip +because no browser round trip works. Adoption is proven when a generated app — not `curl` — completes +a sign-in and a subsequent authenticated read, and when the docs state the supported topology +explicitly instead of leaving the reader to discover the wildcard/`__Host-` conflict at runtime. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Sourced from +`research/repo-audit/auth.md` gap G7; all cited line numbers re-verified against worktree +`fac9e339042c` on 2026-08-08. Split from the TA-03 cluster so each session-lifecycle defect carries +its own acceptance and negative tests. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-04-typed-principal-and-policy-metadata.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-04-typed-principal-and-policy-metadata.md new file mode 100644 index 0000000000..d9742e4720 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-04-typed-principal-and-policy-metadata.md @@ -0,0 +1,109 @@ +# feat(service): handlers receive `principal` as an untyped bag entry and no procedure can declare a policy — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** TA-04 · **Proposed milestone:** 0.0.8 (post-shift "Runtime truth + service slice", SYNTHESIS §5.3) · **Labels:** `type:feat` `area:service` `area:plugins` `area:contracts` `area:auth` `priority:p1` `status:triage` · **Depends on:** none (consumed by T1-05, TA-02, #934; prerequisite for #884) + +## Summary + +`buildRpcContext` writes the resolved `Principal` into a `Record`, so every handler +that wants an identity must hand-declare an optional field and trust it. `@netscript/plugin` has no +principal concept at all — zero occurrences repo-wide — so plugin handlers cannot type one even +badly. Separately, authorization is path-prefix-only: `AuthzRequest` carries `{ principal, method, +path }`, rules match with `startsWith`, and oRPC's `.meta()` is used **nowhere** in the codebase, so +a procedure cannot declare the policy it requires. Policy therefore lives in a second place that can +drift from the contract, OpenAPI emits no `security` metadata, and no generated surface (SDK, MCP, +agent tooling) can tell a protected procedure from a public one. + +## Evidence + +- Corpus: `research/repo-audit/auth.md` §4.3 and §5, gaps **G9** and **G10**; `repo-audit/services-sdk.md` + gap **S14**. +- `packages/service/src/types.ts:270-272` — `export type ContextFactory = (context: Context) => + Record`. +- `packages/service/src/builder/service-builder-impl.ts:276-279` — `const principal = + c.get('principal'); if (principal) { ctx.principal = principal; }` — merged into the untyped bag. +- `packages/service/src/auth/types.ts:29-46` — `Principal { subject, scopes, roles, scheme, claims }`; + its own doc comment delegates organization and tenant identity to the untyped `claims` bag + (`:38-45`). +- `grep -rn principal packages/plugin/src` → **zero matches**. +- `grep -rn '\.meta(' packages plugins --include=*.ts` → no oRPC `.meta()` call anywhere; the only + matches are Zod schema metadata in `packages/aspire/config.ts`. +- `packages/service/src/auth/types.ts` `AuthzRequest = { principal, method, path }` and + `packages/service/src/auth/scope-authorizer.ts:22-29` — rules match `request.path.startsWith(...)`. +- Consumer proof that the gap is real at the seam: `research/repo-audit/services-sdk.md` S14 records + no compile-time link between injected context (`db`/`principal`/`traceHeaders`) and the router's + `$context()`, and no exported handler-context type. + +## Current surface + +A guarded service resolves a correct, hashed-logged `Principal` in middleware +(`packages/service/src/auth/auth-middleware.ts:166-177,231-237`) and then loses its type on the way +into the handler. Authorization rules are authored against derived RPC path strings — a rule *can* +match `/api/rpc/v1/auth/signout` because `RPCLink` appends procedure segments, but that string is a +consequence of the transport, not a declaration by the procedure. Renaming a router breaks a policy +silently. + +## Target contract + +1. **Typed principal.** `@netscript/service` exports a handler-context type carrying an optional + `principal: Principal`, and `ContextFactory` is parameterized so `withContext` composes typed + fields instead of widening to `Record`. `@netscript/plugin` re-exports the + principal type so plugin handlers type it without importing from `@netscript/service` internals. +2. **Procedure policy metadata.** A first-class `$meta`/`.meta()` policy annotation on contract + procedures — minimally `{ public: true }` or `{ scopes: readonly string[]; roles?: readonly + string[] }` — carried on the contract, not on a parallel rule table. +3. **An authorizer adapter that reads it.** `createContractAuthorizer(contract)` derives decisions + from the declared metadata and composes with the existing `createScopeAuthorizer` (which stays + the path-prefix adapter, not the ceiling). Fail-closed remains the default: a procedure with no + declared policy and no matching rule is denied. +4. **Generated-surface propagation.** OpenAPI emits `security` for procedures declaring a policy; + the generated SDK/MCP/agent surfaces expose the same fact so an agent can tell a protected + procedure from a public one. +5. **Forward compatibility with #884.** The principal and policy shapes are designed so #884's + organization/membership/assurance model extends them rather than replacing them; this issue does + **not** add tenant fields. + +## Acceptance + +- [ ] A handler reads `context.principal` with the correct type and no cast. +- [ ] `@netscript/plugin` exposes the principal type to plugin handlers. +- [ ] `ContextFactory` composes typed fields instead of widening to `Record`. +- [ ] Contract procedures can declare a policy (`public` or required scopes/roles). +- [ ] An authorizer derives decisions from declared procedure metadata. +- [ ] A procedure with no declared policy and no matching rule is denied. +- [ ] OpenAPI emits `security` metadata for procedures that declare a policy. +- [ ] Negative test: a handler that reads `context.principal` on an unguarded service fails to type-check or receives `undefined`, never a silently-trusted value. +- [ ] Negative test: renaming a router breaks a contract-declared policy at compile time rather than silently unguarding the route. +- [ ] Negative test: a procedure declaring scopes rejects a principal lacking them with 403. +- [ ] Tests cover both the REST and RPC projections of a policy-annotated procedure. +- [ ] `quality:scan` and `arch:check` green; no new `as unknown as` or `any` on the added surface. +- [ ] The principal/policy shapes are reviewed against #884's contract sketch and the review is recorded. + +## Boundaries + +- **Do not** define organization-aware identity or policy contracts — **#884** owns canonical + organization, membership, connection, role/group, assurance and policy-decision types, and the + authorization request over subject × organization × resource × action × context. This issue + defines only the non-tenant principal and the procedure-level policy annotation, and must be + designed so #884 extends it. Cite #884 in the implementation PR and record the compatibility review. +- **Do not** build the browser-facing deny-by-default gateway — **#934** owns it; it consumes this + metadata. +- **Do not** implement the SDK client seam — **T1-05** owns it; it consumes the same metadata to + decide whether a call needs a credential. +- **Do not** add `PluginServiceConfig.auth` — **TA-02** owns it. +- **Do not** widen the type-soundness program — **#1278** is the epic-of-record for unsound types + (with **#1276** proposed for fold); this issue only avoids adding new unsound surface. +- **Do not** build the conformance/mocking test kit — **#885** owns it. + +## Docs/consumer proof + +`docs/site/tutorials/workspace/05-route-authz.md` teaches authorization entirely through path +prefixes. Adoption is proven when that tutorial declares a policy on the procedure and the route +guard follows from it, when `docs/site/reference/` documents the principal type a handler receives, +and when a generated MCP/agent tool listing distinguishes protected from public procedures without +the author writing a second policy table. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Sourced from +`research/repo-audit/auth.md` gaps G9/G10 and `research/repo-audit/services-sdk.md` S14; all cited +line numbers re-verified against worktree `fac9e339042c` on 2026-08-08. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-05-e2e-auth-gates-prove-nothing.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-05-e2e-auth-gates-prove-nothing.md new file mode 100644 index 0000000000..2eab61324b --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/milestones/0.0.8-runtime-truth-service-slice/TA-05-e2e-auth-gates-prove-nothing.md @@ -0,0 +1,111 @@ +# test(e2e): the scaffold runtime auth gate proves only that an unauthenticated request succeeds — DRAFT (no GitHub mutation; owner ratification pending) + +**Draft-ID:** TA-05 · **Proposed milestone:** 0.0.8 (post-shift "Runtime truth + service slice", SYNTHESIS §5.3) · **Labels:** `type:test` `area:cli` `area:auth` `area:tooling` `priority:p1` `status:triage` · **Depends on:** none for the harness capability; TA-01, TA-02 and TA-03a for the assertions themselves + +## Summary + +`scaffold.runtime`'s only auth behaviour gate is `behavior.auth-session`, an unauthenticated `GET +/api/v1/auth/session` that passes when the response is 2xx. It is a liveness probe wearing an auth +gate's name: it would pass identically if the auth service had no authorization at all, which is +exactly today's state. The harness cannot express anything better — `HttpGateDefinition` carries only +`{ kind, method, url }`, and `HttpGate` passes on `result.ok` and retries until the deadline +otherwise. So there is no way to write "expect 401" or "send this credential", and consequently no +gate anywhere proves an authenticated call succeeds or an unauthenticated call is refused. + +## Evidence + +- Corpus: `research/repo-audit/auth.md` §6 (last paragraph) and §7, gap **G12**. +- `packages/cli/e2e/src/application/gates/scaffold/runtime-gates.ts:404-418` — the three auth gates: + `behavior.auth-live` (`/health/live`), `behavior.auth-ready` (`/health/ready`), + `behavior.auth-session` (`/api/v1/auth/session`). All are plain `httpGate` calls. +- `packages/cli/e2e/src/application/gates/scaffold/gate-factory.ts:77-92` — `httpGate(id, title, + url, method = 'GET')`; no expected-status, header, body, or credential parameter. +- `packages/cli/e2e/src/domain/gate-definition.ts:81-86` — `HttpGateDefinition { kind, method, url }`. +- `packages/cli/e2e/src/application/gates/http-gate.ts` — "Gate that succeeds when an HTTP endpoint + returns a 2xx response"; the loop returns `passed` only on `result.ok` and otherwise retries until + `httpTimeoutMs` elapses. +- `packages/service/tests/auth/define-service-auth_test.ts` is the only place a 401/403/200 triple is + asserted, and it targets `/api/openapi.json` on a hand-built service — never `/api/rpc/*`, and + never a generated project (`auth.md` §6). +- `packages/cli/e2e/src/domain/cli-surface.ts:138-140` — the three gate ids, for reference when + adding new ones. + +## Current surface + +The generated-path auth evidence chain is: three 2xx probes against an unguarded service. A +regression that removed the auth service's authorization entirely would leave every gate green. This +is the same "green wrapper over absent runtime truth" failure mode SYNTHESIS §1.4 identifies across +the runtime plugins. + +## Target contract + +1. `HttpGateDefinition` gains optional `expectStatus?: number | readonly number[]` and + `headers?: Record` (or a credential-factory callback resolved from `RunContext`), + and `HttpGate` asserts the expected status instead of `result.ok`. Retry semantics stay: a + connection failure retries, a wrong-but-served status fails fast rather than burning the deadline. +2. New `scaffold.runtime` gates, ids following the existing `behavior.*` convention: + - `behavior.auth-session-unauthenticated` — expects **401** on `GET /api/v1/auth/session`. + - `behavior.auth-session-authenticated` — expects **200** with a valid credential. + - `behavior.auth-rpc-unauthenticated` — expects **401** on `POST /api/rpc/v1/auth/*`. + - `behavior.service-api-unauthenticated` — expects **401** on the generated user service's `/api`. + - `behavior.service-api-authenticated` — expects **200** on the same route with a credential. + - `behavior.auth-signout-foreign-session` — expects a refusal for a foreign session id (TA-03a). +3. The credential the gates use comes from the generated project's own configuration, not from a + fixture the harness invents — the gate must fail if the generated project cannot produce one. +4. The pre-fix behaviour is recorded: each new negative gate must be demonstrated red on + `fac9e339042c` before the corresponding fix lands. + +## Acceptance + +- [ ] `HttpGateDefinition` supports an expected status and request headers. +- [ ] `HttpGate` asserts the expected status rather than any 2xx. +- [ ] A wrong-but-served status fails the gate without consuming the full retry deadline. +- [ ] `behavior.auth-session-unauthenticated` expects 401 and is green after TA-02. +- [ ] `behavior.auth-session-authenticated` expects 200 with a credential. +- [ ] `behavior.auth-rpc-unauthenticated` covers the `/api/rpc/*` projection. +- [ ] `behavior.service-api-unauthenticated` and `behavior.service-api-authenticated` cover the generated user service. +- [ ] `behavior.auth-signout-foreign-session` proves a foreign session id is refused. +- [ ] The credential is produced by the generated project, not by a harness-only fixture. +- [ ] Each new negative gate is demonstrated red against `fac9e339042c` and the evidence is linked. +- [ ] gate: `deno task e2e:cli run scaffold.runtime --cleanup --format pretty` green with the new gates. +- [ ] `behavior.auth-session` is either removed or renamed so no gate name implies auth coverage it does not provide. + +## Boundaries + +- **Do not** build the auth conformance, mocking, and scaffold test kit — **#885** owns the + security-focused kit (fake OIDC/JWKS, signed tokens, WorkOS/Better Auth fixtures, SCIM, clock and + replay controls, per-backend capability matrix) at milestone 0.0.12. This issue adds only the + generated-path behaviour gates that must pass before that kit exists, and its gates should be + re-expressible on #885's fixtures later without re-filing. +- **Do not** implement the auth fixes themselves — **TA-01**, **TA-02**, **TA-03a** own them. +- **Do not** extend gates to background children or `saga.*` spans — that is the T4 runtime-truth + E2E work, tracked separately in this plan. +- **Do not** change Aspire port resolution in the gates — **#979** owns the hardcoded-port + dependency of `runtime-gates.ts`. +- **Do not** re-file published-canary installed-consumer smoke — **#1343** owns it. + +## Docs/consumer proof + +The gate list is the consumer-visible claim: `deno task e2e:cli gates scaffold.runtime` currently +advertises `behavior.auth-session` as auth coverage. Adoption is proven when that listing names +explicit authenticated and rejected cases, and when a reviewer can point at a red-then-green gate id +as the evidence for each of TA-01/TA-02/TA-03a rather than at a hand-run `curl`. + +## Provenance + +Seed run `plan-fable5-remediation-roadmap--seed`, PR #1347, 2026-08-08. Sourced from +`research/repo-audit/auth.md` gap G12; all cited line numbers re-verified against worktree +`fac9e339042c` on 2026-08-08. + +**Scope correction (recorded, not silently dropped).** The Stage-D brief paired this gate work with +"auth docs debt: arch-debt anchors cited by `add-authentication.md` do not exist" +(`research/repo-audit/auth.md` §7.1, gap G13). **That claim is false against this worktree.** The +audit searched `docs/architecture/`; the registry the checker actually resolves against is +`.llm/harness/debt/arch-debt.md`, where both ids exist — +`seamless-auth-roadmap` at line 1240 and `auth-single-active-backend-boundary` at line 1313 — and +`.llm/tools/docs/check-caveat-refs.ts:185-186` resolves `arch-debt:` against that file, wired as +`check:caveats` in `docs/site/deno.json:6-7` and run in CI by `.github/workflows/pages.yml:38-40`. +No dangling-anchor issue is drafted. The surviving docs defect from G13 — cookie claims contradicted +by code — is carried by **TA-03b**'s acceptance; the zero-SDK-auth-guidance half belongs to the T5 +docs pack. This issue therefore stands alone as a test-coverage issue at `priority:p1` rather than +the briefed `priority:p2` docs-polish, because a missing negative security gate is not docs polish. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/external/eis-chat.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/external/eis-chat.md new file mode 100644 index 0000000000..ec82686471 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/external/eis-chat.md @@ -0,0 +1,411 @@ +# eis-chat — the product-quality frontend bar (teardown) + +**Source of truth.** `rickylabs/eis-chat` cloned read-only (`--depth 1`) to +`/home/codex/repos/netscript-fable5-remediation-plan/.llm/tmp/eis-chat`, HEAD +`d38e3986392e70535d698668c6802b8105e5ea76` (2026-07-21, "Merge pull request #154 … +agent/enterprise-windows-desktop"). All `apps/dashboard/**` paths below are relative to that clone. +NetScript-side paths are relative to this worktree (`origin/main` @ `fac9e339042c`). + +**One-line verdict.** eis-chat is a NetScript app pinned at `0.0.1-beta.9` +(`apps/dashboard/deno.json:29-35`) that reaches product quality by *inventing, in app space, six +conventions the scaffold does not generate*: route guards, the degraded-loader error banner, +server-validated forms, an imperative toast bus, an honest LIVE/DESIGNED data-provenance discipline, +and a per-surface CSS ownership rule. Every API it uses already exists in `packages/fresh` on main. +The gap is almost entirely **scaffold/generation + docs/discovery**, not missing framework API. + +--- + +## 1. Stack (verified) + +| Concern | Choice | Citation | +|---|---|---| +| Runtime/build | Deno 2.9, Vite (`npm:vite --configLoader native`), Fresh 2.3.3 | `apps/dashboard/deno.json:7,38` | +| UI | Preact, `jsx: precompile` + `jsxPrecompileSkipElements` (12 elements) | `apps/dashboard/deno.json:49-66` | +| Framework | `@netscript/fresh@0.0.1-beta.9` (+ `/streams`, `/query`), `@netscript/fresh-ui@0.0.1-beta.9`, `@netscript/sdk@0.0.1-beta.9` | `apps/dashboard/deno.json:29-34` | +| RPC/data | contract-first `createServiceClient` + `createQueryFactories` over `@eis-chat/contracts` | `apps/dashboard/lib/channel-service.ts:1-24` | +| Styling | Tailwind v4 `@import 'tailwindcss'` + `--ns-*` token layer + per-block CSS files; `cn = twMerge(clsx(...))` | `apps/dashboard/assets/styles.css:1-3`, `apps/dashboard/lib/cn.ts:9-11` | +| Chat | TanStack AI (`useChat`), durable streams via `@netscript/plugin-streams-core` | `apps/dashboard/islands/ChatPane.tsx:473`, `apps/dashboard/lib/stream-loaders.ts:23-27` | +| Shell | `deno desktop --backend cef`, Windows singleton sidecar | `apps/dashboard/deno.json:12-13,71-82`, `apps/dashboard/main.ts:23-39` | +| Auth | **none.** Zero hits for `plugin-auth`/`better-auth`/`getSession`/`requireAuth` in `apps/dashboard/**` | grep, 0 results | + +Scale: 21 page routes, 15 API routes, 4 `_middleware.ts`, 28 global islands, 48 app-owned UI +primitives, 10 app-owned blocks, 44 lib modules + 18 lib test files, ~31.9k LoC of TS/TSX. + +--- + +## 2. Route organization — the folder vocabulary (a) + +Fresh route groups `(…)` + underscore-prefixed dirs are used as a **non-routing colocation +vocabulary**, confirmed against the generated manifest (`(_components)` etc. produce no route +patterns — `apps/dashboard/.generated/manifest.ts:83-105` lists only real URLs). + +``` +routes/ + _app.tsx html/head/theme-init/skip-link (34 lines) + _layout.tsx 3-pane app shell, SSR nav, breadcrumbs (240 lines) + (_components)/home-view.tsx route-local presentational view + (design)/design/ route GROUP: DS gallery with its own _layout + SidebarShell + (_components)/ (_islands)/ (_shared)/ + api/… 15 handler-only routes + project/[project]/ + _middleware.ts param guard + (_components)/project-view.tsx + channel/[channel]/ + _middleware.ts param guard + active-channel cookie + index.tsx page BUILDER only — no JSX beyond withLayout + (_components)/channel-view.tsx 7 exported presentational sections + (_components)/create-session-form.tsx withForm component + (_islands)/SessionsGrid.tsx route-local island + knowledge/{_middleware.ts,index.tsx,[doc].tsx,(_components)/…} + session/[session]/{_middleware.ts,index.tsx,(_components)/…} + settings/{index.tsx,(_components)/…} +``` + +Counts: 21 route-local `(_components)` files, 4 route-local `(_islands)`, 2 `(_shared)`, versus 28 +*global* `islands/` files. **The rule in practice:** an island is global only when it is mounted by +the shell or reused across surfaces (`islands/ui/*` = ThemeToggle, NavProgress, ActionToasts, +NewSessionButton); anything owned by one route lives under that route's `(_islands)`. + +The page file is **only a builder**. `routes/project/[project]/channel/[channel]/index.tsx` is 295 +lines with zero presentational JSX except the `withLayout` slot grid (lines 265-287); all markup +lives in `(_components)/channel-view.tsx`. That separation is the single most transferable +convention in the repo. + +Route references: `apps/dashboard/router.ts` re-exports the generated tree and adds a **flat +`appRoutes` alias map** (`router.ts:20-59`) because the generated shape is +`routes.project.$project.channel.$channel.$route` (`.generated/routes.ts`). Every outbound link goes +through `appRoutes.X.href({ path: {...} })` — no string literals — e.g. +`channel-view.tsx:92-94,202-204,301`. + +--- + +## 3. Data fetch + cache strategy (a) + +**Server tier — `definePage()` layers.** Canonical example +`routes/project/[project]/channel/[channel]/index.tsx:148-292`: + +- `.withResource('data'|'mcp'|'skills', ctx => …)` — three independent resources, one of them an + aggregate that runs 4 reads under `Promise.all` (lines 113-118). +- `.withLayer(name, Component, { loader: ctx => props })` — 7 layers, each loader projecting exactly + the props its component declares. Components never fetch. +- `.withForm(...)` — server-validated mutation (§5). +- `.withLayout(slots => JSX)` + `.withMeta(...)` + `.build()`, then + `export const { handler, default: page } = channelPage;`. + +**Loader error contract.** Every read is wrapped: `errorHandler(loader, fallback)()` → +`hasError()` → collect `ErrorData[]` → `extractDataWithFallback()` +(`index.tsx:106-134`, same shape at `session/[session]/index.tsx:66-90`). Errors are **de-duplicated +by message** (line 132) because a total backend outage trips all four reads identically. The +`errors` array is fed to a dedicated `issues` layer that renders an inline banner +(`(_components)/channel-view.tsx:24-36`). This is the "degrade but surface" pattern, and it is the +opposite of try/catch-to-empty. + +**Client tier — cache-first islands.** `QueryIsland` + `useIslandQuery({ queryKey, queryFn, +initialData: , staleTime })`. Seeded from the server loader so first paint has data; +`staleTime` 5s or 15s (`(_islands)/SessionsGrid.tsx:41-56`, `islands/ChannelTreeIsland.tsx:21-24`, +`islands/SkillsPanel.tsx:78-83`, `islands/SessionRail.tsx:99-104`, `islands/SessionScratch.tsx:58-61`, +`islands/KnowledgePanel.tsx:203-206`). Query keys are **contract-anchored**, never string literals: +`channelQueries.listSessions.clientKey({ channelId })`. + +**Cache-key collision discipline.** `lib/channel-service.ts:12-24` documents that the factory-group +name is the cache-key prefix and *must be unique per router*, citing a real +`['service','list']` collision fixed in `lib/skills.ts`. + +**Streaming.** `lib/stream-loaders.ts:52-108` is a server-side prepare/resume helper: it +create-if-missing's the durable stream so the island's first live read cannot 404, then materializes +a resume offset. Best-effort throughout — three separate `catch` sites degrade to `{ messages: [] }` +rather than 500 the page. + +--- + +## 4. Form handling (a) + +`.withForm<'createSession', typeof Schema, Result>('createSession', Component, { schema, method: +'POST', csrf: true, initial, mutate, redirectTo, spanName })` +(`routes/project/[project]/channel/[channel]/index.tsx:212-236`). The zod schema carries the *user- +facing* message (`'Name the session before starting it.'`, line 39). `mutate` calls the typed +service client and returns a typed output; `redirectTo` builds the destination through +`appRoutes.session.href(...)`; `spanName: 'channel.session.create'` names the telemetry span. + +The component receives `RuntimeFormState` and renders `
` with +`firstFieldError(state.fieldErrors, 'title')` plus `state.formErrors?.[0]`, wiring `aria-invalid` +and a `role='alert'` `InlineNotice` +(`(_components)/create-session-form.tsx:18-63`). Values survive a failed submit. + +--- + +## 5. Auth composition (b) + +**There is none, and that is stated honestly in the UI.** `MembersPanel` +(`(_components)/channel-view.tsx:314-331`) renders a single "You" member and a sentence naming the +blocking issue: *"Teammates arrive with team accounts (auth/RBAC — #16)."* The stats grid reports +`members: 1` with the comment *"honest counts: just you until auth/RBAC (#16); connectors are the +REAL MCP pool size (was hardcoded 4 / 3)"* (`index.tsx:194-199`). `PluginGatedView` +(`components/blocks/plugin-gated-view.tsx:15-34`) is a reusable "designed but deferred until a +netscript plugin lands" surface. + +Gap — **product-expectation outside framework scope** for eis-chat, but a **docs/discovery failure** +for NetScript: the repo ships `packages/auth-better-auth`, `packages/auth-kv-oauth`, +`packages/auth-workos`, `packages/plugin-auth-core`, yet the most polished NetScript consumer wired +none of them and instead built a gated-empty-state vocabulary. No scaffold template references auth +(`grep` over `packages/cli/src/kernel/assets/app/**` → 0 hits). + +--- + +## 6. UI-state coverage (a + b) + +| State | Coverage | Citation | +|---|---|---| +| Error (server) | Per-page `issues` layer fed by `ErrorData[]`, de-duplicated, `role='alert'` | `index.tsx:168-170`, `channel-view.tsx:24-36` | +| Error (client action) | `notifyToast({ type:'error', … })` on every mutation catch | `(_islands)/SessionsGrid.tsx:99-105,124-129,150-156,179-185` | +| Error (streaming) | Inline assistant-styled error bubble with a **"Retry complete response"** button calling `reload()` | `islands/ChatPane.tsx:873-897` | +| Empty | Per-surface prose empties, not a generic component: "No sources yet…", "No MCP servers reachable — the agent answers from the knowledge base only.", "No active sessions yet — start one above." | `channel-view.tsx:215,261,` `(_islands)/SessionsGrid.tsx:278` | +| Empty (route) | `EmptyState` primitive used on 6 surfaces incl. `PluginGatedView` | `components/ui/empty-state.tsx`, `plugin-gated-view.tsx:29-31` | +| Loading | **SSR-first: no skeletons in any product surface.** `Skeleton` appears only in the DS gallery | grep: `Skeleton` hits only `components/ui/skeleton.tsx`, `mod.ts`, `(design)/…/components-view.tsx` | +| Loading (streaming) | `TypingIndicator` when `busy && lastIsUser`; `NavProgress` island for client-nav with a watchdog | `ChatPane.tsx:861-871`, `islands/ui/NavProgress.tsx` | +| Optimistic | Hand-rolled overlay: local `pending` rows merged ahead of server rows, reconciled in `onSuccess`/`onError` | `islands/SkillsPanel.tsx:104-145,164-173` | +| Optimistic (delete) | Local `removed: Set` + `titles: Record<>` overlays filtered over query data | `(_islands)/SessionsGrid.tsx:57-67,117,171` | +| A11y | `role='log' aria-live='polite'` on the thread, `role='alert'` on errors, skip-link, `aria-current='page'` nav | `ChatPane.tsx:859`, `_app.tsx:27`, `_layout.tsx:34` | + +**Gap — API/type-system seam (partly already fixed on main).** Every `useIslandQuery` call casts: +`queryKey: channelQueries.listSessions.clientKey({ channelId }) as unknown as string[]` +(`(_islands)/SessionsGrid.tsx:42`, and 4 more at `SkillsPanel.tsx:79`, `SessionScratch.tsx:58`, +`KnowledgePanel.tsx:203`, `ChannelTreeIsland.tsx:21`, `SessionRail.tsx:99`). At beta.9 the SDK's +`clientKey` return type and the island hook's `queryKey` type did not line up. On main they do — +`clientKey: (props?) => readonly unknown[]` (`packages/sdk/src/ports/query-factory.ts:97-100`) and +`queryKey: QueryKey` where `type QueryKey = readonly unknown[]` +(`packages/fresh/src/application/query/query-types.ts:28,130`), converged by +`77c034c33 fix(fresh): converge SDK and island cache tiers (#1265)`. **Action: verify + document the +fix so consumers delete the cast; it is currently the single most-copied wart in the reference app.** + +**Gap — docs/discovery failure.** `invalidate` is used **zero times** in the whole app. Islands call +`query.refetch()` plus manual overlay bookkeeping instead of invalidating canonical keys — which is +exactly what the scaffold's own generated guidance tells agents to do ("`useMutation` with canonical +query keys", `packages/cli/src/kernel/templates/app/agent-conventions.ts:131,181`). Likewise +`cachedAt: Date.now()` is threaded from loader → layer → island through three files +(`routes/skills/index.tsx:49` → `routes/skills/(_components)/skills-view.tsx:40-48` → +`islands/SkillsPanel.tsx:34`) and then **discarded** (`SkillsPanel.tsx:74`, `cachedAt: _cachedAt`) +— the `initialDataUpdatedAt` option that would have consumed it +(`packages/fresh/src/application/query/query-types.ts:135`) was never discovered. + +**Gap — runtime correctness (framework, worked around in app).** Three explicit upstream +workarounds are in-tree, each with a comment: +- `create-session-form.tsx:28-39` — `` + a POST→redirect crashes Fresh 2.3.3's + `domToVNode` reviver; the app must pass the literal **string** `'f-client-nav': 'false'` because a + boolean `false` is dropped by Preact. +- `lib/stream-loaders.ts:86-90` — the streams runtime mislabels plain JSON as + `content-encoding: gzip`, so reads must send `Accept-Encoding: identity` (cites `netscript#219`). +- `lib/stream-loaders.ts:75-81` — the transport's `materializeSnapshotFromDurableStream` replays only + `TEXT_MESSAGE_*` chunks, dropping tool-call/thinking/widget parts, so the app replays through + TanStack's own `StreamProcessor`. + +**Gap — plugin-composition / design-system seam.** `channel-view.tsx:20-23`: *"the framework's +default `ErrorDisplay`/`InlineError` ship hardcoded Tailwind colors that ignore our theme tokens — so +we consume the normalized `ErrorData` into our own primitive."* The error **contract** is reusable; +the error **components** are not, because they are not tokenized. + +--- + +## 7. Design-system usage (a) + +- **Copy-owned registry.** `components/ui/mod.ts:1-4` — "installed with `netscript ui:init` from the + @netscript/fresh-ui registry and are **owned by the scaffolded app**". 48 primitives, all + re-exported with their types from one barrel; every consumer imports + `@app/components/ui/mod.ts`, never a deep path. +- **Layered ownership, annotated in source.** `components/ui/button.tsx:1-6` carries + `@component/@layer 2/@depends theme-seed/@description` JSDoc tags. The app keeps a + machine-readable snapshot of the registry catalog for its own gallery + (`routes/(design)/design/(_shared)/registry.ts:1-30`, `total: 44`). +- **Discriminated-union props over `as` escape hatches.** `Button` is + `ButtonAsButtonProps | ButtonAsLinkProps` with `href?: never` on the button branch and `type: + 'link'` as the discriminant (`button.tsx:40-64`), so the anchor branch gets `f-client-nav` typed + via a local `FreshAnchorNavigationAttributes` interface (`button.tsx:23-25`). +- **Tokens only, both themes.** `:root` = light, `[data-theme='dark']` = dark, OKLCH ramps with hex + fallbacks (`assets/tokens.css:12-60`). Theme is applied pre-paint by an inline script reading + `localStorage['ns-theme']` with a `prefers-color-scheme` fallback (`routes/_app.tsx:3-4,24`). +- **CSS ownership rule.** One CSS file per primitive and per app-owned surface block, wired by a + single ordered `@import` list; the rule is written down: *"per-surface app-owned selectors → + `assets/blocks/.css` (only NET-NEW selectors … don't dup). Add ONE `@import` to + `assets/styles.css` before `@layer base`"* (`docs/design/BUILD-CONTRACT.md:37`). 57 CSS files, + zero raw hex allowed (`docs/design/DECISIONS.md:22-24`). +- **Zero-JS-first interaction budget.** `docs/design/BUILD-CONTRACT.md:34`: Popover API, + ``+`::backdrop`, `
`/`::details-content`, `:has()`, `@container`, + `field-sizing:content`, CSS-only tabs — *"Islands ONLY for streaming/mutations."* Applied: the nav + channel tree is server-rendered `` + native `
`, explicitly rejecting an island + (`docs/design/DECISIONS.md:54-61`); the context rail's 4 tabs are CSS radio tabs + (`docs/design/BUILD-CONTRACT.md:6`); the row action menu is a `
` with `role='menu'` + (`(_islands)/SessionsGrid.tsx:253-272`). +- **Live DS gallery as a route group.** `/design/{tokens,components,composition,generative}` with its + own `_layout.tsx` using `SidebarShell` (`routes/(design)/design/_layout.tsx:46-74`); the root shell + short-circuits for `/design` (`routes/_layout.tsx:64-70`). + +--- + +## 8. Type discipline (a) + +Measured across `apps/dashboard/**` excluding `.generated/`: + +- **`any`: 0 occurrences in code.** The 3 grep hits are the English word inside comments + (`lib/kb-executions-stream.ts:100`, `routes/api/chat.ts:348,493`). +- **`@ts-ignore` / `@ts-expect-error` / `@ts-nocheck`: 0.** +- **`deno-lint-ignore`: 1**, with a stated reason + (`components/ui/mcp-widget.tsx:17`, `verbatim-module-syntax` for a value import). +- **`as unknown as`: 18**, and they cluster into exactly four *framework* seams, not sloppiness: + 1. query-key type mismatch ×6 (§6, fixed on main); + 2. `Deno` desktop-API narrowing ×2 (`lib/desktop-chrome.ts:59-60`) — unavoidable, the desktop API + is not in the type lib; + 3. AI/adapter unions ×4 (`lib/llm.ts:257,265`, `lib/chat-render.ts:209,228`, + `islands/ChatPane.tsx:326`) — TanStack AI part-type widening; + 4. driver handle narrowing ×2 (`islands/SharingPanel.tsx:68`, `islands/KnowledgePanel.tsx:255`). +- **`readonly` props everywhere** — every exported prop interface in `(_components)` and `blocks` + uses `readonly` fields and `readonly T[]` (`channel-view.tsx:39-58,190-196,244-247,270-272`). +- **Explicit return types** on presentational components (`: VNode`) and loaders + (`channel-view.tsx:81,118,176,199,250,275,314`; `index.tsx:60,67,80,86,106,155,179-186`). +- Residual soundness gap — **API/type-system seam**: `islands/SkillsPanel.tsx:93-97` coerces + `tags`/`scopeChannels` to `[]` because *"a row can reach the grid with `tags`/`scopeChannels` + undefined (a serialization/hydration edge the contract types don't catch) … an undefined there + throws and 500s the whole SSR."* Contract-typed non-optional arrays are not guaranteed across the + SSR→hydration boundary. + +--- + +## 9. Route guards — the loader-cannot-fail seam (b: API/type-system seam) + +`apps/dashboard/lib/route-guards.ts:1-45` is the most important framework finding in the repo. Its +header comment, verified against alpha.17 + Fresh 2.3.3: + +> a `definePage()` layer loader **CANNOT** signal a non-200 outcome — the netscript pipeline swallows +> any thrown value (a `Response` or a Fresh `HttpError`) into a 500. The seam Fresh honors is a +> route-subtree `_middleware.ts` (default export) using `ctx.redirect()` / `ctx.next()`. + +Consequence: the app hand-built `guardParam(key, fallback)` and `guardParamIfMatched(...)` and nests +one guard per dynamic segment (4 `_middleware.ts` files, e.g. +`routes/project/[project]/channel/[channel]/_middleware.ts:18-24`), then every loader still writes +`ctx.params.channel ?? ''` defensively (12 occurrences in `channel/[channel]/index.tsx` alone). + +Second seam in the same comment (lines 12-14): `.withRoute(routes..$route)` makes `ctx.path` +**typed but empty at runtime** when there is no `pathSchema`, so loaders must read raw `ctx.params` +instead of the typed surface the builder advertises. + +The middleware also does non-guard work — appending an active-channel cookie after `ctx.next()` +(`_middleware.ts:8-15`) — which is the idiomatic "remember scope" seam. + +--- + +## 10. Provenance discipline: LIVE vs DESIGNED (a — and the single biggest quality differentiator) + +eis-chat forbids fake data and encodes provenance in **types**: + +```ts +export interface ChannelStats { + /** LIVE — session count. */ readonly sessions: number; + /** DESIGNED — member count (no v1 backend). */ readonly members: number; +} +``` +(`(_components)/channel-view.tsx:46-58`.) The build contract states it as law: *"your surface must be +**LIVE against the real DB where the backend supports it**, and cleanly **designed/deferred where it +doesn't** (do NOT fake E2E)"* and lists the deferred set explicitly +(`docs/design/BUILD-CONTRACT.md:3,25`). House style: *"Findings/uncertainty over silent failure — +surface unreachable MCP servers, failed embeddings, and sync conflicts as visible state, not +swallowed errors"* (`docs/SKILL.md:79-80`). + +--- + +## 11. What the scaffold generates today vs. the bar (delta) + +Verified against `packages/cli/src/kernel/assets/app/**` (50 template files) and +`packages/cli/src/kernel/templates/app/agent-conventions.ts`. + +**Already generated (parity — do not re-litigate):** `(_components)`/`(_islands)`/`(_shared)` +vocabulary; `(design)` route group with tokens/components/composition; `components/ui/mod.ts` barrel; +`lib/example-service.ts` contract→client→query-factories; `definePage` layers; `QueryIsland`; +`routes/partials/**` deferred partial; telemetry route; `router.ts`; `_app.tsx`/`_layout.tsx`; and an +agent-facing conventions doc that already names the one-screen path +`contract → createQueryFactories → definePage layers → QueryIsland/useMutation → live stream` +(`agent-conventions.ts:163`). + +**Missing (scaffold/generation failure — each is a concrete issue):** + +| # | Convention at the bar | Evidence it's absent from the scaffold | Class | +|---|---|---|---| +| S1 | `_middleware.ts` param guard + a `lib/route-guards.ts` helper | `find packages/cli/src/kernel/assets/app -name '*middleware*'` → 0 | scaffold/generation | +| S2 | A dynamic `[param]` route at all | no `[`-named template in the 50-file asset tree | scaffold/generation | +| S3 | `@netscript/fresh/error` degraded-loader + `issues` layer + de-dup by message | `grep -rl '@netscript/fresh/error'` over templates → 0 | docs/discovery | +| S4 | `withForm` + zod + CSRF + field errors + `redirectTo` + `spanName` | `grep -rl 'withForm'` over templates → 0 (the API exists: `packages/fresh/src/application/form/mod.ts`) | docs/discovery | +| S5 | Client toast bus (`CustomEvent` on `document`) + one `ActionToasts` outlet in `_layout` | no toast template outside the DS gallery | scaffold/generation | +| S6 | Client-nav progress + silent-failure watchdog island | none | scaffold/generation | +| S7 | Flat `appRoutes` alias map over the nested generated tree | eis-chat hand-writes 15 `createRouteReference` literals (`router.ts:20-59`) that duplicate `.generated/manifest.ts` | scaffold/generation | +| S8 | An app `README.md` that states the app's own conventions | eis-chat's is untouched Fresh boilerplate (`apps/dashboard/README.md:1-17`) while the real rules live in `docs/design/BUILD-CONTRACT.md` | docs/discovery | +| S9 | Optimistic-mutation recipe + `invalidate` on canonical keys | 0 `invalidate` calls in the reference app despite the generated doc prescribing it | docs/discovery | +| S10 | `initialDataUpdatedAt` wired from a loader `cachedAt` | plumbed then dropped (`SkillsPanel.tsx:74`) | docs/discovery | +| S11 | Tokenized `ErrorDisplay`/`InlineError` (no hardcoded Tailwind colors) | app re-implements into `InlineNotice` (`channel-view.tsx:20-23`) | plugin-composition / DS | +| S12 | A LIVE/DESIGNED provenance convention + `PluginGatedView` primitive | app-invented (`components/blocks/plugin-gated-view.tsx`) | docs/discovery | + +--- + +## 12. Concrete conventions the scaffold should generate (ranked, copy-ready) + +1. **Page files are builders only.** Generate every example page as `definePage()` + `withLayout` + slots with **zero** presentational JSX; markup lives in `(_components)/-view.tsx` exporting + named sections. Bar: `channel/[channel]/index.tsx` 295 lines / 0 markup vs + `(_components)/channel-view.tsx` 331 lines / all markup. +2. **Generate a dynamic route with a guard.** `routes//[id]/{_middleware.ts,index.tsx,(_components)/…}` + where `_middleware.ts` default-exports `[guardParam('id', fallbackHref)]`, plus + `lib/route-guards.ts` with `isPresent`/`guardParam`/`guardParamIfMatched`. Until loaders can emit + non-200 (§9), this is the *only* correct 404/redirect seam and the scaffold must say so. +3. **Generate the error contract, not just the API.** Every generated resource loader wrapped in + `errorHandler(loader, fallback)`, results funnelled through `hasError`/`extractDataWithFallback`, + errors de-duplicated by message, rendered by a first `issues` layer with `role='alert'`. +4. **Generate one `withForm` example** with zod messages written as user-facing copy, `csrf: true`, + `redirectTo` via the typed route map, `spanName`, and a component that reads + `firstFieldError(state.fieldErrors, …)` and sets `aria-invalid`. +5. **Generate the toast bus.** `lib/ui/client-toast.ts` (`CustomEvent` on `document`, SSR no-op, + optional `action: {label, run}`) + a single `ActionToasts` outlet mounted in `_layout.tsx`. This + is what makes every island mutation reportable without prop plumbing. +6. **Generate a flat `appRoutes` map** in `router.ts` derived from the generated manifest, so link + sites read `appRoutes.session.href({ path: { … } })` instead of `routes.a.$b.c.$d.$route`. Ship it + as codegen, not as a hand-maintained duplicate. +7. **Generate the optimistic-mutation recipe**: local `pending`/`removed` overlay merged ahead of + query data, reconciled in `onSuccess`/`onError`, **plus** `invalidate` on the canonical key — and + consume `cachedAt` into `initialDataUpdatedAt` so the seam is discovered. +8. **Generate provenance vocabulary**: `PluginGatedView`, plus a documented rule that unbacked + surfaces are rendered and labelled, never faked, with the blocking issue named in the copy. +9. **Generate a real app README** (not Fresh boilerplate) containing: the folder vocabulary table, + the island budget ("islands only for streaming/mutations"), the CSS ownership rule (one + `assets/blocks/.css`, one ordered `@import`, tokens only, no raw hex, light + dark), and + the per-slice verification command pair. +10. **Generate the zero-JS interaction budget as working examples**: `
` row menu with + `role='menu'`, CSS-radio tabs, native ``, `@container` — so agents copy progressive + enhancement instead of reaching for an island. +11. **Type discipline defaults**: `readonly` props, explicit `: VNode` returns, discriminated-union + component props (`type: 'link'` + `href?: never`) — and a lint/gate that fails on `any`, + `@ts-ignore`, and un-commented `as unknown as`. eis-chat proves 0/0/justified-only is achievable + at 31.9k LoC. + +--- + +## 13. Framework-side actions implied (not scaffold) + +- **F1 (API/type-system seam, likely already fixed):** confirm `#1265` removed the + `clientKey → queryKey` cast; add a regression test asserting `clientKey(...)` is assignable to + `IslandQueryOptions['queryKey']`, and note the fix in the beta.9→current migration doc so the six + copied casts get deleted. +- **F2 (API/type-system seam):** loaders cannot emit non-200 (`route-guards.ts:5-9`). Either make + `definePage` loaders honor a thrown `Response`/`HttpError`, or document `_middleware.ts` as the + official redirect/404 seam in the builder's own docs. +- **F3 (API/type-system seam):** `.withRoute()` types `ctx.path` but leaves it empty at runtime + without `pathSchema` (`route-guards.ts:12-14`) — typed-but-lying surface. +- **F4 (plugin-composition/DS):** tokenize `ErrorDisplay`/`InlineError` so the framework error + components are usable inside a token-driven design system (`channel-view.tsx:20-23`). +- **F5 (runtime correctness):** `` + POST→redirect crashes Fresh 2.3.3's + `domToVNode`; the only escape is the literal string `'false'` + (`create-session-form.tsx:28-39`). Needs either a framework guard or a documented opt-out helper. +- **F6 (runtime correctness):** streams runtime sends `content-encoding: gzip` on plain JSON + (`stream-loaders.ts:86-90`, cites `netscript#219`); durable-stream snapshot replay drops + tool-call/thinking/widget chunks (`stream-loaders.ts:75-81`). +- **F7 (API/type-system seam):** contract-typed non-optional arrays arrive `undefined` after + hydration and 500 SSR (`SkillsPanel.tsx:93-97`). +- **F8 (docs/discovery):** eis-chat's own `docs/NETSCRIPT-UPSTREAM-CANDIDATES.md:1-32` already lists + three review-ready upstream candidates (typed desktop backend `webview|cef`, an explicit + loopback-capable hostname axis on `ServeOptions`/`ServiceConfig`/`defineService`, combined-graph + Windows deployment). These are unowned on the NetScript board as far as this teardown can see. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/external/meta-frameworks.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/external/meta-frameworks.md new file mode 100644 index 0000000000..978b2bfbb2 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/external/meta-frameworks.md @@ -0,0 +1,474 @@ +# Competitive teardown — production meta-frameworks, 2026 + +**Run:** `plan-fable5-remediation-roadmap--seed` · **Baseline:** `origin/main` `fac9e339042c` (2026-08-08) +· **Scope:** external competitive research + verification of NetScript's current state against the +bar. Read-only audit; no repo source touched. + +**Evidence rules applied:** every framework claim carries a vendor-doc URL. Every NetScript claim +carries a repo path (+line where load-bearing) or a command output reproduced inline. Where a claim +is sentiment rather than fact it is labelled `[sentiment]` and its source is named. + +--- + +## 0. Verification commands used for NetScript claims + +```text +$ ls packages +ai aspire auth-better-auth auth-kv-oauth auth-workos bench cli config contracts cron database +fresh fresh-ui kv logger mcp plugin plugin-ai-core plugin-auth-core plugin-sagas-core +plugin-streams-core plugin-triggers-core plugin-workers-core prisma-adapter-mysql queue +runtime-config sdk service telemetry watchers + +$ ls plugins +ai auth sagas streams triggers workers + +$ ls packages/cli/src/public/features +agent config contracts db deploy generate init marketplace plugins root services ui + +$ curl -s https://jsr.io/@fresh/core/meta.json # latest: 2.3.3 +``` + +--- + +## 1. Per-framework teardown + +Axes per framework: **(a) scaffold/CLI generation depth · (b) typed end-to-end data story · +(c) auth · (d) background jobs / durable workflows · (e) observability · (f) docs / AI-agent +readiness · (g) the ONE thing users praise.** + +### 1.1 Next.js 16 (Vercel) + +- **(a) Scaffold/CLI.** `create-next-app` only. There is **no incremental generator surface** — + no `make:route`, no `make:job`. Config-level features are toggles in `next.config.js`, e.g. + `cacheComponents: true` and `reactCompiler: true` + (, + ). + Next.js competes on *runtime* depth, not *generation* depth. +- **(b) Typed data.** Server Components + Server Actions. Types flow because the call is a direct + import, not a wire contract — there is no emitted client, no OpenAPI, no contract artifact. Next + 16 added `updateTag()` (Server-Actions-only, read-your-writes) and `refresh()` (uncached-data + refresh) as explicit cache-coherence primitives on top of Cache Components / PPR + (). **Note the direction of travel: the frontier is no longer + "can you type the call", it is "can you express cache/invalidation semantics in the type-safe + call".** +- **(c) Auth.** **Nothing first-party.** Users assemble Better Auth / Auth.js / Clerk / WorkOS. + This is the single largest hole in the market leader and the reason auth vendors have such + leverage in the React ecosystem. +- **(d) Jobs/durable workflows.** Not in the framework. Vercel ships this *beside* the framework: + the open-source **Workflow Development Kit** (public beta) turns any async function durable with + a `"use workflow"` directive — "Durable: Survive deployments and crashes with deterministic + replays… No manual wiring of queues, no schedulers, no YAML" + (, + ). + **This is the new bar for durable execution ergonomics: a directive, not a DSL.** +- **(e) Observability.** Vercel-platform-coupled. In-framework: dev overlay + a built-in MCP + endpoint exposing runtime errors/routes/logs. +- **(f) Agent readiness.** Strongest-in-class *runtime* agent surface: Next 16 **enables an MCP + endpoint by default at `http://localhost:3000/_next/mcp`**, and `next-devtools-mcp` discovers it + so agents get "live runtime errors, routes, and logs" + (, ). + Gated behind `experimental.mcpServer` in some configs + (). +- **(g) Praised for:** ubiquity and hiring signal — "78% of new React apps use Next.js" + `[sentiment]` (). Satisfaction is *falling*: + State of JS 2025 shows a 21% positive / 17% negative split and Astro leading meta-framework + satisfaction by 39 points `[sentiment, secondary source]` (same URL). + +### 1.2 Nuxt 4 (+ Nitro v3) + +- **(a) Scaffold/CLI.** `nuxi` with a real incremental generator surface (`nuxi add` for pages, + components, composables, server routes, middleware, layers). Plus a **modules ecosystem** — + install-a-module is the extension unit. Layers (`~~/layers/test` → `#layers/test`) give + cross-project config inheritance (). +- **(b) Typed data.** `useFetch`/`useAsyncData` typed against Nitro server routes; typed + per-environment config overrides in `nuxt.config`; typed layout props and typed route meta + (`definePageMeta` with dynamic `types`/`toTypes`/`fromTypes`). 4.4 added custom + `useFetch`/`useAsyncData` factories (). Payloads are serialized with + devalue; custom class types need explicit reducers/revivers — **a real seam** documented as such + (). +- **(c) Auth.** No first-party auth in core; `nuxt-auth-utils` and community modules carry it. Same + hole as Next, softened by module-ecosystem discoverability. +- **(d) Jobs.** **Nitro Tasks**, still gated: `experimental: { tasks: true }`, with + `scheduledTasks: { '* * * * *': ['cms:update'] }`, a `nitro task list` CLI, and dev API routes + `/_nitro/tasks` and `/_nitro/tasks/:name` (). **Not durable** — + no replay, no at-least-once guarantee, and it does not work on several serverless hosts + (, + ). +- **(e) Observability.** Nuxt DevTools; build profiling added in 4.4. No first-party distributed + tracing. +- **(f) Agent readiness.** **Best-in-class docs-for-agents.** `nuxt.com/llms.txt` is a curated task + router with per-page raw-markdown twins (`/raw/docs/4.x/...md`), plus a full + `nuxt.com/llms-full.txt` corpus, plus a public docs MCP server. The team states it *dogfooded* + this: 4.4 "was built internally using the AI SDK, our MCP server, and Nuxt UI components" + (, ). +- **(g) Praised for:** documentation + module ecosystem DX; consistently top-ranked "admired" + `[sentiment]` (). + +### 1.3 SvelteKit 2 + +- **(a) Scaffold/CLI.** `sv create` / `sv add` (add-ons for tailwind, drizzle, lucia, paraglide…). + Thin, but the add-on model exists. +- **(b) Typed data — the reference implementation of the 2026 seam.** **Remote functions** + (stable since 2.27) exported from `.remote.ts` in four flavours: `query`, `form`, `command`, + `prerender`. Arguments are validated by any **Standard Schema** passed as the first argument + (`query(v.string(), async (slug) => …)`), and errors/redirects are first-class. Critically, + **single-flight mutations**: `form` submission or `command` invocation "can refresh queries and + pass their results back to the client in a single request", and `form` degrades gracefully with + JS disabled (). + **This is the cleanest typed-RPC-with-cache-coherence design shipping today.** +- **(c) Auth.** Nothing first-party. Lucia/Better Auth via `sv add`. +- **(d) Jobs/durable workflows.** **Nothing.** Not in scope for SvelteKit. +- **(e) Observability.** Nothing first-party. +- **(f) Agent readiness.** `svelte.dev/docs` serves LLM-oriented text; community Claude skills + exist for remote functions () + — i.e. the *community*, not the vendor, closed the agent gap. +- **(g) Praised for:** the simplest correct mental model + highest satisfaction of the big three + `[sentiment]`; 91% retention / 62.4% admiration in State of JS 2025 for Svelte + (). + +### 1.4 TanStack Start + +- **(a) Scaffold/CLI.** `npm create @tanstack/start` with an **add-on picker** — Better Auth is a + first-class checkbox in the create flow (). +- **(b) Typed data.** `createServerFn()` with `.validator()` + `.handler()`, method selection, + composable request middleware, and — uniquely — **serialization type-checking as a type-system + feature**: `createServerFn({ strict: false })` / `{ strict: { input: false } }` explicitly opts + out of input/output serializability checks. Production function IDs are configurable via + `serverFns.generateFunctionId` in the Vite/rsbuild plugin + (). CSRF is a + first-class middleware (`createCsrfMiddleware`, filtered by `handlerType: 'serverFn'`). + **The serialization-boundary type check is a directly stealable idea for a contract-first + framework.** +- **(c) Auth.** Partner-backed (Clerk, WorkOS gold partners) plus the Better Auth create-flow + add-on; not first-party (). +- **(d) Jobs.** **Nothing.** +- **(e) Observability.** Nothing first-party. +- **(f) Agent readiness.** Standard docs site; no vendor llms.txt/MCP surfaced in the docs nav. +- **(g) Praised for:** explicitness. The recurring community line is that Start "makes you define + loaders and server fns explicitly. IMO this is the right approach" `[sentiment]` + (). +- **Maturity caveat:** v1 was announced as a **Release Candidate** on 2025-09-23 and the live docs + still serve under `/start/v0/docs/…` — treat as pre-1.0 + (). + +### 1.5 RedwoodSDK + +- **(a) Scaffold/CLI.** `npx create-rwsdk my-project-name`; the app is a `defineApp([...])` route + list, deliberately minimal — no generator suite (the Redwood *generator* heritage was + **abandoned** in the pivot) + (, + ). +- **(b) Typed data.** RSC + Server Functions via a Vite plugin; `useSyncedState` for bidirectional + realtime state (). +- **(c) Auth.** **First-party-ish and opinionated**: a bundled **Passkey (WebAuthn) addon** with + server logic + client hooks, sessions stored in **Cloudflare Durable Objects** + (, ). + Notable: it picks *one* modern mechanism rather than shipping an abstraction over five. +- **(d) Jobs/durable.** Inherited from the platform: Durable Objects "handle coordination, + persistence, and global distribution" for realtime (). Queues/cron + are Cloudflare primitives, not RedwoodSDK abstractions. **Platform lock-in is the design.** +- **(e) Observability.** Cloudflare's. +- **(f) Agent readiness — most forward-looking of the set.** Ships a `.well-known/api-catalog` and + `.well-known/agent-skills/index.json`, and states the design principle explicitly: *"Simplicity + for humans is clarity for AI"* — avoid "custom noise" so AI focuses on business logic rather than + framework conventions (). **Agent-skills-as-a-published-artifact is ahead of + llms.txt.** +- **(g) Praised for:** web-standards purity on Cloudflare — Request/Response in, no framework + ceremony. + +### 1.6 AdonisJS 6 + +- **(a) Scaffold/CLI — the deepest generator surface in the Node world.** `node ace make:command`, + `make:controller`, `make:test`, plus **`node ace add `** which installs the package, wires + `adonisrc.ts` (`commands`, `providers`, `preloads`) and writes config in one step + (, + ). Commands declare lifecycle intent via + `static options: CommandOptions = { startApp, staysAlive, allowUnknownFlags }` and can register + teardown with `this.app.terminating(...)` — **a genuinely good CLI-command contract to copy.** + Controllers are now referenced through a **generated typed registry**: + `import { controllers } from '#generated/controllers'` then + `router.resource('posts', controllers.Posts)` + (). Directory layout is configurable via + `defineConfig({ directories: { controllers: 'app/http/controllers' } })`. +- **(b) Typed data.** VineJS validation + Lucid ORM; typed within the app, no emitted client. +- **(c) Auth.** **First-party** (`@adonisjs/auth`), plus `@adonisjs/bouncer` for authorization and + an AdonisJS Plus type-safe RBAC layer on Bouncer `[sentiment: reddit r/adonisjs]`. +- **(d) Jobs.** `@adonisjs/queue`, built on `@boringnode/queue`, with Redis / Database (Lucid) / + Sync adapters, `worker: { concurrency, idleDelay }`, `locations: ['./app/jobs/**/*']`, and a + **fake for tests** (`QueueManager.fake()` + `fake.assertPushed(Job, { payload, delay })`). + **Explicitly experimental**: "The `@adonisjs/queue` package is currently experimental. Its API + may change between minor releases… Pin the package version" + (). Not durable-workflow-grade (no + replay/compensation). +- **(e) Observability.** No first-party tracing. +- **(f) Agent readiness.** **Weakest of the set.** The introduction page carries no llms.txt, no + AI-docs, no MCP reference (). +- **(g) Praised for:** "Laravel for TypeScript" — batteries-included conventions and the Ace + generator flow `[sentiment]` (). + +### 1.7 Encore.ts — the closest structural competitor to NetScript + +- **(a) Scaffold/CLI.** `encore run`, `encore build docker`, scaffold commands for init, and + **`encore gen client --output=./client.ts`** — a generated typed RPC client as a first-class + artifact (, + ). +- **(b) Typed data.** "Define APIs as plain TypeScript functions. Calling another service is a + normal function call; Encore handles serialization, routing, and validation." + **Infrastructure primitives are declared in TypeScript**: SQL databases, Pub/Sub + topics/subscriptions, object-storage buckets, caches, cron jobs, secrets + (). +- **(c) Auth.** Auth handlers as a framework concept (declared, then enforced across services). +- **(d) Jobs/durable.** Pub/Sub topics + subscriptions and **cron jobs declared in code**. Not + replay-durable workflows, but *infrastructure-backed* rather than in-process. +- **(e) Observability — the bar.** Built-in local dev dashboard with **distributed tracing**, logs, + and DB exploration, with production tracing in Encore Cloud + (, ). +- **(f) Agent readiness — the bar, and the one to beat.** *"Because infrastructure is declared in + code, AI coding assistants can understand and modify your full stack"* — shipped as AI + instructions **plus a local MCP server**: `encore mcp run --app=`, attachable with + `claude mcp add --transport stdio encore-local -- encore mcp run --app=…`. The MCP server exposes + **19 tools across nine areas: services and APIs, databases, traces, pub/sub, cache and storage, + infrastructure…** (, + , ). + Positioning is explicit: "the infrastructure platform for the intelligence era, where engineers + **and AI agents** build production systems" (). +- **(g) Praised for:** "you get observability for free" — the automatic distributed tracing across + services without manual instrumentation `[sentiment]` + (). +- **Structural gap noted by third-party analysis:** Encore is Tier A "3/4 SSOT (one structural + gap)" in a single-source-of-truth ranking of TS backend frameworks + (). + +### 1.8 Laravel 13 — the full-stack bar + +- **(a) Scaffold/CLI.** `php artisan make:*` for every archetype, official **starter kits** + (React/Vue/Livewire × Breeze/Jetstream, WorkOS option) as the standard entry point + (, + ). The MCP surface itself is generated: + `php artisan make:mcp-server WeatherServer`, `php artisan make:mcp-tool CurrentWeatherTool` + (). +- **(b) Typed data.** N/A in the TS sense (PHP); Inertia collapses the client/server seam instead. +- **(c) Auth.** First-party and complete (starter kits + Fortify/Sanctum/Passport + policies). +- **(d) Jobs/durable — the bar.** Queues + scheduler in core; **Horizon** for queue ops. +- **(e) Observability — the bar.** **Telescope** (dev introspection) and **Pulse** (production + app-health) shipped as first-party packages, listed in the docs nav alongside Horizon + ( → "Packages: Horizon / Telescope / Pulse"). +- **(f) Agent readiness — the bar, twice over.** + 1. **Laravel MCP** (first-party, in core docs): servers, tools with **streaming responses** + (`yield Response::notification('processing/progress', …)`), an **MCP client** + (`Mcp::client('github')->tools()`), an **inspector** (`php artisan mcp:inspector mcp/weather`), + and **unit-testable servers** (`WeatherServer::actingAs($user)->tool(...)`) + (). + 2. **Laravel Boost** — `composer require laravel/boost --dev`, `php artisan boost:install`, + `claude mcp add -s local -t stdio laravel-boost php artisan boost:mcp`. Boost ships **AI + guidelines**, a **Record Rule** MCP tool that writes durable project rules into `.ai/rules` + "so future agents inherit it", and **third-party packages can ship their own AI guidelines** + that Boost auto-loads on install (, + ). Boost 2.0 made guidelines "~40% leaner" `[sentiment]`. + Note the tagline Laravel now uses: *"The clean stack for Artisans **and agents**"* (page title, + ). +- **(g) Praised for:** "everything is already there and it all fits together" — the completeness of + the first-party surface. **This is the standard NetScript's README explicitly benchmarks against** + (`README.md:11-12`). + +### 1.9 Deno Fresh 2 — NetScript's own substrate + +- **Status.** Out of beta. `curl https://jsr.io/@fresh/core/meta.json` → `latest: 2.3.3`; the + road-to-2.0 tracking issue is closed with "Fresh 2 has been officially released, though a proper + announcement blog post will follow" (). + NetScript pins `"fresh": "jsr:@fresh/core@^2.3.3"` (`packages/fresh/deno.json:38`) — **current**. +- **(a) Scaffold/CLI.** `deno run -A -r jsr:@fresh/init`. Minimal. +- **(b) Typed data.** Islands + handlers; **no server-function/RPC layer at all**. This is the + hole NetScript fills with oRPC. +- **(c) Auth / (d) jobs / (e) observability.** None. Fresh is a routing + islands + build layer. +- **(f) Agent readiness.** Docs only. +- **(g) Praised for:** zero-JS-by-default islands and 10× faster boot (86ms → 8ms) with the Vite + plugin; `` restored; programmatic `App()` API with file routing as a plugin; automatic + React/Preact aliasing (, + ). +- **Honest downside cited by the ecosystem:** "you also accept a smaller ecosystem than larger + JavaScript frameworks" and Preact-only islands + (). **NetScript inherits this.** + +--- + +## 2. The capability bar NetScript must clear, per category + +| Category | Table stakes (must have to be credible) | Frontier (differentiator in 2026) | Who sets it | +|---|---|---|---| +| Scaffold/CLI generation | `create` command + **incremental generators for every archetype** + one-step `add ` that wires config, providers and commands atomically | Generated **typed registries** the app imports (`#generated/controllers`) rather than string wiring | AdonisJS, Laravel | +| Typed end-to-end data | Server functions with schema-validated inputs (Standard Schema), typed errors + redirects, composable middleware | **Single-flight mutations** (mutation returns refreshed query results in one round trip); **serialization-boundary type checking**; emitted client as an artifact (`encore gen client`) | SvelteKit, TanStack Start, Encore | +| Auth | A default that works out of the scaffold, not a shopping list | Pick one modern primitive and make it excellent (passkeys/WebAuthn) rather than abstracting five | Adonis/Laravel (table stakes); RedwoodSDK (frontier) | +| Jobs / durable workflows | Queue + scheduler + a **test fake** (`assertPushed`) + a worker command | Durable execution by **directive** (`"use workflow"`), deterministic replay across deploys/crashes | Laravel/Adonis (stakes); Vercel WDK (frontier) | +| Observability | Local dev dashboard with logs + resource health | **Automatic distributed tracing** across services with zero manual instrumentation, queryable in dev and prod | Encore (frontier); Laravel Pulse/Telescope (stakes) | +| Docs / agent readiness | `llms.txt` + per-page raw-markdown twins on the **public docs site** | **Runtime** MCP over the *running app* (traces, routes, DB, pub/sub) + published agent skills + package-authored AI guidelines that install transitively | Nuxt, Next 16, Encore, Laravel Boost, RedwoodSDK | +| Deployment | One command to a container/host, multiple targets | Portability guarantee: "deployment configuration changes; route authoring and server boundaries do not" | TanStack Start, Nitro | + +--- + +## 3. NetScript measured against that bar (verified current state) + +### 3.1 What exists and works + +- **Contract-first typed data path is real and current.** oRPC `^1.14.6` is a direct dependency of + both the server and the client packages: `packages/service/deno.json:22-25` + (`@orpc/server`, `@orpc/openapi`, `@orpc/client`, `@orpc/zod`) and `packages/sdk/deno.json:31-36` + (adds `@orpc/contract`, `@orpc/tanstack-query`). `@netscript/sdk` describes itself as "Service + discovery, oRPC clients, and cache-backed query factories" and exports `./client`, `./query`, + `./query-client`, `./collections`, `./streams`, `./discovery` (`packages/sdk/deno.json`). + **This clears the "emitted typed client" bar that only Encore also clears.** +- **Jobs/durable workflows exist as first-party plugins, not add-ons.** `@netscript/plugin-sagas` + — "durable saga orchestration, workflow APIs, and saga runtime metadata" + (`plugins/sagas/deno.json:4`); `@netscript/plugin-workers` — "background job scheduling, task + execution, and worker API endpoints"; `@netscript/plugin-triggers`; `@netscript/plugin-streams` + ("Durable Streams service"). Each exports a uniform seam set — `./contracts`, `./runtime`, + `./services`, `./cli`, `./aspire`, `./scaffold`, `./doctor`, `./streams` + (`plugins/sagas/deno.json`, `plugins/workers/deno.json`). Plus substrate packages + `@netscript/queue` (Deno KV / Redis / AMQP / Postgres adapters **with dead-letter stores per + backend**) and `@netscript/cron`. **No JS meta-framework in this teardown ships a first-party + saga/compensation abstraction. This is a genuine differentiator.** +- **Auth is first-party and multi-backend**: `packages/auth-better-auth`, `packages/auth-kv-oauth`, + `packages/auth-workos`, `packages/plugin-auth-core`, `plugins/auth` ("unified auth API, + single-active backend selection, auth database schema, and auth session streams"). **Ahead of + Next/Nuxt/SvelteKit/TanStack on this axis.** +- **Observability is OTEL-native**: `@netscript/telemetry` exports `./tracer`, `./context`, + `./instrumentation`, `./registry`, `./orpc`, `./hono`, `./ai`, `./otel`, `./query`, `./testing` + (`packages/telemetry/deno.json`) — i.e. **RPC and HTTP instrumentation is built in, and there is + a query surface plus a testing surface.** Aspire supplies the dashboard/graph. +- **Agent readiness is genuinely competitive on two of three fronts.** + 1. *Docs site*: `docs/site/_plugins/ai-tooling.ts` emits `/llms.txt` (tiered index) and + `/llms-full.txt` from live page data (lines 12–15, 116, 120), published at + `https://rickylabs.github.io/netscript/` (`docs/site/_config.ts:52`). + 2. *Scaffolded projects*: `netscript agent init` writes `llms.txt`/`llms-full.txt` into the user's + project with a `## Task router` section that is version-checked on regeneration + (`packages/cli/src/public/adapters/agent/deno-agent-docs-generator.ts:141,236`). + 3. *Runtime MCP*: `@netscript/mcp` — "Token-bounded MCP tools for NetScript diagnostics, + telemetry insights, docs, and CLI actions" (`packages/mcp/deno.json`), with an immutable + enumerable tool registry (`createToolRegistry`, `packages/mcp/src/application/tool-registry.ts:63`) + and flows for doctor/telemetry, bounded CLI execution under a `CommandPolicy`, service + + operation + schema discovery over an endpoint directory, export-surface discovery, and + `record_drift`. Exposed via `netscript agent mcp` + (`packages/cli/src/public/features/agent/mcp/agent-mcp-command.ts`), with a + `command-policy-parity_test.ts` guarding CLI↔MCP parity. + **This is Encore-class in kind. Encore ships 19 tools across nine areas; NetScript's registry + is smaller and should be counted before claiming parity.** +- **CLI generation depth is above every JS competitor except Adonis.** Feature groups: + `agent config contracts db deploy generate init marketplace plugins root services ui` + (`packages/cli/src/public/features/`). Notably `services add`, `services add-handler`, + `services generate`, `services configure` (Adonis-class incremental generation); + `db` with 16 subcommands including `introspect`, `studio`, `seed`, `migrate`, `resolve`, + `validate`; `ui add|list|update|remove` over a registry (`packages/cli/src/public/features/ui/registry.ts`, + shadcn-style); `plugins install|new|scaffold|doctor|update|dispatch|host`; `deploy` with + `deno-deploy`, `package-cli`, `build`, `target`, `logs`, `status`, `upgrade`; `marketplace + publish|search`. + +### 3.2 Gaps — classified + +| # | Gap | Class | Evidence / bar | +|---|---|---|---| +| G1 | **No single-flight mutation semantics.** oRPC + TanStack Query gives typed calls and cache factories (`packages/sdk/deno.json:35`), but nothing in the exports (`./query`, `./query-client`, `./collections`) advertises "this mutation returns the refreshed queries in one round trip". SvelteKit ships this as a named, documented guarantee. | API/type-system seam | | +| G2 | **No serialization-boundary type checking on the RPC seam**, and no documented escape hatch equivalent to `createServerFn({ strict: { input: false } })`. Contract-first frameworks are *more* exposed to this than directive-based ones. | API/type-system seam | | +| G3 | **No durable-execution ergonomics story.** Sagas exist as a plugin with contracts/runtime; the 2026 frontier is a one-line opt-in that survives deploys and crashes with deterministic replay. NetScript should either match the ergonomic (a directive/decorator) or explicitly position sagas as *compensating orchestration*, which is a different and arguably stronger product — but that positioning is not stated anywhere in `README.md:11-18`. | product-expectation / docs-discovery | | +| G4 | **No `netscript` equivalent of `node ace add `** that installs a package *and* wires providers/commands/preloads/config atomically. `plugins install` exists; whether it reaches Adonis's atomicity is unverified from manifests alone. | scaffold/generation | | +| G5 | **No generated typed registry import surface** analogous to `import { controllers } from '#generated/controllers'`. NetScript has `generate runtime-schemas` and `generate plugins` (`packages/cli/src/public/features/generate/`), but the *ergonomic payoff* — a typed object you import instead of strings — is the part users praise. | scaffold/generation | | +| G6 | **No job-testing fake in the public surface.** `@netscript/queue` exports `./testing` and `@netscript/telemetry` exports `./testing`, but `plugins/workers/deno.json` exports no `./testing`. Adonis's `QueueManager.fake()` + `fake.assertPushed(Job, { payload, delay })` is table stakes for jobs. | API/type-system seam | | +| G7 | **MCP tool count and coverage areas are unpublished.** Encore markets "19 tools across nine areas"; Laravel markets an inspector + testable servers + a Record Rule that persists agent rules to `.ai/rules`. NetScript has the machinery (`packages/mcp/src/application/`) but no comparable published tool catalog on the docs site. | docs/discovery | , | +| G8 | **No transitive AI-guidelines contract for third-party plugins.** Laravel Boost auto-loads guidelines shipped by any installed package. NetScript has a marketplace (`packages/cli/src/public/features/marketplace/`) and a plugin protocol — the natural place for `plugin ships agent guidelines → installing it teaches the agent` — but nothing in `plugins/*/deno.json` exports an agent/guidelines surface. | docs/discovery + plugin-composition | | +| G9 | **No published agent-skills artifact.** RedwoodSDK serves `.well-known/agent-skills/index.json` and `.well-known/api-catalog`. NetScript's docs site emits llms.txt only (`docs/site/_plugins/ai-tooling.ts:116,120`). | docs/discovery | | +| G10 | **Substrate ecosystem risk, inherited.** Fresh is Preact-only with an acknowledged smaller ecosystem; `packages/fresh-ui` is *excluded from the root `deno task check`* (`deno.json` check task exclude regex `^(packages/(fresh-ui)\|…)`), i.e. the UI package is not type-checked by the default gate. | runtime correctness / gate integrity | `deno.json` (`tasks.check`), | +| G11 | **Praise-line is unowned.** Every framework in this teardown has one crisp thing users repeat (Next: ubiquity; Nuxt: docs; SvelteKit: mental model; TanStack: explicitness; Encore: free tracing; Laravel: completeness; Fresh: zero-JS). `README.md:3-5` leads with six adjectives and no single claim. A meta-framework with no repeatable one-liner does not get repeated. | product-expectation / positioning | `README.md:3-18` | + +### 3.3 Where NetScript is genuinely differentiated + +Ranked by defensibility, not by effort: + +1. **First-party durable *orchestration* (sagas + compensation), not just durable *execution*.** + Vercel WDK gives replay; nobody in the JS meta-framework space gives sagas with compensation as + a framework primitive with contracts, runtime, CLI, Aspire wiring and a doctor + (`plugins/sagas/deno.json`). Encore gives pub/sub; Laravel gives queues. **This is the strongest + unowned position on the board.** +2. **Aspire as the local-graph orchestrator.** Encore's headline is "no Docker Compose needed — + real Postgres, local Pub/Sub broker, object storage" (). NetScript + gets the same outcome from a *standard, non-proprietary* orchestrator (`packages/aspire`, + `netscript generate aspire`), where Encore's equivalent is proprietary and pushes toward Encore + Cloud. **"Encore's dev experience without Encore's cloud" is a sayable sentence.** +3. **Plugin composition as the extension unit with a uniform seam contract.** Every plugin exports + the same set — `./contracts ./runtime ./services ./cli ./aspire ./scaffold ./doctor ./streams`. + Nuxt modules are the closest analogue and have no equivalent typed seam vocabulary. **This is + also the natural carrier for G8 (transitive agent guidelines) — a plugin that teaches the agent + when installed is a category-defining move Laravel only does at package level in PHP.** +4. **Contract-first as the *reason* the agent story works.** Encore's own argument is "because + infrastructure is declared in code, AI coding assistants can understand and modify your full + stack". NetScript's oRPC contract + generated runtime schemas + MCP endpoint directory is the + same argument with a *portable* contract format (oRPC/OpenAPI) rather than a proprietary one. +5. **Deno-native single toolchain.** No node_modules, no bundler config, `deno doc` as the public + surface, JSR as the registry. Competitors' scaffolds all begin with a package-manager choice + prompt. + +### 3.4 Table stakes NetScript currently misses (do these before differentiating) + +In priority order, each traceable to a gap above: + +- **G11 → one claim.** Pick the sentence. Recommended: *"The only TypeScript framework where + background jobs, sagas and streams are first-party — behind one typed contract."* +- **G1/G2 → close the RPC seam to 2026 spec.** Single-flight mutation + serialization type checking + are the two things a reviewer coming from SvelteKit/TanStack will look for first and not find. +- **G6 → ship `plugins/workers/./testing` with an `assertDispatched`-style fake.** Cheapest + credibility win on the whole list; Adonis proves the API shape. +- **G7/G9 → publish the MCP tool catalog and an agent-skills artifact on the docs site.** The + machinery exists (`packages/mcp/src/application/tool-registry.ts:63`); the *marketing surface* + does not. Competitors win this axis by publishing, not by building. +- **G10 → get `packages/fresh-ui` inside the default check gate** or state publicly why it is out. + An excluded package in the root gate is the kind of thing an evaluator finds and discounts the + whole quality story for. +- **G4/G5 → make `plugins install` atomic and emit a typed registry.** Adonis's `node ace add` + and `#generated/controllers` are the two ergonomics users cite when they say a framework "feels + finished". + +--- + +## 4. Source index + +Next.js: · + · + · · + · + +Nuxt: · · + · · + +SvelteKit: · + · +TanStack Start: · + · · + +RedwoodSDK: · · + · · + +AdonisJS: · + · + +Encore: · · + · · + · · + +Laravel: · · + · +Fresh: · · + · `curl https://jsr.io/@fresh/core/meta.json` +Sentiment (secondary, labelled in text): · + · + + +## 5. Confidence and residual unknowns + +- **High confidence:** all vendor-doc capability claims (§1), all NetScript manifest/path claims (§3.1). +- **Medium:** G4 (`plugins install` atomicity) and G5 (typed registry) were inferred from directory + listings, not from reading the command implementations — a follow-up should read + `packages/cli/src/public/features/plugins/install/` and `.../generate/plugins/` before these are + written into an issue. +- **Medium:** NetScript's MCP tool *count* was not enumerated (flows were read, the registry array + was not). Do not publish a "N tools" number until `packages/mcp/src/application/tool-registry.ts` + is read in full. +- **Low/labelled:** all `[sentiment]` items; State of JS figures are quoted through secondary + aggregators, not the primary survey export. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/external/orpc.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/external/orpc.md new file mode 100644 index 0000000000..1a7f13e489 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/external/orpc.md @@ -0,0 +1,704 @@ +# oRPC extension model vs. NetScript's plugin → SDK/client contribution seam + +**Scope:** oRPC's extension mechanisms as documented upstream and as *actually shipped in the version +NetScript pins*, mapped against NetScript's current wrapper surfaces (`@netscript/sdk`, +`@netscript/service`, `@netscript/plugin`) and the plugin-contribution seam. +**Baseline:** worktree `plan/fable5-remediation-roadmap` @ `fac9e339042c` (2026-08-08). +**Method:** primary docs + the pinned package `.d.ts` in the Deno npm cache + repo source + one +executed `deno check` type-probe. Every load-bearing claim carries a citation. + +--- + +## 0. What NetScript pins (verified) + +| Package | deno.json range | deno.lock resolved | npm `latest` | +|---|---|---|---| +| `@orpc/client` | `^1.14.6` (`deno.json:215`) | `1.14.6` (`deno.lock:1355`) | `1.14.15` | +| `@orpc/contract` | `^1.14.6` (`deno.json:216`) | `1.14.6` (`deno.lock:1364`) | `1.14.15` | +| `@orpc/openapi` | `^1.14.6` (`deno.json:217`) | `1.14.6` (`deno.lock:1396`) | `1.14.15` | +| `@orpc/otel` | `^1.14.7` (`deno.json:218`) | `1.14.7` (`deno.lock:1410`) | `1.14.15` | +| `@orpc/server` | `^1.14.6` (`deno.json:219`) | `1.14.6` (`deno.lock:1418`) | `1.14.15` | +| `@orpc/tanstack-query` | `^1.14.6` (`deno.json:220`) | `1.14.6` (`deno.lock:1501`) | `1.14.15` | +| `@orpc/zod` | `^1.14.6` (`deno.json:221`) | `1.14.6` (`deno.lock:1509`) | `1.14.15` | + +Facts established by direct inspection (not inference): + +- **`.resources/deps-docs/` does not exist** in this worktree (`ls .resources` → exit 2). There is a + `resources/` dir containing only `design/`. No local oRPC extract exists; this report reads the + cached package tarballs at `/home/codex/.cache/deno/npm/registry.npmjs.org/@orpc/*/1.14.6/dist/` + and the live docs. +- **npm publish timeline** (`registry.npmjs.org/@orpc/server`): `1.14.6` = 2026-06-12, + `1.14.15` = 2026-08-07. NetScript is **~2 months / 9 patch releases behind**, but only by + *lockfile*: the declared range `^1.14.6` already admits `1.14.15`. +- **`@orpc/server`'s public surface is byte-identical between 1.14.6 and 1.14.15.** Diffing the + export lists of `dist/index.d.ts`, `dist/plugins/index.d.ts`, and `dist/adapters/fetch/index.d.ts` + from the cached 1.14.6 against the freshly downloaded `server-1.14.15.tgz` produced **no + differences**. `deno task deps:latest` reports the gap; `deno outdated --update` closes it. + → **Conclusion: the pinned version is not the constraint. Every gap below is NetScript's own.** +- **`dist-tags` on `@orpc/server` include `beta: 2.0.0-beta.25`.** oRPC v2 is in public beta; the + v2 `main` branch already carries `feat(rpc): restrict RPC handlers to POST, PUT, PATCH and DELETE + by default` and `feat(server): add MethodOverrideHandlerPlugin` + (`gh api repos/dinwwwh/orpc/commits`, both absent from the 1.14.15 tarball). Those are the real + future breakages — see §7. +- **`orpc.unnoq.com` 301-redirects to `orpc.dev`** (observed on every fetch). Canonical doc host is + now `orpc.dev`; URLs below are the post-redirect canonical ones. The pinned `.d.ts` JSDoc already + emits `orpc.dev` links (e.g. `@orpc/server/dist/shared/server.qKsRrdxW.d.ts:105`). + +--- + +## 1. oRPC's extension model — mechanism by mechanism + +### 1.1 Context typing: initial vs execution context + +Two distinct context layers, both type-tracked +(): + +- **Initial context** — declared with `os.$context()`, *supplied by the caller* at + `handler.handle(request, { context: { … } })`. Doc snippet: + `const base = os.$context<{ headers: Headers, env: { DB_URL: string } }>()`. +- **Execution context** — produced during the call by middleware `next({ context })`. + +The builder tracks both as separate type parameters: + +```ts +// @orpc/server@1.14.6 dist/index.d.ts:473,491 +declare class Builder + $context(): Builder, U, …>; +``` + +Merging is a real type operation, not a cast +(`dist/shared/server.qKsRrdxW.d.ts:6-7`): + +```ts +type MergedInitialContext = TInitial & Omit; +type MergedCurrentContext = Omit & U; +``` + +`InferRouterInitialContexts` / `InferRouterCurrentContexts` let a host *derive* the context a router +demands (`server.qKsRrdxW.d.ts:161,170`). **This is the single most important mechanism for a +plugin host** — it is how a framework can compile-time prove "the context factory supplies what +every mounted plugin router requires." + +### 1.2 Middleware + +`Middleware` is a 3-arg function returning `{ output, context }` +(`server.qKsRrdxW.d.ts:107-109`, `78-101`): + +```ts +interface Middleware { + (options: MiddlewareOptions, + input: TInput, output: MiddlewareOutputFn): Promisable>; +} +interface MiddlewareOptions<…> { context; path; procedure; signal?; lastEventId; next; errors } +``` + +Key properties: + +- `next({ context })` **adds** to the execution context and the addition is captured in + `TOutContext`; `.use()` returns a builder whose `TCurrentContext` is + `MergedCurrentContext` (`index.d.ts:533`). +- **Dependent context**: `os.$context().middleware(…)` declares a middleware that *requires* `T`; + applying it to a builder whose current context lacks `T` is a type error + (`index.d.ts:517-518`; docs ). +- `DecoratedMiddleware.mapInput` / `.concat` compose middlewares before they reach a procedure + (`index.d.ts:440-459`). +- **Dedupe**: `os.$config({ dedupeLeadingMiddlewares: false })` + (`BuilderConfig`, `index.d.ts:462-466`; + ). "Deduplication occurs only if the + router middlewares is a subset of the leading procedure middlewares and appears in the same + order." +- Middleware can be attached at **procedure**, **router** (`RouterBuilder.use`), or **contract + implementer** level (`RouterImplementer.use`, `index.d.ts:721`). + +### 1.3 Interceptors — six distinct slots + +Interceptors are `Interceptor` functions (from `@orpc/shared`), *not* classes. +Server side (`@orpc/server/dist/shared/server.7cEtMB30.d.ts:47-62`): + +```ts +interface StandardHandlerOptions { + plugins?: StandardHandlerPlugin[]; + interceptors?: Interceptor, Promise>[]; + rootInterceptors?: Interceptor, Promise>[]; + clientInterceptors?: Interceptor, Meta>, Promise>[]; +} +``` + +Client side (`@orpc/client/dist/shared/client.2jUAqzYU.d.ts:30-34`): + +```ts +interface StandardLinkOptions { + interceptors?: Interceptor, Promise>[]; + clientInterceptors?: Interceptor, Promise>[]; + plugins?: StandardLinkPlugin[]; +} +``` + +Plus a fetch-adapter slot (`@orpc/client/dist/adapters/fetch/index.d.ts`): + +```ts +interface LinkFetchClientOptions extends ToFetchRequestOptions { + fetch?: (request, init, options, path, input) => Promise; + adapterInterceptors?: Interceptor, Promise>[]; + plugins?: LinkFetchPlugin[]; +} +``` + +So the extension surface is **6 interceptor arrays + 3 plugin arrays**, each with a different +options shape and a different point in the request lifecycle +(, ). + +### 1.4 Plugins — a two-method interface, nothing more + +```ts +// server: dist/shared/server.7cEtMB30.d.ts:7-10 +interface StandardHandlerPlugin { + order?: number; + init?(options: StandardHandlerOptions, router: Router): void; +} +// client: dist/shared/client.2jUAqzYU.d.ts:5-8 +interface StandardLinkPlugin { + order?: number; + init?(options: StandardLinkOptions): void; +} +``` + +A plugin is *a function that mutates the handler/link options object at construction time* — it +pushes interceptors, sets defaults, and orders itself relative to other plugins via `order`. +`CompositeStandardHandlerPlugin` / `CompositeStandardLinkPlugin` sort and fan out `init`. +Registration is a plain `plugins: []` array on the handler/link constructor +(: `new RPCHandler(router, { plugins: [new CORSPlugin({…})] })`). + +**Built-ins shipped in the pinned 1.14.6** (verified from the tarball export lines, not the docs): + +| Entrypoint | Exports | +|---|---| +| `@orpc/server/plugins` | `BatchHandlerPlugin`, `CORSPlugin`, `RequestHeadersPlugin`, `ResponseHeadersPlugin`, `SimpleCsrfProtectionHandlerPlugin`, `StrictGetMethodPlugin`, `experimental_RethrowHandlerPlugin` | +| `@orpc/server/fetch` | `BodyLimitPlugin`, `CompressionPlugin` (+ `FetchHandlerPlugin`, `CompositeFetchHandlerPlugin`) | +| `@orpc/client/plugins` | `BatchLinkPlugin`, `ClientRetryPlugin`, `DedupeRequestsPlugin`, `RetryAfterPlugin`, `SimpleCsrfProtectionLinkPlugin` | +| `@orpc/contract/plugins` | `RequestValidationPlugin`, `ResponseValidationPlugin` | +| `@orpc/openapi/plugins` | `OpenAPIReferencePlugin` | + +Note the adapter-level extension hook: `LinkFetchPlugin` adds `initRuntimeAdapter?(options)` on top +of `StandardLinkPlugin` — a plugin may reach the fetch layer specifically. + +### 1.5 Client links + +`ClientLink` is a one-method interface (`client.i2uoJbEp.d.ts:25-27`): + +```ts +interface ClientLink { + call: (path: readonly string[], input: unknown, options: ClientOptions) => Promise; +} +``` + +`createORPCClient(link)` turns it into a proxy-typed nested client. Extension points on the concrete +`RPCLink` (`StandardRPCLinkCodecOptions`, `client.B3pNRBih.d.ts`): + +- `url`, `method`, `fallbackMethod`, `maxUrlLength` — all `Value<…, [options, path, input]>`, i.e. + **per-call functions that receive the client context**. +- `headers?: Value, [options: ClientOptions, path, input]>` + — this is oRPC's *native credential-injection seam*. Docs snippet: + `headers: async ({ context }) => ({ 'x-api-key': context?.something ?? '' })` + (). +- `customJsonSerializers?: readonly StandardRPCCustomJsonSerializer[]` — extend the wire format. +- `DynamicLink` (`@orpc/client` index.d.ts:218) resolves a *different link per call* from + `(options, path, input)` — oRPC's equivalent of tRPC's `splitLink`. + +Crucially, **`ClientContext = Record` is a free type parameter** +(`client.i2uoJbEp.d.ts:5`) and `ClientRest` makes it *required at the call site* when non-empty +(`client.i2uoJbEp.d.ts:16`): if a link declares `RPCLink<{ token: string }>`, every call must pass +`{ context: { token } }` or fail to compile. That is the composition primitive for +plugin-contributed credentials. + +### 1.6 Contract-first router extension + +`implement(contract)` returns an `Implementer` that is a *structural mirror of the contract tree*, +each node carrying `$context`, `$config`, `.use`, `.router`, `.lazy`, `.handler` +(`@orpc/server/dist/index.d.ts:744-759`, `702-742`; +). The contract builder `oc` +(`@orpc/contract`) is transport-free — it carries `.route`, `.meta`, `.errors`, `.input`, `.output` +only. `EnhancedRouter` (`server.ChyoA9XY.d.ts`) is the type-level result of applying router-level +middlewares/prefix/tags/errors to a subtree, preserving each leaf's own generics. + +`setHiddenRouterContract` / `getHiddenRouterContract` (`index.d.ts:799-800`) let a host attach the +originating contract to an assembled router — the mechanism a plugin registry would use to recover +"what contract does this mounted subtree implement." + +### 1.7 Procedure metadata + +`os.$meta(initialMeta)` fixes a metadata type for the whole builder +(`index.d.ts:497`); `.meta(m)` spread-merges (`index.d.ts:540`). Middleware reads it off the +procedure handle it is given: `procedure['~orpc'].meta.cache` +(). `MiddlewareOptions.procedure` and +`ProcedureHandlerOptions.procedure` are both typed `Procedure<…, TMeta>` +(`server.qKsRrdxW.d.ts:48,96`), so metadata is **typed all the way into middleware** — this is the +native home for policy metadata (`requiresAuth`, `scopes`, `rateLimit`, `cache`). + +### 1.8 Type-safe errors + +`.errors({ CODE: { status, message, data: } })` builds an `ErrorMap`; the handler +receives an `ORPCErrorConstructorMap` (`errors.NOT_FOUND({ data })`) and the client's promise type +carries the error union (`server.qKsRrdxW.d.ts:10-15,116`; +): + +```ts +type ProcedureClient = + Client, InferSchemaOutput, ErrorFromErrorMap>; +type ClientPromiseResult = PromiseWithError; +``` + +`safe()` then **discriminates on `isDefined`** (`@orpc/client/dist/index.d.ts:139-158`): + +```ts +declare function safe(promise: ClientPromiseResult): Promise>; +// SafeResult = success | [Exclude>, undefined, false, false] | [Extract>, undefined, true, false] +``` + +`InferClientErrors` / `InferClientErrorUnion` expose the per-procedure error union to consumers +(`client.i2uoJbEp.d.ts:71-81`). + +--- + +## 2. Contrast: tRPC's extension model + +| Concern | oRPC 1.14.x | tRPC v11 | +|---|---|---| +| Server extension unit | `StandardHandlerPlugin` (`init(options, router)`) + 3 interceptor arrays | Middleware (`t.middleware`) only; no handler-plugin concept | +| Client extension unit | `ClientLink` + `StandardLinkPlugin` + 3 interceptor arrays | `TRPCLink` chain (`links: []`), observable-based | +| Link composition | `DynamicLink` resolves per call; plugins push interceptors | `splitLink`, `loggerLink`, `retryLink`; "links execute in the order they are added … and again in reverse when handling a response" () | +| Custom link shape | `call(path, input, options) => Promise` — a plain async fn | `() => ({ next, op }) => observable(observer => …)` — requires the observable runtime () | +| Per-call client context | `ClientContext` type param, **required at the call site** when non-empty | No typed per-call context; `httpLink({ headers: ctx => … })` reads `op`, untyped | +| Contract-first | First-class: `@orpc/contract` + `implement()`; contract has no server dep | Not supported — the router *is* the contract | +| Procedure metadata | `os.$meta()`, typed into middleware via `procedure['~orpc'].meta` | `initTRPC.meta()` + `.meta()`, read via `opts.meta` — comparable | +| Type-safe errors | `.errors({…})` → per-procedure error union on client, `safe()` discriminates `isDefined` | Fixed `TRPCError` code enum + `errorFormatter`; the tRPC error-handling docs cover codes and `getHTTPStatusCodeFromError` but **do not document per-procedure client-side error payload inference** () | +| OpenAPI | First-party `@orpc/openapi` + `.route()` on the contract | Community (`trpc-openapi`) | + +**Takeaway for NetScript:** oRPC already provides *more* extension surface than tRPC in exactly the +four dimensions a plugin system needs — typed per-call client context, contract-first router +composition, typed procedure metadata, and per-procedure typed errors. NetScript's problem is not +a missing upstream primitive; it is that its wrappers **erase** all four. + +--- + +## 3. What NetScript has today — what exists and works + +1. **Real oRPC handler plugins are authored in-repo and do work.** + `packages/telemetry/src/orpc/tracing-plugin.ts` wraps the first-party `ORPCInstrumentation` from + `@orpc/otel`; `packages/telemetry/src/orpc/error-plugin.ts` and `packages/logger/orpc-plugin.ts` + implement `init(handlerOptions, router)` and push root/client interceptors + (`packages/logger/orpc-plugin.ts:173`). These are legitimate `StandardHandlerPlugin`s. +2. **A composed default plugin stack exists.** + `createRPCPlugins()` (`packages/service/src/primitives/handlers.ts:69-108`) assembles + Tracing → ErrorHandling → Logging → CORS and appends `config.plugins`. + `createOpenAPIHandler` additionally adds `ZodSmartCoercionPlugin` + (`handlers.ts:164`). +3. **Client link uses genuine oRPC plugin composition.** + `createHttpClientLink` builds an `RPCLink` with `ClientRetryPlugin` + `DedupeRequestsPlugin`, + context-driven dedupe groups, `inferRPCMethodFromContractRouter`, and a custom `fetch` that opens + a CLIENT span (`packages/sdk/src/client/http-client-link.ts:75-156`). This is idiomatic oRPC. +4. **Contract-first is real.** `BASE_PLUGIN_CONTRACT_ROUTES` is built with the actual contract + builder `oc` and carries the base error map + capabilities output schema + (`packages/plugin/src/contract-base/domain/base-contract.ts:106-114`); `BasePluginContract` + constrains extra routes to `AnyContractRouter`, so `satisfies` is a genuine guard + (`base-contract.ts:73-79`). +5. **A shared, typed error map exists.** `BASE_PLUGIN_ERRORS` (`NOT_FOUND` 404 / `VALIDATION_ERROR` + 422 / `INTERNAL` 500) with Standard-Schema `data` payloads, shaped for spread into + `.errors({…})` (`packages/plugin/src/contract-base/domain/base-errors.ts:98-118`). +6. **The upstream TanStack bridge is wired.** `createServiceQueryUtils` really calls + `createTanstackQueryUtils` (`packages/sdk/src/query-client/create-service-query-utils.ts:12,57`). +7. **Desktop transport reuses oRPC's MessagePort link** with custom JSON serializers + (`packages/sdk/src/desktop/application/desktop-rpc-client.ts:18-21`). +8. **The reference plugin uses `$context` correctly.** + `workersContractV1.$context()` + (`plugins/workers/services/src/routers/router-context.ts:41-43`) preserves the implementer type + and derives handler types with a mapped type rather than `any` + (`router-context.ts:59-62`). + +--- + +## 4. Gaps — classified + +### G1. There is no plugin → SDK/client contribution group at all +**Class: plugin-composition failure.** + +`PluginContributions` (`packages/plugin/src/config/domain/plugin-contributions.ts:12-39`) enumerates +`cli`, `services`, `backgroundProcessors`, `streamTopics`, `databaseSchemas`, +`runtimeConfigTopics`, `contractVersions`, `e2e`, `telemetry`, `migrations`, `aspire`, `doctor`. +**None of these is a client-side contribution.** There is no `linkPlugins`, `handlerPlugins`, +`clientContext`, `headers`, `queryFactories`, `invalidation`, or `policy` group. A plugin therefore +has *no declarative way* to influence the SDK client, the query layer, or the server handler stack. + +Corroborating: `ServiceContribution` is three fields — `name`, `entrypoint`, `port?` +(`packages/plugin/src/config/domain/service-contribution.ts:2-9`). + +Sub-finding (same class): the one CLI contribution slot is a **closed literal union naming a +first-party plugin**: `readonly doctorChecks?: readonly 'auth-backend'[]` +(`plugin-contributions.ts:16`). A third-party plugin cannot contribute a doctor check without +editing framework source. + +### G2. The service client has no credential/header injection seam +**Class: API/type-system seam.** + +- `CreateServiceClientOptions` (`packages/sdk/src/ports/service-client.ts:203-222`) exposes + `contract, serviceName, routerName, protocol, apiPath, apiVersion, port, timeout, + propagateTraceContext`. **No `headers`, no `context`, no `plugins`, no `interceptors`, + no `fetch`.** +- `createHttpClientLink`'s `headers` callback is hard-coded to `Content-Type` + trace propagation + (`packages/sdk/src/client/http-client-link.ts:83-101`) and its `plugins` array is a **literal** + (`http-client-link.ts:102-126`) — nothing can be appended. +- `DefineServiceConfig` (`packages/sdk/src/presets/define-services.ts:22-44`) adds only `options` + and `queryPath`; same absence. +- The desktop link is likewise closed (`desktop-rpc-client.ts:18-21`). + +oRPC hands this over for free (`headers: Value<…, [ClientOptions, path, input]>`, +`plugins: StandardLinkPlugin[]`, `interceptors`, `clientInterceptors`, `adapterInterceptors`, +`fetch`). **NetScript closes all six.** + +### G3. Client context is fixed and closed — it cannot carry plugin-contributed credentials +**Class: API/type-system seam.** + +`ServiceClientContext` (`packages/sdk/src/ports/service-client.ts:129-155`) is a **concrete +interface** with exactly `signal`, `cache`, `retry`, `retryDelay`, `shouldRetry`, `onRetry`, +`traceHeaders`. It is not a type parameter. Every call site is typed +`ServiceClientMethod = (input, options?: { context?: ServiceClientContext }) => Promise` +(`service-client.ts:168-171`). + +Worse on the query side: + +```ts +// packages/sdk/src/ports/service-query-utils.ts:33 +/** Empty oRPC client context used by SDK-created service clients. */ +export type ServiceQueryClientContext = Record; +``` + +The TanStack surface **hard-codes an empty client context**. So even if a plugin could reach the +link, no per-call token/tenant/scope could be threaded through `queryOptions({ context })` — the +exact mechanism oRPC's docs prescribe for `x-api-key`-style injection. + +### G4. `ServiceClient` re-derives oRPC's client instead of using `RouterClient` — and drops the error type +**Class: API/type-system seam. Verified by executed type-check.** + +`ServiceClientShape` / `ServiceClientMethod` (`service-client.ts:168-196`) reconstruct the client +tree structurally, returning `Promise`. oRPC returns +`ClientPromiseResult>` +(`server.qKsRrdxW.d.ts:116-117`). **The `TError` channel is discarded at the SDK boundary.** + +Consequence, and this is the sharpest finding in the report: + +`packages/sdk/src/client/errors.ts:86` declares `safe(promise): Promise>` +— one generic, `TError` defaulted to `unknown` (`errors.ts:49`). Then +`isDefinedError(error: T): error is Extract` (`errors.ts:75`). With +`T = unknown`, `Extract` evaluates to `never`. + +Executed probe (scratchpad file, root `deno.json`): + +```ts +const [error, data] = await safe(p); +if (error && isDefinedError(error)) { const c = error.code; } +``` + +``` +$ deno check --unstable-kv --config deno.json /probe.ts +TS2339 [ERROR]: Property 'code' does not exist on type 'never'. + const c = error.code; +Found 2 errors. +``` + +**The SDK's documented error-handling pattern does not compile.** The published docs assert the +opposite: + +- `docs/site/services-sdk/how-to/discover-services.md:138-146` — "narrow defined errors with + `isDefinedError`" then `// error.code and error.data are typed from the contract`. +- `docs/site/services-sdk/sdk.md:199` — the same `safe`/`isDefinedError` snippet. +- `packages/sdk/README.md:156` lists `isDefinedError` as a `./client` export. + +And the guard that should have caught it is neutralised: the README doctest **re-declares** the +helpers with different signatures rather than importing them — +`declare function safe(value: Promise): Promise<[unknown, T | undefined]>;` and +`declare function isDefinedError(error: unknown): error is { readonly code: string; readonly data: unknown };` +(`packages/sdk/tests/readme-doctest_test.ts:36-37`). The doctest passes against a fiction. + +This is simultaneously a **docs/discovery failure** (docs claim a capability that is absent) and an +**API/type-system seam** (the wrapper drops a generic). + +### G5. The plugin contract binder erases the implementer type +**Class: API/type-system seam.** + +```ts +// packages/plugin/src/service/presentation/plugin-contract-binder.ts:18 +export type PluginContractRouter = object; +``` + +`PluginContractImplementer.$context(): PluginContractRouter` throws away +`Implementer`; route keys are then recovered +structurally by probing for `{ handler: (...args: never[]) => unknown }` +(`plugin-contract-binder.ts:33-35`). Consequences: + +- **No `.use()` is exposed on the binder at all** — a plugin cannot contribute oRPC middleware + through the sanctioned seam, even though `RouterImplementer.use` exists upstream + (`@orpc/server/dist/index.d.ts:721`). +- Contract error maps and metadata do not survive to the handler map type. +- First-party plugins annotate the result as `Readonly>` + (`plugins/workers/services/src/router.ts:11`, `plugins/sagas/services/src/router.ts:11`, + `plugins/auth/services/src/router.ts:11`, `plugins/triggers/services/src/router.ts:15`). + +Divergence worth flagging (**scaffold/generation failure**): the CLI scaffold emits +`bindPluginContract(...)` (`packages/cli/src/public/features/plugins/new/new-plugin-use-case.ts:660,667`, +asserted by `new-plugin_test.ts:75`), but **no first-party plugin uses it** — they call +`assemblePluginContractRouter` directly, and `workers` bypasses the seam entirely with +`workersContractV1.$context()`. Generated plugins therefore start on a +different, less-used path than the reference implementations. + +### G6. `ServiceHandlerPlugin` is an untyped shim; `StandardHandlerOptions` never reaches plugin authors +**Class: API/type-system seam.** + +```ts +// packages/service/src/types.ts:216-225 +export interface ServiceHandlerPlugin { + readonly order?: number; + init?(options: unknown, router: unknown): void; + initRuntimeAdapter?(options: unknown): void; +} +``` + +Upstream gives `init?(options: StandardHandlerOptions, router: Router)`. Because +NetScript hands over `unknown`, every plugin author must cast before touching +`options.interceptors` / `rootInterceptors` / `clientInterceptors` — which is exactly what the +in-repo logger plugin does (`packages/logger/orpc-plugin.ts` redeclares its own +`RootLoggingInterceptorOptions` / `ClientLoggingInterceptorOptions` shims, lines 11-42). The +framework's own plugins are the proof that the seam is unusable as typed. + +### G7. The `plugins` option is unreachable from the builder path +**Class: runtime correctness / plugin-composition failure.** + +`RPCHandlerConfig.plugins` exists (`packages/service/src/primitives/handlers.ts:45`) — but the only +non-doc call site in the repo is: + +```ts +// packages/service/src/builder/service-rpc.ts:57 +const rpcHandler = createRPCHandler(options?.rpcRouter ?? router, { serviceName, debug }); +``` + +`plugins` is never populated. And `ServiceBuilder.withRPC` accepts only +`{ rpcPath, apiPath, debug, traceContext, rpcAliases, deprecatedRpcRoutes, rpcRouter }` +(`packages/service/src/builder/service-builder-impl.ts:222-240`) — **no `plugins`, no +`interceptors`**. `createPluginService` likewise passes none +(`packages/plugin/src/service/presentation/create-plugin-service.ts:165-178`). So the documented +extension point is dead code on every path a plugin or app actually takes. + +### G8. "Middleware" in NetScript means Hono middleware, not oRPC middleware +**Class: API/type-system seam / docs-discovery failure.** + +```ts +// packages/service/src/types.ts:243 +export type ServiceMiddleware = MiddlewareHandler; // from hono +``` + +`createPluginService`'s `config.middleware` feeds `builder.use(middleware)` +(`create-plugin-service.ts:157-159`) → Hono. There is **no** seam anywhere that accepts an +`@orpc/server` `Middleware`. Credential extraction, tenant resolution, and policy enforcement +therefore cannot produce *typed execution context*; they can only mutate Hono's context bag. + +### G9. The oRPC context contract is unverified at the host boundary +**Class: runtime correctness.** + +```ts +// packages/service/src/types.ts:270 +export type ContextFactory = (context: Context) => Record; +``` + +`buildRpcContext` then bolts on `ctx.db`, `ctx.traceHeaders`, `ctx.principal` +(`packages/service/src/builder/service-builder-impl.ts:255-281`), and +`packages/service/src/primitives/orpc-router.ts:10` declares +`OrpcRouter = Router>`. + +Meanwhile the workers connector *asserts* `WorkersRequestContext = { db; traceHeaders?; workers }` +(`plugins/workers/services/src/routers/router-context.ts:35-39`) and re-narrows at runtime with a +throw: + +```ts +// router-context.ts:63-69 +export function getWorkersRuntime(context: unknown): WorkersServiceRuntime { + const runtime = (context as Partial).workers; + if (!runtime) throw new Error('Workers service runtime missing from request context.'); +``` + +oRPC's `InferRouterInitialContexts` (`server.qKsRrdxW.d.ts:161`) exists precisely so the host can +*prove* the factory satisfies every mounted router. NetScript instead converts a compile-time +guarantee into a runtime `throw`. + +### G10. Two incompatible query/invalidation systems, neither aligned with oRPC keys +**Class: API/type-system seam + product-expectation drift.** + +- `createQueryFactory` (`packages/sdk/src/query/query-factory.ts`) is hand-rolled: it enumerates + procedures with `Object.keys(contract)` (**line 61**, so *flat contracts only* — a nested + contract router silently contributes zero actions), emits keys + `[resource, action, { input }]` (**line 147**) and `[resource, action]` (**line 165**), and + invalidates via a bespoke `getCacheProvider().invalidateQueries([resource, action])` + (**lines 56-58, 87-89**). +- `createServiceQueryUtils` delegates to upstream `createTanstackQueryUtils`, whose keys are + `[path: readonly string[], options: { type, input, fnOptions }]` + (`@orpc/tanstack-query/dist/index.d.ts:39`, generated by `generateOperationKey`, line 109). + +`defineServices` returns **both** (`packages/sdk/src/presets/define-services.ts:83-92`: +`clients`, `queries`, `queryUtils`). The two key algebras do not overlap, so +`queryClient.invalidateQueries(utils.orders.key())` cannot invalidate anything the `queries` factory +cached, and vice versa. Also unavailable on the hand-rolled path: `infiniteOptions`, +`experimental_streamedOptions`, `experimental_liveOptions`, `mutationKey`, and +`experimental_defaults` (`@orpc/tanstack-query/dist/index.d.ts:135-243`) — all of which +`@orpc/tanstack-query@1.14.6` already ships. + +### G11. Procedure metadata is entirely unused — the natural policy seam is empty +**Class: plugin-composition failure.** + +`grep -rnE '\$meta<|\.meta\(\{ *(policy|scope|auth|requires)'` over `packages/` + `plugins/` returns +**zero hits**. No contract calls `oc.$meta()`; no procedure calls `.meta({…})`. The only `.meta(` +matches in the repo are Zod schema annotations (`packages/aspire/config.ts:348…`). + +oRPC's metadata is typed into `MiddlewareOptions.procedure` (`server.qKsRrdxW.d.ts:96`) and +`ProcedureHandlerOptions.procedure` (line 48) — i.e. it is the designed home for +`requiresAuth` / `scopes` / `rateLimit` / `cache` policy that middleware and plugins read. NetScript +instead performs auth/authz as Hono-level path guards (`withAuthn`/`withAuthz`, +`service-builder-impl.ts:244-252`), which cannot see procedure identity. + +--- + +## 5. Mechanism → NetScript seam map + +| Seam NetScript needs | oRPC gives free (1.14.6, verified) | NetScript must add | Gap ref | +|---|---|---|---| +| **Credential injection (client)** | `RPCLink.headers: Value<…, [ClientOptions, path, input]>`; `ClientContext` as a type parameter required at the call site | Open `CreateServiceClientOptions` to `headers`/`context`/`plugins`/`interceptors`; make `ServiceClientContext` a *parameter* with a NetScript base, not a closed interface | G2, G3 | +| **Header/trace/tenant contribution from a plugin** | `StandardLinkPlugin.init(options)` pushing `clientInterceptors`; `RequestHeadersPlugin`/`ResponseHeadersPlugin` server-side | A `linkPlugins` (client) + `handlerPlugins` (server) contribution group in `PluginContributions`, and an aggregation point that concatenates them into the link/handler arrays | G1, G2, G7 | +| **Query factories** | `createTanstackQueryUtils` with `queryOptions/mutationOptions/infiniteOptions/streamedOptions`, `experimental_defaults`, nested routers | Delete or reduce the bespoke `createQueryFactory` to a thin adapter over upstream utils; make the cache provider key off `generateOperationKey` | G10 | +| **Invalidation** | `.key()` partial-match keys `[path[], { type, input }]`; `queryClient.invalidateQueries(utils.x.key())` | One key algebra. Today there are two, and they are disjoint | G10 | +| **Policy metadata** | `os.$meta()` / `oc.$meta()`, typed into `MiddlewareOptions.procedure['~orpc'].meta` | Define a NetScript `ProcedureMeta` (auth/scopes/rate-limit/cache), thread `$meta` through `contract-base`, and ship a middleware/plugin that reads it | G11, G8 | +| **Typed execution context from plugins** | `Middleware` + `next({ context })` + `MergedCurrentContext`; `$context()` dependent-context guards | A seam that accepts `@orpc/server` `Middleware` (not Hono `MiddlewareHandler`), plus `InferRouterInitialContexts`-based verification that `ContextFactory` satisfies every mounted router | G8, G9, G5 | +| **Typed errors end-to-end** | `.errors()` → `ErrorFromErrorMap` → `ClientPromiseResult` → `safe()` discriminating `isDefined` | Stop re-deriving the client: alias `ServiceClient` to `RouterClient<…>` (or add the `TError` channel), and give `safe`/`isDefinedError` the second generic | G4 | +| **Contract-first plugin routers** | `implement()` → `Implementer` with `$context`/`$config`/`.use`/`.router`/`.lazy`; `setHiddenRouterContract` | Stop erasing to `object`; expose `.use()`; keep the contract handle on the assembled router so the registry can recover it | G5 | + +--- + +## 6. What the pinned version *cannot* do + +Almost nothing NetScript needs is blocked by `1.14.6`: + +- Every mechanism cited in §1 is present in the cached 1.14.6 `.d.ts` — plugins, six interceptor + arrays, `$context`/`$meta`/`$config`, typed errors, `DynamicLink`, `customJsonSerializers`, + contract-first `implement()`, and the full TanStack utils surface incl. `infiniteOptions`, + `experimental_streamedOptions`, `experimental_liveOptions`, `experimental_defaults`. +- The `@orpc/server` public export list is **identical** in 1.14.6 and 1.14.15 (diff run, §0). + +Genuine limits of the pinned line: + +1. `experimental_` prefixes on `streamedOptions` / `liveOptions` / `RethrowHandlerPlugin` / + `ProcedureUtilsDefaults` — API stability is not guaranteed inside 1.x. Anything NetScript builds + on those needs a wrapper it controls. +2. `@orpc/tanstack-query` `CreateProcedureUtilsOptions` / `CreateRouterUtilsOptions` carry + `@todo remove default generic types on v2` (`dist/index.d.ts:246,261`) — a signposted v2 break. +3. `@orpc/otel` is at `1.14.7` while everything else is `1.14.6` — a mixed-version island + (`deno.lock:1410` vs `1418`), which also drags a second `@orpc/shared@1.14.7` + (`deno.lock:1445`) into the graph alongside `1.14.6` (line 1434). Duplicate `@orpc/shared` copies + are a known source of `instanceof ORPCError` cross-context failures — the pinned client even + ships a `Symbol.hasInstance` workaround for exactly this class of problem + (`@orpc/client/dist/index.d.ts:118-129`). + +**Upgrade implications** + +- **1.14.6 → 1.14.15: patch-level, zero public-surface change on `@orpc/server`.** Do it, if only to + collapse the `@orpc/shared` 1.14.6/1.14.7 duplication. `deno task deps:latest` already flags all + seven packages; `.agents/skills/netscript-deno-toolchain` owns the mechanics. +- **2.0.0-beta.25 is published on the `beta` dist-tag.** The v2 `main` branch already contains + `feat(rpc): restrict RPC handlers to POST, PUT, PATCH and DELETE by default` and + `feat(server): add MethodOverrideHandlerPlugin` (`gh api repos/dinwwwh/orpc/commits`; neither is + in the 1.14.15 tarball). The first of those directly threatens NetScript's GET-based caching path: + `createHttpClientLink` sets `method: inferRPCMethodFromContractRouter(contract)` and dedupes on + `request.method === 'GET'` (`packages/sdk/src/client/http-client-link.ts:82,109`). **Plan the + remediation so the transport policy lives behind one NetScript-owned function**, not scattered + across link construction — otherwise the v2 migration touches every client call path. +- The oRPC GitHub org moved (`unnoq/orpc` → `dinwwwh/orpc`) and `orpc.unnoq.com` now 301s to + `orpc.dev`. Any doc links NetScript ships pointing at `orpc.unnoq.com` should be rewritten. + +--- + +## 7. Recommended remediation shape (ordered by leverage) + +1. **Reopen the client link.** Add `headers` / `context` / `plugins` / `interceptors` / + `clientInterceptors` / `fetch` passthrough to `CreateServiceClientOptions` and + `DefineServiceConfig`; make `ServiceClientContext` a type parameter with a NetScript base. + Unblocks G2, G3, and most of G1's client half. *(API/type-system seam.)* +2. **Restore the error channel.** Give `ServiceClientMethod` a `TError`, and `safe` / + `isDefinedError` their second generic. Then delete the fictitious declarations from + `readme-doctest_test.ts:36-37` so the doctest actually type-checks the shipped helpers. + Fixes the only *verified compile failure* in this report. *(API/type-system seam + docs.)* +3. **Add a client/server contribution group to `PluginContributions`** (`linkPlugins`, + `handlerPlugins`, `middleware` as oRPC `Middleware`, `procedureMeta`), and wire an aggregation + point into `createServiceClient` and `withRPC`. This is the seam that does not exist today. + *(plugin-composition.)* +4. **Un-erase the binder.** `PluginContractRouter = object` → the real `Implementer`; expose `.use`. + Then reconcile the scaffold (`bindPluginContract`) with what first-party plugins actually do + (`assemblePluginContractRouter` / raw `$context`). *(API seam + scaffold/generation.)* +5. **Pick one query algebra.** Reduce `createQueryFactory` to an adapter over + `createTanstackQueryUtils` keys, or drop it. Two disjoint invalidation namespaces returned from + the same `defineServices()` call is a correctness trap, not just duplication. *(API seam.)* +6. **Introduce `$meta`-based policy** and move authn/authz from Hono path guards to oRPC middleware + that reads `procedure['~orpc'].meta`. *(plugin-composition.)* +7. **Verify the context contract** with `InferRouterInitialContexts` instead of + `Record` + runtime throws. *(runtime correctness.)* +8. **Bump 1.14.6 → 1.14.15** and de-duplicate `@orpc/shared`. Low risk (identical surface), removes + a real `instanceof` hazard. + +--- + +## 8. Citation index + +**Primary docs (canonical host `orpc.dev`; `orpc.unnoq.com` 301s here):** + · + · + · + · + · + · + · + · + · + · + · + + +**Pinned package sources** (`/home/codex/.cache/deno/npm/registry.npmjs.org/@orpc/…/1.14.6/dist/`): +`server/index.d.ts`, `server/plugins/index.d.ts`, `server/adapters/fetch/index.d.ts`, +`server/shared/server.qKsRrdxW.d.ts`, `server/shared/server.7cEtMB30.d.ts`, +`server/shared/server.ChyoA9XY.d.ts`, `client/index.d.ts`, `client/plugins/index.d.ts`, +`client/adapters/fetch/index.d.ts`, `client/shared/client.i2uoJbEp.d.ts`, +`client/shared/client.2jUAqzYU.d.ts`, `client/shared/client.B3pNRBih.d.ts`, +`contract/plugins/index.d.ts`, `openapi/plugins/index.d.ts`, `tanstack-query/index.d.ts`. +1.14.15 comparison tarball: `registry.npmjs.org/@orpc/server/-/server-1.14.15.tgz`. + +**Repo files cited:** `deno.json:215-221`; `deno.lock:1355-1509`; +`packages/sdk/src/client/http-client-link.ts`; `packages/sdk/src/client/service-client.ts`; +`packages/sdk/src/client/errors.ts`; `packages/sdk/src/ports/service-client.ts`; +`packages/sdk/src/ports/service-query-utils.ts`; `packages/sdk/src/ports/client-link-factory.ts`; +`packages/sdk/src/query/query-factory.ts`; `packages/sdk/src/query-client/create-service-query-utils.ts`; +`packages/sdk/src/presets/define-services.ts`; `packages/sdk/src/desktop/application/desktop-rpc-client.ts`; +`packages/sdk/tests/readme-doctest_test.ts`; `packages/sdk/README.md:156`; +`packages/service/src/types.ts`; `packages/service/src/primitives/handlers.ts`; +`packages/service/src/primitives/orpc-router.ts`; `packages/service/src/builder/service-builder-impl.ts`; +`packages/service/src/builder/service-rpc.ts`; +`packages/plugin/src/service/presentation/plugin-contract-binder.ts`; +`packages/plugin/src/service/presentation/create-plugin-service.ts`; +`packages/plugin/src/contract-base/domain/base-contract.ts`; +`packages/plugin/src/contract-base/domain/base-errors.ts`; +`packages/plugin/src/config/domain/plugin-contributions.ts`; +`packages/plugin/src/config/domain/service-contribution.ts`; +`packages/logger/orpc-plugin.ts`; `packages/telemetry/src/orpc/tracing-plugin.ts`; +`packages/cli/src/public/features/plugins/new/new-plugin-use-case.ts:660,667`; +`plugins/workers/services/src/routers/router-context.ts`; +`plugins/{workers,sagas,auth,triggers}/services/src/router.ts`; +`docs/site/services-sdk/sdk.md:199`; `docs/site/services-sdk/how-to/discover-services.md:138-146`. + +**Executed evidence:** `deno check --unstable-kv --config deno.json /probe.ts` → +`TS2339: Property 'code' does not exist on type 'never'` (§G4); +`deno task deps:latest | grep -i orpc` → all seven at `1.14.6/1.14.7 → 1.14.15`; +export-list diff of `@orpc/server` 1.14.6 vs 1.14.15 → empty; +`curl registry.npmjs.org/@orpc/server` → publish times + `dist-tags.beta = 2.0.0-beta.25`. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/github-board-history.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/github-board-history.md new file mode 100644 index 0000000000..624473cdd0 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/github-board-history.md @@ -0,0 +1,395 @@ +# GitHub board history — the 0.0.5 release train (rickylabs/netscript) + +Audit date **2026-08-08**. All GitHub reads are live via `gh api` / `gh issue|pr list` against +`rickylabs/netscript`; local git reads are `git tag`/`git log` in +`/home/codex/repos/netscript-fable5-remediation-plan`. Orchestration artifacts are read from the +**open draft PR #1337** branch `orchestrator/0.0.5-continuation` and from the legacy orchestrator +branch `orchestrator/0.0.5` (`b31c3c200b3f2a3362ec3dbded1b5f1c032555e3`) — those files are **not on +`main`**, so anyone re-deriving this must fetch the branches. + +Read-only run: no issue, PR, label, milestone, or comment was mutated. + +--- + +## 1. Milestone 0.0.5 — live state + +| Fact | Value | Source | +| --- | --- | --- | +| Milestone number | 23 | `GET /repos/rickylabs/netscript/milestones` | +| Title / description | `0.0.5` — "Agent-surface release. Capability discovery and tooling that make the framework legible to autonomous agents — building on 0.0.4 stability." | milestone 23 | +| Created | 2026-08-03T12:04:25Z | milestone 23 | +| State | **open** | milestone 23 | +| Open rows | 22 = 21 issues + 1 PR (#1337) | `gh issue list --milestone 0.0.5 --state open` (21); `gh pr list --search milestone:0.0.5` | +| Closed rows | 160 = 77 issues + 83 PRs | milestone 23 `closed_issues`; verified 84 PRs total / 79 merged / 4 closed-unmerged (#1195, #1269, #1270, #1305) | +| Stable release | **not cut** — latest GitHub release is prerelease `v0.0.5-canary.16` (2026-08-07T17:16:52Z); last stable is `v0.0.4` (2026-08-03T19:01:55Z) | `gh release list` | + +**Fact:** 0.0.5 is a milestone that has consumed 16 canaries over five days and has not shipped +stable. It is the largest milestone in repo history by closed rows (0.0.4 = 63 closed, 0.0.3 = 38, +0.0.2 = 53). + +### 1.1 The 21 open 0.0.5 issues (what is still unshipped) + +Grouped by the continuation plan's wave assignment (§4.2). Labels abbreviated. + +| Issue | Pri | Area | Title (abridged) | Plan slice | +| --- | --- | --- | --- | --- | +| #1338 | p0 | agentic | make DeepSeek V4 Flash 0731 max the formal IMPL-EVAL default | observational T1 closure (PR #1339 merged 2026-08-06) | +| #1325 | p1 | plugins/aspire | **triggers**: generated background runtime omits Redis adapter, crash-loops on default Aspire cache | W2-A | +| #1329 | p0 | plugins/docs/telemetry | **streams**: documented SSE consumer shape ≠ wire protocol; no standard event/OTEL envelope | W2-B | +| #1202 | p1 | cli/database | users service Prisma binds a stale Postgres endpoint — DB health fails on clean `scaffold.runtime` | W2-C | +| #1327 | p1 | cli/database | `db migrate` reports success headless without creating the migration | W2-C | +| #1326 | p0 | plugins | **streams**: `DurableStreamProducer` permanently drops writes after initial connection failure; no reconnect | W3-A | +| #1102 | p1 | docs/tooling | make MCP capability discovery an intent-aware primary agent workflow | W3-B | +| #1197 | p1 | tooling/agentic | agent-init harness had **zero adoption** on 0.0.4 — 0 MCP calls, 0 plugin doctor, 0 aspire otel, 0 skills across a 452-call agent run | W3-B | +| #1119 | p2 | agentic | "canary" means two unrelated things — rename the AI-rollout pair | W3-C | +| #1333 | p0 | cli/fresh/fresh-ui | **scaffold/frontend**: default app must be an idiomatic eis-chat-grade reference; derive name from project | W4-A | +| #1208 | p0 | docs | no tutorial demonstrates the page builder; every tutorial underleverages NetScript | W4-B | +| #1108 | p1 | docs/tooling | verify generated package references against live export maps | W4-C | +| #1137 | p1 | service | [openapi-mcp S11] contract summary/tags enrichment | W5-A | +| #1138 | p2 | docs | [openapi-mcp S12] agent-facing OpenAPI→MCP reference | W5-A | +| #1332 | p1 | docs/database/contracts | show generated DB schemas as normative predecessor to API contracts | W5-B | +| #1334 | p1 | docs | complete the capability story on the homepage beyond "end-to-end typesafety" | W5-B | +| #1004 | p1 | tooling | canary lane has no same-semver republish path — a 503 forces a wasted canary.N | evidence hand-close | +| #1090 | p1 | cli/agentic | verify(wave-five): does the shipped agent surface actually change agent behaviour? | observational hand-close | +| #1166 | p1 | tooling | canary payload misses work landing behind a release PR via a merge commit | evidence hand-close | +| #1126 | p1 | tooling/service | Epic: OpenAPI→MCP service introspection | umbrella hand-close | +| #1169 | p1 | tooling | epic: guarantee a one-pass publish (0.0.4 took three canaries + six reruns) | umbrella hand-close | + +**Fact:** every remaining p0 in 0.0.5 is a *product-surface* defect (#1326, #1329 streams; #1333 +scaffold frontend; #1208 tutorials) — the tooling/release/agentic p0s (#1312, #1324, #1328, #1345) +all closed on canary.15/.16. The unshipped tail is exactly the "does the framework feel right to a +user/agent" tail. + +--- + +## 2. Canary cadence — 0.0.5-canary.1 … .16 + +Payload derived from the `canary:0.0.5-canary.N` labels the release workflow applies to merged PRs +and closed issues (labels enumerated from `/labels`), cross-checked against local tag dates. + +| Canary | Tag commit (UTC) | GH release published | Payload issues (label-derived) | Notes | +| --- | --- | --- | --- | --- | +| .1 | 08-03 22:32 | 08-03 22:34 | #1105 #1127 #1128 #1129 #1134 #1168 #1170 #1171 #1172 #1173 #1174 | wave-1 boundary; 10 PRs, `populated` derivation (legacy `cut-trace.md`) | +| .2 | 08-04 08:38 | 08-04 **08:45** | #1106 #1130 #1131 #1184 #1187 #1191 #1207 | out-of-order publish vs .3/.4 | +| .3 | 08-04 08:41 | 08-04 08:42 | #1187 (also .2) | overlapping/wasted cut | +| .4 | 08-04 08:47 | 08-04 08:49 | **none** | wasted canary — no issue or PR carries the label | +| .5 | 08-04 10:07 | 08-04 10:08 | #1109 #1132 | | +| .6 | 08-04 11:50 | 08-04 11:52 | #1133 (+PRs #1206 #1211 #1216 #1217) | pair went **red** on `runtime.aspire-restore` → filed #1227 | +| .7 | 08-04 12:51 | 08-04 12:53 | #1223 (+PRs #1218 #1221 #1222 #1224) | | +| .8 | 08-04 17:43 | 08-04 17:45 | #1104 #1135 #1136 #1235 #1236 #1240 #1247 #1250 #1253 #1254 #1267 | largest payload | +| .9 | 08-04 21:03 | 08-04 21:06 | #1225 #1228 #1229 #1230 #1231 #1234 #1248 #1251 #1252 | plugin/runtime batch | +| .10 | 08-04 23:11 | 08-04 23:16 | #1110 #1116 #1158 #1188 #1196 #1219 #1227 #1287 #1288 #1290 #1294 | | +| .11 | 08-05 05:04 | 08-05 21:16 | #1227 #1274 #1310 | **partial publish: 25 of 31 packages** — JSR weekly attempt cap hit → #1312 | +| .12 | 08-05 21:42 | 08-05 21:44 | (PRs #1313 #1321) | recovery cut, no issue closures | +| .13 | 08-05 22:04 | 08-05 22:07 | (PRs #1322 #1323) | recovery cut, no issue closures | +| .14 | 08-06 21:37 | 08-06 21:39 | #1115 #1117 #1189 #1295 (+#1331) | last aggregated-train cut (PR #1340); initial pinned E2E failed on a transient JSR 502 → tag-bound same-semver recovery run `31128595811`, child `31128614286`, **no canary.15 burned** | +| .15 | 08-07 16:15 | 08-07 16:18 | #1024 #1148 #1312 #1324 #1328 #1330 | W1 cluster; published 35/35 but pinned production E2E run `31196896495` **failed** → #1345 | +| .16 | 08-07 17:14 | 08-07 17:16 | #1345 | repair cut from PR #1346; 35 packages verified `complete`; pinned child `31201560939` **success**; `release/canary-pair` green | + +**Facts about cost:** the plan budgeted **three** cuts (.14/.15/.16) and spent exactly three. The +first eleven cuts (08-03 → 08-05) are the expensive part: canary.3/.4/.12/.13 carry no issue payload +at all — four of sixteen cuts were pure release-machinery churn. Post-canary.13 evidence recorded +**1,076 / 4,000 JSR publish attempts used** with 35 publishable packages +(`research.md`, orchestrator branch); canary.11's partial publish (25/31) is the incident that +produced #1312's fail-before-mint preflight, landed in PR #1341 on canary.15. + +**Hypothesis (well-supported, not documented as such):** the canary.2/.3/.4 cluster inside 11 +minutes on 08-04, with .4 carrying no payload, is the concrete instance of open issue #1004 +("a 503 mid-publish forces a wasted canary.N"). #1004 is still open. + +--- + +## 3. Remediation-topic issues — what exactly landed, and where + +### 3.1 Sagas (the #1064/#1065 → #1184 → #1190 → #1223 chain) + +| Issue | State | Milestone | Canary | What landed | +| --- | --- | --- | --- | --- | +| #1065 `sagaCompensate` effects silently dropped — `createDurableSagaRuntime` wires no compensator | CLOSED 2026-08-03 | **0.0.4** | 0.0.4-canary.1 | pre-0.0.5 | +| #1184 generated runtime glue registers no KV adapter — saga runner crashes on default scaffold | CLOSED 2026-08-04T08:35 | 0.0.5 | canary.2 | PR #1193 `fix(sagas): register KV adapter in generated runtime glue`. Filed mid-run by the owner from a wave-4 DeepSeek run against **published 0.0.4** (legacy `cut-trace.md` R2); scheduled as W2-F into the canary.2 train. Its closure bar became the **seven-point saga verification protocol** — the standard for all saga work in 0.0.5. | +| #1190 `POST /publish` still hangs indefinitely outside the Redis path; "single-point primitives do not compose" | CLOSED 2026-08-04T12:49 | 0.0.5 | **no canary label** | Fix shipped as PR #1198 `fix(sagas): deliver published messages to the runner` (merged 08-04T06:33, `canary:0.0.5-canary.2`), but that PR carries only `Refs #1190` — closure was **hand-close on evidence**, 56 s after PR #1224 merged. Timeline confirms `closed` with no commit id. | +| #1223 Redis-persisted saga state reaches projection with unrevived dates — `saga_instances` never projected | CLOSED 2026-08-04T12:48 | 0.0.5 | canary.7 | PR #1224 `fix(sagas): revive persisted dates for projections`. Filed by the orchestrator *from the #1190 protocol run* — i.e. verifying one saga fix found the next. | +| #1225 stream mirror only reconciles at service start | CLOSED 2026-08-04 | 0.0.5 | canary.9 | PR #1284 `feat(sagas): mirror every durable transition` | + +**Fact:** the sagas thread in 0.0.5 is four sequential defects, each discovered by verifying the +previous one, none of which was caught by the existing gates. **No saga issue remains open in +0.0.5.** + +### 3.2 Streams — the largest open remediation surface + +- #1235 `createNetScriptStreamDB` erases collection types to `unknown` — CLOSED, canary.8, PR #1238. +- #1326 (p0) `DurableStreamProducer` permanently drops writes after an initial connection failure — + **OPEN**, plan slice W3-A, route Sol medium. +- #1329 (p0) documented SSE consumer shape differs from the wire protocol; no standard event/OTEL + envelope — **OPEN**, plan slice W2-B, route Sol medium, and W3-A is declared *dependent* on + W2-B's envelope (plan §Wave schedule). +- Plan records accepted debt: `plugin-streams-core` carries **AP-13 console-warning debt** plus + streams-connector convergence debt; the plan explicitly requires #1326 to *replace* the misleading + warning behavior, not deepen it (`plan.md` archetype table). + +### 3.3 Triggers / workers / cron + +- #1229 one-shot defer scheduler (DeferAction rejected to DLQ as unsupported) — CLOSED, canary.9, + PR #1283. +- #1228 `createJobTools` trace/span/progress helpers were no-op stubs — CLOSED, canary.9, PR #1281. +- #1234 `generate plugins` cannot emit a job registry for a custom job — CLOSED, canary.9, PR #1239. +- #1104 cron retry/backoff contract honored — CLOSED, canary.8, PR #1226. +- #1325 (p1) triggers generated background runtime omits the Redis adapter and crash-loops on the + default Aspire cache — **OPEN**, W2-A. Note this is structurally the *same defect class* as the + already-fixed saga #1184 (generated glue omits an adapter), one plugin over. + +### 3.4 Scaffold / CLI / database + +Closed on 0.0.5: #1191 (SQLite `--allow-ffi`), #1254 + #1290 (`@database/zod` barrel, twice — +the first fix broke the generated contract), #1287 (`QueryClientPort` vs `QueryClient` — a fresh +workspace failed its own `deno task check`), #1251 (Aspire graph omitted SQLite / modelled deno-kv +as an unresolved parameter), #1196 + #1310 (ephemeral `db` AppHosts; second Postgres corrupting +PGDATA), #1236 (`plugin remove` non-atomic), #1247 (agent init hard-coded VS Code), #1264 (Windows +npm materialization), #1294 (no gate walked the Quickstart end to end), #1328 + #1024 (canary.15: +generated quality gates now own executable source; consumer `.llm/tools` bundle). + +Still open: #1202 (stale Postgres endpoint in the users service), #1327 (`db migrate` false +success headless), #1333 (default app is not an idiomatic reference). + +**Fact:** #1335 `Epic: Scaffold conformance — generated surfaces match current docs, exports and +idiomatic usage` (p1, umbrella) exists and sits in **Backlog / Triage**, unmilestoned. PR #1342 +explicitly declares "#1335/W1-C whole-scaffold inventory" out of scope. This is the natural parent +for a scaffold-remediation milestone. + +### 3.5 Auth / SDK + +Thin. #1106 auth session-lifecycle docs — CLOSED, canary.2, PR #1200. #1252 fresh/SDK cache-tier +divergence (`queryOptions` bypasses `CacheQuery`, `IslandQueryOptions` rejects +`initialDataUpdatedAt`) — CLOSED, canary.9, PR #1265. #1243 (`auth session list --stream-url` +pins a dead `localhost:4437` after #1211 randomized ports) — **OPEN, moved to 0.0.6**. No auth code +work landed in 0.0.5. + +### 3.6 MCP / agentic + +Epic #1126 (OpenAPI→MCP) ran S1–S12 as issues #1127–#1138: S1–S3 proofs and S8 on canary.1, S4/S5 on +canary.2, S6 on canary.5, S7 on canary.6, S9/S10 on canary.8, S11/S12 (#1137/#1138) **still open**, +S13 (#1139, F2-gated) and S14 (#1140) **moved to 0.0.6**. #1117 (scaffolded OpenAPI surface as MCP +tools) closed on canary.14 via PR #1317. #1218/#1253 export-corpus work closed on canary.7/.8. + +The adoption question is unresolved: **#1197** records that the agent-init harness had *zero* +adoption on 0.0.4 (0 MCP calls, 0 plugin doctor, 0 aspire otel, 0 skills across 452 agent calls), +and **#1090** ("does the shipped agent surface actually change agent behaviour?") is still open as an +observational row. #1324/#1330 (OpenCode ignored generated MCP config; resume forwarded empty +assistant turns) closed on canary.15 via PR #1344 — that PR's evidence is the first recorded +non-zero NetScript MCP use in a measured session (`ses_023871aaeffehRNSqFc3I43Fvc`). + +### 3.7 The #1340–#1346 PR train (post-canary.14) + +| PR | Merged | Closes | Canary | Content | +| --- | --- | --- | --- | --- | +| #1340 | 08-06 21:34 | #1295 #1189 #1117 #1115 | .14 | umbrella that squash-merged train branch `canary/0.0.5-canary.14` (PRs #1315–#1318) to `main`; carries an inline ```acceptance-evidence``` block; declared "the final use of the temporary aggregated train" | +| #1341 | 08-07 06:53 | #1312 #1148 | .15 | fail-before-mint JSR budget preflight; partial-publication classified distinctly from pinned-E2E failure; version-residue scan widened to generated `.ts`. PLAN-EVAL waived by owner; IMPL-EVAL mandatory | +| #1342 | 08-07 12:54 | #1024 #1328 (Refs #1343) | .15 | generated `.netscript` quality runner covering owned `.ts`/`.tsx`/`.mts`; fixes generator defects rather than excluding source; installed-consumer observation deferred to #1343 | +| #1344 | 08-07 14:37 | #1324 #1330 | .15 | OpenCode MCP attachment + fail-closed preflight + provider-valid resume normalization; PLAN-EVAL Minimax M3 PASS, IMPL-EVAL DeepSeek V4 Flash max PASS | +| #1346 | 08-07 17:12 | #1345 (Refs #1343) | .16 | repairs the two scaffold assumptions that failed canary.15's pinned production E2E (formatted Flow-B rewrite; quickstart's premature whole-project check) | +| #1343 | — | open, **0.0.6** | — | deferred installed-consumer scaffold smoke against a post-fix canary | + +(#1339 merged 08-06 20:46 into the train, closing nothing; #1338 retained open as an observational +row.) + +--- + +## 4. How the milestone-orchestrator grouped PRs into waves + +Two complete, *different* orchestration plans ran against milestone 0.0.5. Both are on branches, not +`main`. + +### 4.1 Plan v2 — legacy run (`orchestrator/0.0.5`, `b31c3c200`) + +`/.llm/runs/release-0.0.5--orchestration/plan.md` @ `b31c3c200`, "Wave plan v2 — 0.0.5 (stage B)", +2026-08-03. Revised against a PLAN-EVAL **FAIL** (v1 at `79a28e612`). + +- **Disposition table first:** every one of the 44 open issues placed in exactly one class — + PR-closable (33), PR+evidence hand-close (3: #1166, #1168, #1004), observational hand-close (2: + #1149, #1090), epic/tracking with *no closing keywords* (3: #1126, #1169, #1117), gated-out (1: + #1139), moved to 0.0.6 (2: #1140, #1175). Totals are asserted and check-summed (`= 44 ✓`). +- **Seven waves, 31 PRs covering 36 issues.** Each row is `PR-id | issues | scope | lane | proving + gates`. Lanes are `Sol · low|medium|high` (Codex GPT-5.6) plus one serialized `agy` docs lane per + wave. +- **Four canary points at wave boundaries 1, 3, 5, 7** — owner-decided "6 if strictly needed + otherwise 3-4"; "a red canary blocks only the cut, never the next dispatch". +- **Canary = content unit, wave = dispatch unit.** Membership derived from actual merge history, not + from the plan; version strings come from `release-canary.yml` output, "never typed". +- **Re-planning is logged, not re-numbered.** `cut-trace.md` records R1 (three PRs landed externally + before wave-1 dispatch → remainder re-clustered), R2 (#1184 filed mid-run → scheduled W2-F into the + *canary.2 train*, "one canary train, not a train of its own"), R3 (#1189 filed → W6-A on the + canary.4 train, sequenced after #1093 because they share plugin-core surface). Ten more mid-run + additions (#1187, #1188, #1219, #1223, #1227, #1234–#1236, #1246–#1254) are each dispositioned in + the same table with a route and a lane, taking the plan from 44 to 51 rows. +- An explicit **owner onboarding-verification wave** (added 2026-08-04 evening) added 8 rows sourced + from a real Windows/Zed onboarding against canary.7 plus an independent Codex verification pass; + route directive `Sol · medium`; three lanes W6-Q/W6-R/W6-S. +- Refuted findings are recorded as *do-not-schedule*: "withForm hydrated-POST value loss — + independently refuted (form modules byte-identical 0.0.4→canary.2); never schedule." + +Reality diverged: 16 canaries were cut, not 4. + +### 4.2 Plan v3 — continuation run (PR #1337, `orchestrator/0.0.5-continuation`, open draft) + +Baseline `origin/main` `2508eb8c9` on 2026-08-06. Legacy checkout `8399126ef` was declared "evidence, +not an implementation base" (ahead 155 / behind 72). Separate **Minimax M3 high** PLAN-EVAL session +`567e3125-…` returned PASS (comment on #1337, 2026-08-06T15:10Z). + +Shape: + +- Scope cut **38 open issues → 30**; eight rows moved to a newly created 0.0.6 with per-issue written + reasons (#1085, #1093, #1112, #1139, #1201, #1210, #1260, #1293). +- **18 supervisor-owned PR clusters**, one supervisor per cluster, **max 3 active supervisors per + wave**, dependencies only across waves: `T1-A/T1-B`, `T2-A/T2-B`, `W1-A/B/C`, `W2-A/B/C`, + `W3-A/B/C`, `W4-A/B/C`, `W5-A/B`, plus stage `F` for evidence closures. +- Each slice row carries: issues, *what it proves*, expected paths, decisive gates, and a route + (`Sol low` default; `Sol medium` only for #1329 and #1326 and #1333; #1333 additionally takes a + GLM 5.2 xhigh design review). +- **Exactly three canary boundaries** — C14 after W1, C15 after W3, C16 after W5 — with a six-step + cut ritual (freeze membership from first-parent history → pre-merge/close/review-thread/docs gates + + one-pass `scaffold.runtime` → merge umbrella → dispatch OIDC workflow, never publish locally → + verify every package at the exact version + pinned `e2e-cli-prod` + `release/canary-pair` → + record in `cut-trace.md`). +- A per-cluster **archetype/doctrine/JSR table** naming existing accepted debt that a slice may cite + but not widen, and a **risk register** and **open-decision sweep** classifying each unresolved + decision as must-resolve-now vs safe-to-defer with a named owner. +- **Evidence rows never get closing keywords.** #1004/#1090/#1126/#1166/#1169 are hand-close only; + the risk register lists "observational rows are auto-closed by code" with mitigation "no closing + keywords; GraphQL closing-reference audit before every merge". + +**The mid-flight pivot (this is the most transferable lesson).** Drift entry **C-D25** (2026-08-06): +"The temporary aggregate canary/orchestrator-branch PR mechanism outlived the JSR publish-cap +workaround. Owner retired it after C14. Every post-cut meaningful connected cluster targets `main` +directly and owns independent CI/review; the orchestrator branch retains coordination history only." +The plan carries a superseding section (`## Post-C14 owner correction`) that explicitly overrides its +own earlier train-target, W1-before-C14, always-PLAN-EVAL and Qwen-default statements. Post-C14 the +model is: **one tightly-connected cluster = one small draft PR directly against `main`**, PLAN-EVAL +conditional (only for genuinely complex/decision-heavy work), IMPL-EVAL always mandatory +(`deepseek/deepseek-v4-flash-0731` max default per #1338/#1339, C-D20/C-D23). + +`drift.md` on that branch is 27 entries, most about *agent-runtime* failures rather than product +work — duplicate thread writers (C-D11, C-D13, C-D14, C-D17, C-D27), evaluator sessions launched +twice and failed closed (C-D10, C-D15), launcher wrappers rewriting `deno.lock` (C-D16, C-D21), +OpenRouter budget exhaustion mid-verdict (C-D18), and a merge-helper that could only see +OpenHands-authored verdicts (C-D24, merged with `--no-eval-gate`). That is the real cost centre of +this milestone. + +### 4.3 Delivery status of plan v3 as of 2026-08-08 + +| Stage | Status | +| --- | --- | +| T1 (#1295→#1315, #1189→#1316) | done, canary.14 | +| T2 (#1117→#1317, #1115→#1318) | done, canary.14 | +| **Cut C14** | done 08-06, green pair after tag-bound same-semver recovery | +| W1-A/B/C (#1312+#1148, #1024+#1328, #1324+#1330) | done as direct-to-main PRs #1341/#1342/#1344, canary.15 | +| **Cut C15** | published but pinned production E2E **failed** → repaired by #1346 | +| **Cut C16** | done 08-07, green pair, 35/35 verified | +| **W2, W3, W4, W5** | **not started** — 15 issues, including four p0s | +| Stage F evidence closures | not started (#1004/#1090/#1126/#1166/#1169 open) | + +**Fact:** the continuation plan is ~40 % delivered. Everything remaining is W2–W5 plus stage F, and +those clusters are already specified with paths, gates and routes in `plan.md` — a remediation plan +that re-derives them from scratch will duplicate existing work; a remediation plan that *inherits* +them should cite the slice ids (W2-A … W5-B) so the mapping is auditable. + +--- + +## 5. House pattern for milestone shifts + +Milestones are **renamed in place, never recreated**, highest-to-lowest, and only then is the freed +title created as a new milestone. Verified twice. + +### 5.1 The 2026-08-06 rollover (fully documented) + +From `research.md` on `orchestrator/0.0.5-continuation` — the pre-mutation inventory was read live, +the rename order was locked, execution ran 14:41:13Z→14:41:54Z, and a post-mutation verification +table proves nothing else moved: + +| Milestone # | Before | After | Open before→after | Closed | +| --- | --- | --- | --- | --- | +| 21 | 0.0.12 | 0.0.13 | 44→44 | 0→0 | +| 20 | 0.0.11 | 0.0.12 | 11→11 | 0→0 | +| 19 | 0.0.10 | 0.0.11 | 10→10 | 0→0 | +| 18 | 0.0.9 | 0.0.10 | 2→2 | 0→0 | +| 17 | 0.0.8 | 0.0.9 | 15→15 | 0→0 | +| 16 | 0.0.7 | 0.0.8 | 50→50 | 4→4 | +| 24 | 0.0.6 | **0.0.7** | 34→20 | 6→6 | +| 25 | (created) | **0.0.6** | 0→14 | 0→0 | + +Rules the artifact states and verifies: renames preserve **number, state, due date, description, +creation time, and every issue assignment** — only `title` and `updated_at` change; the new milestone +is created **only after** the old holder frees the title; **no closed historical assignment moves**; +**no issue traverses an intermediate milestone**; every moved issue gets a written per-issue reason; +and post-mutation counts are re-verified by REST pagination (milestone 23 = 35 open rows, milestone +25 = 22 open rows, milestone 24 = 20 open frontend rows + 6 closed historical PR assignments +#1217/#1222/#1241/#1272/#1286/#1291). + +Confirmed independently from issue timelines: + +- #1140: `milestoned 0.0.5` (08-03 14:04) → `demilestoned 0.0.5 / milestoned 0.0.6` (08-03 19:49) → + `demilestoned 0.0.7 / milestoned 0.0.6` (08-06 14:41:53) — i.e. it never moved on 08-06; the + milestone under it was renamed 0.0.6→0.0.7 and the issue was then moved into the *new* 0.0.6. +- #1320: `milestoned 0.0.6` (08-05) → `demilestoned 0.0.7 / milestoned 0.0.6` (08-06 14:41:34) — + same signature. +- #922: `milestoned 0.0.1-beta.13` (07-19 14:21) → `Backlog / Triage` (07-19 14:39) → `0.0.6` + (08-03 13:27), now reading as 0.0.7. + +### 5.2 The earlier cascades (inferred from artifacts, consistent with timelines) + +- Milestone 21's description still reads *"Cascaded from beta.18 when beta.12 became the + stabilisation release."* — a record of a beta-line cascade written into the description. +- Issue #950 timeline: `milestoned 0.0.1-beta.13` (2026-07-31) → `demilestoned 0.0.3 / milestoned + 0.0.4` (2026-08-01). The same milestone object read as `0.0.1-beta.13` on 07-31 and as `0.0.3` on + 08-01 — i.e. the whole `0.0.1-beta.N` line was renamed to `0.0.N` around 2026-08-01. #950 today + sits in milestone 16, titled `0.0.8`. +- Milestone 16 was therefore titled `0.0.4` on 2026-08-01 and is `0.0.8` today — **four renames in + six days**. New milestones 22 (`0.0.4`, 08-03 05:41), 23 (`0.0.5`, 08-03 12:04) and 24 (08-03 + 13:21) were inserted below it on 2026-08-03, and 25 (`0.0.6`) on 08-06. + +**Hypothesis (strong, not directly documented):** the 2026-08-03 insertion of 0.0.4/0.0.5/0.0.6 used +the same rename-cascade mechanism as the 08-06 rollover; I have the creation dates and the resulting +titles but not a `research.md`-style pre/post table for that date. + +**Implication for a remediation milestone:** inserting a new release slot is an established, +mechanically-specified operation with a written receipt. The precedent to copy is +`.llm/runs/release-0.0.5--orchestration/research.md` §"Milestone rollover pre/post-mutation" — +pre-inventory table, locked highest-to-lowest rename order, post-verification table, per-issue move +reasons, and REST-paginated count reconciliation. + +### 5.3 Milestones ship with residual open issues + +0.0.2 is stable-released (`v0.0.2`, 2026-08-01) yet still holds **5 open issues** (#175, #767, #768, +#863, #864). 0.0.3 and 0.0.4 hold 0 open. So the house pattern permits closing a release with a +non-empty milestone, but 0.0.4 shows the preferred end-state is empty. + +--- + +## 6. Conflicts, gaps, and things a remediation plan must not get wrong + +1. **The 0.0.5 orchestration record is not on `main`.** `plan.md`, `research.md`, `cut-trace.md`, + `drift.md`, `post-c14-handoff.md`, `canary-16-recovery-receipt.md` and 30+ slice files live only + on `orchestrator/0.0.5-continuation` (draft PR #1337, open since 2026-08-06). `.llm/runs/` on + `main` contains `release-0.0.4--orchestration` but **not** `release-0.0.5--orchestration`. Any + plan that assumes the 0.0.5 history is discoverable from `main` is wrong. +2. **#1337 must not acquire a closing keyword.** Its body says so explicitly; it is a coordination + artifact for 21+ issues. +3. **Slippage is undeclared.** Nothing on the board yet says W2–W5 slip out of 0.0.5. The milestone + is still open with 21 issues and no stable cut. Deciding "0.0.5 stable ships now, remainder moves" + vs "0.0.5 continues" is an *open* decision, and §5 gives the exact mechanism for either. +4. **#1335 (Scaffold conformance epic) is unmilestoned in Backlog / Triage** while #1333/#1202/#1327 + sit in 0.0.5 and #1262/#1263/#1246 in 0.0.6. That epic is currently orphaned from its children. +5. **Duplicate-risk pairs** a remediation plan is likely to re-file: #1326/#1329 (streams reconnect + + SSE envelope — already specified as W3-A/W2-B with a declared dependency), #1325 (triggers Redis + glue — same defect class as closed #1184), #1333 (default app quality — already a p0 with a GLM + design-review requirement), #1197/#1090 (agent-surface adoption measurement — two rows, one + question), #1243/#1211 (dead default port), #1320/#1295 (Zod: #1295 landed the npm Zod-4 + alignment; #1320 remains **blocked** on `@ag-ui/core` hard `^3` and `kvdex`). +6. **The evaluator lane is the milestone's dominant failure mode.** 27 drift entries, of which + ~15 are agent-runtime/evaluator/transport failures, versus a handful of product-scope drifts. A + remediation roadmap that only counts product issues will under-budget the orchestration overhead + that actually consumed 0.0.5. +7. **Label hygiene is real and enforced.** Every closed 0.0.5 row carries exactly one `status:` + (terminal `status:shipped`), a `canary:0.0.5-canary.N`, and its milestone; the close-gate + (#1188, #1303) verifies GraphQL `closingIssuesReferences` against body keywords. Draft issues + produced by this run must carry the full namespaced taxonomy and an explicit milestone or they + will be rejected by the same gates. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/github-board-open.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/github-board-open.md new file mode 100644 index 0000000000..97882c1ec6 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/github-board-open.md @@ -0,0 +1,818 @@ +# GitHub open-board snapshot — `rickylabs/netscript` + +**Captured:** 2026-08-08, via authenticated `gh` reads (`gh issue list --state open --limit 500`, +`gh api repos/rickylabs/netscript/milestones`, `gh pr list --state open`). Read-only; no mutations. +This document is the **dedup ground truth** for the Fable 5 remediation roadmap: any new issue draft +must be checked against §2/§3 before filing. + +## 0. Headline counts (facts) + +| Metric | Value | +|---|---| +| Open issues (excludes PRs) | **259** | +| Closed issues | 385 | +| Open milestones | **13** (not 11 — see §0.1) | +| Open PRs | 8 (all drafts) | +| Open umbrella issues (`type:umbrella`) | **20** | +| Distinct `epic:*` labels on open issues | 13 | + +### 0.1 Milestone inventory (`gh api .../milestones`) + +`open`/`closed` below are GitHub's own counters (they include PRs, so they exceed the issue-only +column on the right). + +| # | Title | GH open | GH closed | Open **issues** (measured) | +|---|-------|--------:|----------:|---------------------------:| +| 14 | 0.0.2 | 5 | 53 | 5 | +| 15 | 0.0.3 | 0 | 38 | 0 | +| 22 | 0.0.4 | 0 | 63 | 0 | +| 23 | 0.0.5 | 22 | 160 | 21 | +| 25 | 0.0.6 | 23 | 0 | 22 | +| 24 | 0.0.7 | 20 | 6 | 20 | +| 16 | 0.0.8 | 50 | 4 | 48 | +| 17 | 0.0.9 | 15 | 0 | 15 | +| 18 | 0.0.10 | 2 | 0 | 2 | +| 19 | 0.0.11 | 10 | 0 | 10 | +| 20 | 0.0.12 | 11 | 0 | 11 | +| 21 | 0.0.13 | 44 | 0 | 44 | +| 3 | Backlog / Triage | 58 | 15 | 58 | +| — | *(no milestone)* | — | — | **3** | + +**Conflict vs pre-plan:** the pre-plan said "~265 open issues, 11 open milestones". Current state is +**259 open issues across 13 open milestones** (0.0.3 and 0.0.4 are open milestone records with zero +open items; 0.0.2 still carries 5 open issues *behind* three shipped milestones — see §5.1). + +### 0.2 Label distribution across the 259 open issues + +`type:` — feat 161, fix 30, umbrella 20, chore 14, test 11, docs 11, refactor 9, `type:feature` 1 (typo'd singleton on one issue). +`priority:` — p1 128, p2 93, p0 19, p3 16 (3 unlabelled). +`status:` — plan 162, triage 82, research 10, blocked 2, impl 1 (2 unlabelled). +`wave:` — v1 66, defer 33, v1-min 21 (139 unlabelled). +`area:` (top) — cli 69, plugins 62, deploy 58, tooling 27, fresh-ui 24, fresh 23, docs 21, service 21, auth 20, aspire 19, database 19, telemetry 14, agentic 11. +`gate:` — e2e 6, jsr 8. Non-namespaced legacy labels still present: `rfc` 4, `documentation` 1. + +### 0.3 Open PRs (context for "is this already in flight?") + +| PR | Draft | Branch | Title | +|---|---|---|---| +| 1347 | yes | `plan/fable5-remediation-roadmap` | plan(seed): Fable 5 long-range remediation roadmap (this run) | +| 1337 | yes | `orchestrator/0.0.5-continuation` | chore(harness): orchestrate the 0.0.5 continuation | +| 1215 | yes | `orchestrator/docs-mainpages` | orchestrator: docs main-pages revamp + docs-leverage program | +| 822 | yes | `plan/rfc-single-deployment` | RFC: NetScript Single Deployment (#820) | +| 780 | yes | `feat/dashboard-visual-revamp` | feat(dashboard): visual revamp to reference bar | +| 778 | yes | `feat/canvas-shots-tool` | feat(tooling): canvas-shots screenshot gate | +| 775 | yes | `fix/design-sync-preact-compat` | fix(design-sync): Preact value-import surface | +| 572 | yes | `copilot/evaluate-proposal-and-documentation` | harness: GlideMQ evaluation run | + +No non-draft PR is open. Nothing in §2 is currently being implemented by an open ready PR. + +--- + +## 1. How to read the tables + +`U` column = `**EPIC**` when the issue carries `type:umbrella`. +Label condensation order: `type · priority · a:area(s) · e:epic(s) · w:wave · s:status · g:gate · `. +Every issue number links to `https://github.com/rickylabs/netscript/issues/`. + +--- + +## 2. Per-milestone open-issue tables (exhaustive, 198 issues in release milestones) + +### Milestone `0.0.2` — 5 open issues + +| # | U | Title | Labels (condensed) | +|---|---|-------|--------------------| +| [175](https://github.com/rickylabs/netscript/issues/175) | | Service logs spurious "MySQL is NOT reachable" ERR under --db sqlite | | +| [767](https://github.com/rickylabs/netscript/issues/767) | | quality(docs): docs:readme:check is a dead gate — checker/template/house-style three-way divergence | chore · p3 · a:docs+tooling · w:v1 · s:triage | +| [768](https://github.com/rickylabs/netscript/issues/768) | | fix(ci): OpenHands agent runtime fails to bootstrap — ModuleNotFoundError: No module named 'fastapi' | fix · p2 · a:tooling+agentic · w:v1 · s:triage | +| [863](https://github.com/rickylabs/netscript/issues/863) | | scaffold: `netscript db init` can block indefinitely on an Unhealthy-but-Running Postgres resource (clean-machine quickstart flake) | fix · p1 · a:cli+aspire · w:v1 · s:triage | +| [864](https://github.com/rickylabs/netscript/issues/864) | | cli: `deploy list --json` advertises an `emit` operation that no target command tree ships | fix · p2 · a:cli+deploy · w:v1 · s:triage | + +### Milestone `0.0.5` — 21 open issues + +| # | U | Title | Labels (condensed) | +|---|---|-------|--------------------| +| [1004](https://github.com/rickylabs/netscript/issues/1004) | | fix(release): canary lane has no same-semver republish path — a 503 mid-publish forces a wasted canary.N | fix · p1 · a:tooling · s:plan | +| [1090](https://github.com/rickylabs/netscript/issues/1090) | | verify(wave-five): does the shipped agent surface actually change agent behaviour? | test · p1 · a:cli+agentic · s:triage | +| [1102](https://github.com/rickylabs/netscript/issues/1102) | | feat(mcp): make capability discovery an intent-aware primary agent workflow | feat · p1 · a:docs+tooling · s:triage | +| [1108](https://github.com/rickylabs/netscript/issues/1108) | | docs(tooling): verify generated package references against live export maps | docs · p1 · a:docs+tooling · s:triage | +| [1119](https://github.com/rickylabs/netscript/issues/1119) | | agentic: 'canary' means two unrelated things — rename the AI-rollout pair before the release cadence lands | chore · p2 · a:agentic · s:plan | +| [1126](https://github.com/rickylabs/netscript/issues/1126) | **EPIC** | Epic: OpenAPI→MCP service introspection — agent-legible service APIs | umbrella · p1 · a:tooling+service · e:openapi-mcp · s:plan | +| [1137](https://github.com/rickylabs/netscript/issues/1137) | | [openapi-mcp S11] Contract summary/tags enrichment across first-party contracts | feat · p1 · a:service · e:openapi-mcp · s:plan | +| [1138](https://github.com/rickylabs/netscript/issues/1138) | | [openapi-mcp S12] Docs: agent-facing OpenAPI→MCP reference + cross-links | docs · p2 · a:docs · e:openapi-mcp · s:plan | +| [1166](https://github.com/rickylabs/netscript/issues/1166) | | fix(release): canary payload misses work that lands behind a release PR via a merge commit | fix · p1 · a:tooling · e:harness-v3 · s:triage | +| [1169](https://github.com/rickylabs/netscript/issues/1169) | **EPIC** | epic: guarantee a one-pass publish — eliminate the non-deterministic failures that made 0.0.4 take three canaries and six reruns | umbrella · p1 · a:tooling · e:harness-v3 · s:triage | +| [1197](https://github.com/rickylabs/netscript/issues/1197) | | agentic: the agent-init harness had zero adoption on 0.0.4 — 0 MCP calls, 0 plugin doctor, 0 aspire otel, 0 skills across a full 452-call agent run | fix · p1 · a:tooling+agentic · s:plan | +| [1202](https://github.com/rickylabs/netscript/issues/1202) | | fix(scaffold): users service Prisma binds a stale Postgres endpoint — DB health check fails on a clean scaffold.runtime run | fix · p1 · a:cli+database · s:plan | +| [1208](https://github.com/rickylabs/netscript/issues/1208) | | docs(tutorials): no tutorial demonstrates the page builder — every tutorial underleverages NetScript, and the MCP-served docs will teach the wrong patterns | docs · p0 · a:docs · s:plan | +| [1325](https://github.com/rickylabs/netscript/issues/1325) | | fix(triggers): generated background runtime omits the Redis adapter and crash-loops on the default Aspire cache | fix · p1 · a:plugins+aspire · s:triage | +| [1326](https://github.com/rickylabs/netscript/issues/1326) | | fix(streams): DurableStreamProducer permanently drops writes after an initial connection failure; reconnect is never attempted | fix · p0 · a:plugins · s:triage | +| [1327](https://github.com/rickylabs/netscript/issues/1327) | | fix(cli): db migrate reports success in headless mode without creating the migration implied by the command | fix · p1 · a:cli+database · s:triage | +| [1329](https://github.com/rickylabs/netscript/issues/1329) | | fix(streams): documented SSE consumer shape differs from the wire protocol and does not specify the standard event/OTEL envelope | fix · p0 · a:docs+plugins+telemetry · s:triage | +| [1332](https://github.com/rickylabs/netscript/issues/1332) | | docs(data/contracts): show generated DB schemas as the normative predecessor to API contracts in DB-backed products | docs · p1 · a:docs+database+contracts · s:triage | +| [1333](https://github.com/rickylabs/netscript/issues/1333) | | fix(scaffold/frontend): make the default app an idiomatic eis-chat-grade reference and derive its name from the project | fix · p0 · a:cli+fresh-ui+fresh · s:triage | +| [1334](https://github.com/rickylabs/netscript/issues/1334) | | docs(home): complete the capability story beyond end-to-end typesafety without turning the landing page into a catalog | docs · p1 · a:docs · s:triage | +| [1338](https://github.com/rickylabs/netscript/issues/1338) | | chore(agentic): make DeepSeek V4 Flash 0731 max the formal IMPL-EVAL default | chore · p0 · a:tooling+agentic · w:v1 · s:impl | + +### Milestone `0.0.6` — 22 open issues + +| # | U | Title | Labels (condensed) | +|---|---|-------|--------------------| +| [1085](https://github.com/rickylabs/netscript/issues/1085) | | fix(agentic): launch-codex-slice dies on SIGTERM and takes the Codex turn with it | fix · p2 · a:tooling+agentic · s:triage | +| [1093](https://github.com/rickylabs/netscript/issues/1093) | | plugin core: discovery hardcodes official plugins' factory functions - third-party plugins cannot participate | fix · p2 · a:plugins · s:triage | +| [1112](https://github.com/rickylabs/netscript/issues/1112) | | docs(database): make the MySQL Prisma adapter example honest and executable | docs · p1 · a:database · s:triage | +| [1139](https://github.com/rickylabs/netscript/issues/1139) | | [openapi-mcp S13] EndpointPolicy + invoke_service_operation (gated on F2) | feat · p2 · a:tooling · e:openapi-mcp · s:plan | +| [1140](https://github.com/rickylabs/netscript/issues/1140) | | [openapi-mcp S14] Wave observation: introspection calls vs blind curl (routes to #1090) | chore · p2 · a:tooling · e:openapi-mcp · s:plan | +| [1163](https://github.com/rickylabs/netscript/issues/1163) | | verify(milestone-run): 0.0.5 runs on the orchestrator skill + milestone-run profile as its first real execution | test · p1 · a:tooling+agentic · e:harness-v3 · s:triage | +| [1175](https://github.com/rickylabs/netscript/issues/1175) | | fix(ci): replace the fixed 120s JSR-propagation sleep with a version-named poll that reports what it waited for | fix · p3 · a:tooling · s:triage | +| [1201](https://github.com/rickylabs/netscript/issues/1201) | | mcp: serve the generated export surfaces, not just prose docs — the most-used doc surface has no MCP path | feat · p2 · a:docs+tooling+agentic · s:plan | +| [1210](https://github.com/rickylabs/netscript/issues/1210) | | docs(web-layer): differentiator deep-dives + competitive tutorial benchmark — per-API sub-pages for the page builder (withResource, Partials, …) | docs · p1 · a:docs · s:plan | +| [1243](https://github.com/rickylabs/netscript/issues/1243) | | auth: session list --stream-url default pins localhost:4437 which no longer exists post-#1211 | fix · p3 · a:auth · s:triage | +| [1246](https://github.com/rickylabs/netscript/issues/1246) | | windows: project-local node_modules/.deno materialization is incomplete — scaffolded frontend cannot start | fix · p1 · a:cli+fresh+deps · s:triage | +| [1260](https://github.com/rickylabs/netscript/issues/1260) | | mcp: include SDK guidance in the shipped search_docs corpus | feat · p2 · a:docs+tooling+agentic · s:triage | +| [1262](https://github.com/rickylabs/netscript/issues/1262) | | scaffold: db seed is a placebo — SELECT 1 plus a success banner, no rows seeded | fix · p2 · a:cli+database · s:triage | +| [1263](https://github.com/rickylabs/netscript/issues/1263) | | service: generated by-id handler returns 500 {defined:false} for a missing row instead of a defined 404 | fix · p2 · a:service+contracts · s:triage | +| [1278](https://github.com/rickylabs/netscript/issues/1278) | **EPIC** | Type soundness ratification: eliminate unsound and arbitrary types across the public surface and the docs | umbrella · p1 · a:docs+packages+contracts · s:triage | +| [1279](https://github.com/rickylabs/netscript/issues/1279) | **EPIC** | docs: migration chapter — per-framework guides, compatibility matrix, and e2e migration recipes | umbrella · p2 · a:docs · s:triage | +| [1280](https://github.com/rickylabs/netscript/issues/1280) | | aspire: backing services report no real health check — blocked on TypeScript AppHost custom health-check support | fix · p2 · a:aspire+database · s:blocked | +| [1293](https://github.com/rickylabs/netscript/issues/1293) | | prisma-adapter-mysql: adapter class is unexported and has no connection-error hook — the honest example needs both | feat · p2 · a:database+packages · s:triage | +| [1296](https://github.com/rickylabs/netscript/issues/1296) | | contracts/ai: source-side rows split out of the #1110/#1112/#1108 docs batch | fix · p2 · a:packages+contracts · s:triage | +| [1306](https://github.com/rickylabs/netscript/issues/1306) | | fix(aspire): 'the dashboard is the authority' is unusable for an agent — aspire start detaches in a non-TTY and prints no login token | feature · p1 · a:cli · s:triage | +| [1320](https://github.com/rickylabs/netscript/issues/1320) | | deps: collapse to a single Zod instance — blocked on @ag-ui/core hard ^3 and kvdex | fix · p2 · a:deps · s:blocked | +| [1343](https://github.com/rickylabs/netscript/issues/1343) | | verify(0.0.6): prove installed-consumer scaffold smoke against post-fix canary | test · p1 · a:cli+tooling+agentic · s:triage · g:e2e | + +### Milestone `0.0.7` — 20 open issues + +| # | U | Title | Labels (condensed) | +|---|---|-------|--------------------| +| [922](https://github.com/rickylabs/netscript/issues/922) | **EPIC** | Epic: Frontend contribution layer — plugins that ship UI | umbrella · p1 · a:plugins+fresh · e:frontend-contrib · s:plan | +| [923](https://github.com/rickylabs/netscript/issues/923) | | [frontend-contrib S1] P1 proof: mounted sub-app command ordering | chore · p0 · a:fresh · e:frontend-contrib · s:plan | +| [924](https://github.com/rickylabs/netscript/issues/924) | | [frontend-contrib S2] P2 proof: literal lazy route loaders + normalizeFreshRouteModule | chore · p0 · a:fresh · e:frontend-contrib · s:plan | +| [925](https://github.com/rickylabs/netscript/issues/925) | | [frontend-contrib S3] P3 proof: dependency-island build matrix + plugin-vite pin policy | chore · p0 · a:fresh · e:frontend-contrib · s:plan | +| [926](https://github.com/rickylabs/netscript/issues/926) | | [frontend-contrib S4] P4 proof: SSR failure-containment fixtures | chore · p0 · a:fresh · e:frontend-contrib · s:plan | +| [927](https://github.com/rickylabs/netscript/issues/927) | | [frontend-contrib S5] P5 proof: gateway threat model + streaming abort | chore · p0 · a:fresh · e:frontend-contrib · s:plan | +| [928](https://github.com/rickylabs/netscript/issues/928) | | [frontend-contrib S6] @netscript/plugin-frontend-core contracts/v1 | feat · p0 · a:plugins · e:frontend-contrib · s:plan | +| [929](https://github.com/rickylabs/netscript/issues/929) | | [frontend-contrib S7] @netscript/plugin pointer axis (.withFrontend) | feat · p0 · a:plugins · e:frontend-contrib · s:plan | +| [930](https://github.com/rickylabs/netscript/issues/930) | | [frontend-contrib S8] Frontend registry emissions: transactional replace-set | feat · p0 · a:cli · e:frontend-contrib · s:plan | +| [931](https://github.com/rickylabs/netscript/issues/931) | | [frontend-contrib S9] @netscript/fresh/plugins host runtime | feat · p0 · a:fresh · e:frontend-contrib · s:plan | +| [932](https://github.com/rickylabs/netscript/issues/932) | | [frontend-contrib S10] Scaffold template wiring + HostSurfaceDescriptor + vite feed | feat · p1 · a:cli · e:frontend-contrib · s:plan | +| [933](https://github.com/rickylabs/netscript/issues/933) | | [frontend-contrib S11] Workers dogfood: zone panel + console route + island | feat · p1 · a:plugins · e:frontend-contrib · s:plan | +| [934](https://github.com/rickylabs/netscript/issues/934) | | [frontend-contrib S12] Generated deny-by-default procedure gateway | feat · p1 · a:fresh · e:frontend-contrib · s:plan | +| [935](https://github.com/rickylabs/netscript/issues/935) | | [frontend-contrib S13] plugin new --with frontend | feat · p2 · a:cli · e:frontend-contrib · s:plan | +| [936](https://github.com/rickylabs/netscript/issues/936) | | [frontend-contrib S14] netscript plugin dev (frontend watch loop) | feat · p1 · a:cli · e:frontend-contrib · s:plan | +| [937](https://github.com/rickylabs/netscript/issues/937) | | [frontend-contrib S15] Doctor frontend check + five-state taxonomy | feat · p2 · a:cli · e:frontend-contrib · s:plan | +| [938](https://github.com/rickylabs/netscript/issues/938) | | [frontend-contrib S16] Quarantine render states + provenance chrome | feat · p2 · a:fresh · e:frontend-contrib · s:plan | +| [939](https://github.com/rickylabs/netscript/issues/939) | | [frontend-contrib S17] AppTarget scaffolder seam + plugin resource add --app | feat · p1 · a:plugins · e:frontend-contrib · s:plan | +| [940](https://github.com/rickylabs/netscript/issues/940) | | [frontend-contrib S18] defineFrontendTestSuite + budgets enforcement | feat · p1 · a:plugins · e:frontend-contrib · s:plan | +| [941](https://github.com/rickylabs/netscript/issues/941) | | [frontend-contrib S19] generate frontend-wiring adoption verb | feat · p2 · a:cli · e:frontend-contrib · s:plan | + +### Milestone `0.0.8` — 48 open issues + +| # | U | Title | Labels (condensed) | +|---|---|-------|--------------------| +| [511](https://github.com/rickylabs/netscript/issues/511) | | [process-manager PM-0] Wire linux-service/windows-service deploy targets + fix resolveTargetConfig key mismatch + de-gate flat verbs | fix · p1 · a:cli+deploy · e:process-manager · w:v1-min · s:plan | +| [512](https://github.com/rickylabs/netscript/issues/512) | | [process-manager PM-1] Process-graph contract v1 (ProcessGraphShape/ProcessSpecShape + state vocabulary + restart policies) | feat · p1 · a:plugins+config · e:process-manager · w:v1-min · s:plan | +| [513](https://github.com/rickylabs/netscript/issues/513) | | [process-manager PM-2] Telemetry: netscript.process domain + ProcessInstrumentationLike facade | feat · p1 · a:telemetry · e:process-manager · w:v1-min · s:plan | +| [514](https://github.com/rickylabs/netscript/issues/514) | | [process-manager PM-3] Core package scaffold: ports + KV registry adapter | feat · p1 · a:plugins+kv · e:process-manager · w:v1-min · s:plan | +| [515](https://github.com/rickylabs/netscript/issues/515) | | [process-manager PM-4] Restart controller: pure nextDelay(state, policy, clock) | feat · p1 · a:plugins · e:process-manager · w:v1-min · s:plan | +| [516](https://github.com/rickylabs/netscript/issues/516) | | [process-manager PM-5] Process runner: Deno.Command-native spawn + descendant-tree kill | feat · p1 · a:plugins · e:process-manager · w:v1-min · s:plan | +| [517](https://github.com/rickylabs/netscript/issues/517) | | [process-manager PM-6] Supervisor loop + composable start policies (autostart/cron/watch) | feat · p1 · a:plugins · e:process-manager · w:v1-min · s:plan | +| [518](https://github.com/rickylabs/netscript/issues/518) | | [process-manager PM-7] Log multiplexer + persistent sink (rotation) | feat · p2 · a:plugins · e:process-manager · w:v1 · s:plan | +| [519](https://github.com/rickylabs/netscript/issues/519) | | [process-manager PM-8] Dev loop + ShutdownManager integration (engine side of pm dev) | feat · p1 · a:cli+plugins · e:process-manager · w:v1-min · s:plan | +| [520](https://github.com/rickylabs/netscript/issues/520) | | [process-manager PM-9] ProcessManagerContract v1: the 18-route table | feat · p1 · a:plugins+service · e:process-manager · w:v1-min · s:plan | +| [521](https://github.com/rickylabs/netscript/issues/521) | | [process-manager PM-10] Control-plane oRPC service (PluginServiceContribution) | feat · p1 · a:plugins+service · e:process-manager · w:v1-min · s:plan | +| [522](https://github.com/rickylabs/netscript/issues/522) | | [process-manager PM-11] Loopback transport + address-descriptor discovery + CLI degraded mode | feat · p1 · a:plugins+service · e:process-manager · w:v1-min · s:plan | +| [523](https://github.com/rickylabs/netscript/issues/523) | | [process-manager PM-12] Token auth: opaque bearer + 0o600 secrets + deny-remote default | feat · p1 · a:plugins+service · e:process-manager · w:v1-min · s:plan | +| [524](https://github.com/rickylabs/netscript/issues/524) | | [process-manager PM-13] Event stream + follow-logs (eventIterator, live-only v1) | feat · p2 · a:plugins+service · e:process-manager · w:v1 · s:plan | +| [525](https://github.com/rickylabs/netscript/issues/525) | | [process-manager PM-14] Telemetry wiring: span/metric catalog + 14-point checklist | feat · p2 · a:plugins+telemetry · e:process-manager · w:v1 · s:plan | +| [526](https://github.com/rickylabs/netscript/issues/526) | | [process-manager PM-15] systemd renderer knobs: Type=notify, WatchdogSec, hardening, cgroups v2 | feat · p2 · a:deploy · e:process-manager · w:v1 · s:plan | +| [527](https://github.com/rickylabs/netscript/issues/527) | | [process-manager PM-16] Pure-Deno sd_notify helper (+ abstract-namespace spike) | feat · p2 · a:deploy · e:process-manager · w:v1 · s:plan | +| [528](https://github.com/rickylabs/netscript/issues/528) | | [process-manager PM-17] OsServicePort capability descriptor + warn-and-omit | feat · p2 · a:plugins+deploy · e:process-manager · w:v1 · s:plan | +| [529](https://github.com/rickylabs/netscript/issues/529) | | [process-manager PM-18] Conventions wiring: ServiceDeployTargetPorts → wired 8-op adapters | feat · p1 · a:deploy · e:process-manager · w:v1-min · s:plan | +| [530](https://github.com/rickylabs/netscript/issues/530) | | [process-manager PM-19] Compile adapters: ProcessSpec → systemd/Servy unit configs | feat · p1 · a:plugins+deploy · e:process-manager · w:v1-min · s:plan | +| [531](https://github.com/rickylabs/netscript/issues/531) | | [process-manager PM-20] Extract packages/deploy-core (@netscript/deploy-core) + promote F-DEPLOY gates | refactor · p2 · a:deploy · e:process-manager · w:v1 · s:plan | +| [532](https://github.com/rickylabs/netscript/issues/532) | | [process-manager PM-21] Deploy-facing schema: process-graph knobs on deploy.targets.linux/.windows | feat · p1 · a:config+deploy · e:process-manager · w:v1-min · s:plan | +| [533](https://github.com/rickylabs/netscript/issues/533) | | [process-manager PM-22] --no-aspire resolvers + pm explain provenance | feat · p1 · a:cli+plugins · e:process-manager · w:v1-min · s:plan | +| [534](https://github.com/rickylabs/netscript/issues/534) | | [process-manager PM-23] AspireResource[] resolver (manifest-wins precedence) | feat · p2 · a:cli+aspire · e:process-manager · w:v1 · s:plan | +| [535](https://github.com/rickylabs/netscript/issues/535) | | [process-manager PM-24] pm scaffold.plugin.json + plugin add typed glue (pm.config.ts) | feat · p2 · a:cli+plugins · e:process-manager · w:v1 · s:plan | +| [536](https://github.com/rickylabs/netscript/issues/536) | | [process-manager PM-25] netscript pm CLI router + read verbs (+ degraded-local reads) | feat · p1 · a:cli · e:process-manager · w:v1-min · s:plan | +| [537](https://github.com/rickylabs/netscript/issues/537) | | [process-manager PM-26] Lifecycle verbs via the shared CommandInvokePort shape | feat · p1 · a:cli · e:process-manager · w:v1-min · s:plan | +| [538](https://github.com/rickylabs/netscript/issues/538) | | [process-manager PM-27] pm dev: foreground multiplexer over the engine dev loop | feat · p1 · a:cli · e:process-manager · w:v1-min · s:plan | +| [539](https://github.com/rickylabs/netscript/issues/539) | | [process-manager PM-28] pm monitor: live status + follow stream (no TUI lib) | feat · p2 · a:cli · e:process-manager · w:v1 · s:plan | +| [540](https://github.com/rickylabs/netscript/issues/540) | | [process-manager PM-29] Admin console: Fresh app, browser-served by the control plane | feat · p2 · a:fresh-ui+service · e:process-manager · w:v1 · s:plan | +| [541](https://github.com/rickylabs/netscript/issues/541) | | [process-manager PM-30] Docs wave: 7 pages + cli-reference staleness fix | docs · p1 · a:docs · e:process-manager · w:v1-min · s:plan | +| [542](https://github.com/rickylabs/netscript/issues/542) | | [process-manager PM-31] Merge-readiness e2e: scaffold suites + JSR-scoped publish dry-run | test · p1 · a:cli · e:process-manager · w:v1-min · s:plan · g:e2e | +| [543](https://github.com/rickylabs/netscript/issues/543) | | [process-manager PM-32] Desktop packaging of the console (deno desktop, 5-target cross-compile) | feat · p2 · a:fresh-ui · e:process-manager · w:defer · s:plan | +| [734](https://github.com/rickylabs/netscript/issues/734) | | feat(plugin): dashboard-panel contribution axis in the plugin manifest | feat · p2 · a:plugins · w:v1 · s:triage | +| [742](https://github.com/rickylabs/netscript/issues/742) | | feat(sagas): saga definition versioning — .version() builder, versioned registry key, migration scaffold | feat · p2 · a:sagas · w:v1 · s:triage | +| [827](https://github.com/rickylabs/netscript/issues/827) | | feat(pm): graph adoption & reconcile contract [PM-A] | feat · p1 · a:plugins · e:process-manager · w:v1 · s:plan | +| [828](https://github.com/rickylabs/netscript/issues/828) | | feat(pm): supervised-child runtime helper — pipe-EOF liveness + descendant cleanup [PM-B] | feat · p1 · a:plugins · e:process-manager · w:v1 · s:plan | +| [829](https://github.com/rickylabs/netscript/issues/829) | | feat(plugins): official plugins ship compile-able ./services entrypoints | feat · p1 · a:plugins · w:v1 · s:plan | +| [844](https://github.com/rickylabs/netscript/issues/844) | | feat(pm): Windows Task Scheduler adapter — cron-policy processes compile to Scheduled Tasks [PM-C] | feat · p2 · a:plugins+deploy · e:process-manager · w:v1 · s:plan | +| [859](https://github.com/rickylabs/netscript/issues/859) | | test(deploy): native auto-update apply/rollback execution proof — pending upstream denoland/deno#36150 | test · p1 · a:cli · e:desktop-frontend · w:v1 · s:triage · g:e2e | +| [872](https://github.com/rickylabs/netscript/issues/872) | | [enterprise-auth S1] Make auth capability discovery backend-truthful | fix · p0 · a:auth+service · e:enterprise-auth · w:v1 · s:triage | +| [893](https://github.com/rickylabs/netscript/issues/893) | | [deploy-plugin DPB-1] Move deploy contracts to plugin-deploy-core behind compatibility re-exports | refactor · p0 · a:cli+deploy · e:deploy-plugin · s:plan | +| [894](https://github.com/rickylabs/netscript/issues/894) | | [deploy-plugin DPB-2] Move pure deploy conventions with their constants to core | refactor · p1 · a:cli+deploy · e:deploy-plugin · s:plan | +| [895](https://github.com/rickylabs/netscript/issues/895) | | [deploy-plugin DPB-3] Empty duplicate-rejecting core registry + CLI compatibility composition root | feat · p0 · a:cli+deploy · e:deploy-plugin · s:plan | +| [896](https://github.com/rickylabs/netscript/issues/896) | | [deploy-plugin DPB-4] Host-owned deploy shell split + router rewired over core contracts | refactor · p1 · a:cli+deploy · e:deploy-plugin · s:plan | +| [897](https://github.com/rickylabs/netscript/issues/897) | | [deploy-plugin DPB-5] Capability + topology contracts, rejection compiler, conformance harness | feat · p0 · a:deploy · e:deploy-plugin · s:plan | +| [898](https://github.com/rickylabs/netscript/issues/898) | | [deploy-plugin DPB-6] Two-phase config loader + deploy schema re-home + frozen legacy union | feat · p0 · a:config+deploy · e:deploy-plugin · s:plan | +| [950](https://github.com/rickylabs/netscript/issues/950) | | [AI-stack hardening] production agent lifecycle, completion policy, MCP routing, and conformance | feat · a:ai-core · e:ai-stack · w:v1 | + +### Milestone `0.0.9` — 15 open issues + +| # | U | Title | Labels (condensed) | +|---|---|-------|--------------------| +| [899](https://github.com/rickylabs/netscript/issues/899) | | [deploy-plugin DPB-7] Extract deploy-baremetal (build pipeline, Servy/systemd, compat handlers) | refactor · p1 · a:cli+deploy · e:deploy-plugin · s:plan | +| [900](https://github.com/rickylabs/netscript/issues/900) | | [deploy-plugin DPB-8] Extract deploy-aspire (target-by-op table, applier matrix, secret-safe state policy) | refactor · p1 · a:aspire+deploy · e:deploy-plugin · s:plan | +| [901](https://github.com/rickylabs/netscript/issues/901) | | [deploy-plugin DPB-9] Extract deploy-deno (honest manifest; no emit by design) | refactor · p1 · a:deploy · e:deploy-plugin · s:plan | +| [902](https://github.com/rickylabs/netscript/issues/902) | | [deploy-plugin DPB-10] Adapter-side config member schemas over the schema registry | refactor · p1 · a:config+deploy · e:deploy-plugin · s:plan | +| [903](https://github.com/rickylabs/netscript/issues/903) | | [deploy-plugin DPB-11] Legacy/config compatibility gate | test · p1 · a:cli+deploy · e:deploy-plugin · s:plan | +| [904](https://github.com/rickylabs/netscript/issues/904) | | [deploy-plugin DPB-12] Host: CLI mount-children contribution contract | feat · p1 · a:cli+plugins+deploy · e:deploy-plugin · s:plan | +| [905](https://github.com/rickylabs/netscript/issues/905) | | [deploy-plugin DPB-13] Host: async CLI bootstrap, loader isolation, plugin-absent UX | feat · p1 · a:cli+deploy · e:deploy-plugin · s:plan | +| [906](https://github.com/rickylabs/netscript/issues/906) | | [deploy-plugin DPB-14] Host: doctor-checks as data + installer tooling variant + contributionAxes | feat · p1 · a:plugins+deploy · e:deploy-plugin · s:plan | +| [907](https://github.com/rickylabs/netscript/issues/907) | | [deploy-plugin DPB-15] plugins/deploy: manifest triad, descriptor composition root, verify-plugin | feat · p1 · a:plugins+deploy · e:deploy-plugin · s:plan | +| [908](https://github.com/rickylabs/netscript/issues/908) | | [deploy-plugin DPB-16] Plugin CLI children: target add/remove, capabilities, cells apply, eight-op router | feat · p1 · a:cli+deploy · e:deploy-plugin · s:plan | +| [909](https://github.com/rickylabs/netscript/issues/909) | | [deploy-plugin DPB-17] Scaffolder: deploy/ leaf, Story-0 assets, conditional capability-check pipeline step | feat · p1 · a:deploy · e:deploy-plugin · s:plan | +| [910](https://github.com/rickylabs/netscript/issues/910) | | [deploy-plugin DPB-18] Story-0 scaffold.runtime E2E (install, target add, plan) | test · p1 · a:cli+deploy · e:deploy-plugin · s:plan | +| [911](https://github.com/rickylabs/netscript/issues/911) | | [deploy-plugin DPB-19] deploy-events stream, telemetry, runtime-config topic | feat · p2 · a:telemetry+deploy · e:deploy-plugin · s:plan | +| [920](https://github.com/rickylabs/netscript/issues/920) | | [deploy-plugin DPB-28] Docs: target-matrix reference + per-target how-tos replace the alpha-minimal page | docs · p1 · a:docs+deploy · e:deploy-plugin · s:plan | +| [944](https://github.com/rickylabs/netscript/issues/944) | | [frontend-contrib S22] Sagas/triggers/streams dashboard-zone panels | feat · p2 · a:plugins · e:frontend-contrib · s:plan | + +### Milestone `0.0.10` — 2 open issues + +| # | U | Title | Labels (condensed) | +|---|---|-------|--------------------| +| [873](https://github.com/rickylabs/netscript/issues/873) | | [enterprise-auth S2] Add a first-class Microsoft Entra ID OAuth/OIDC profile | feat · p1 · a:cli+auth · e:enterprise-auth · s:triage | +| [874](https://github.com/rickylabs/netscript/issues/874) | | [enterprise-auth S3] Compose multiple auth backends with tenant-aware routing | feat · p1 · a:auth+service · e:enterprise-auth · s:triage | + +### Milestone `0.0.11` — 10 open issues + +| # | U | Title | Labels (condensed) | +|---|---|-------|--------------------| +| [875](https://github.com/rickylabs/netscript/issues/875) | | [enterprise-auth S4] Complete the WorkOS AuthKit and enterprise SSO interactive adapter | feat · p1 · a:auth · e:enterprise-auth · s:triage | +| [876](https://github.com/rickylabs/netscript/issues/876) | | [enterprise-auth S5] Normalize per-tenant enterprise connection setup | feat · p1 · a:cli+auth · e:enterprise-auth · s:triage | +| [877](https://github.com/rickylabs/netscript/issues/877) | | [enterprise-auth S6] Integrate WorkOS Directory Sync and SCIM lifecycle | feat · p1 · a:auth+database · e:enterprise-auth · s:triage | +| [878](https://github.com/rickylabs/netscript/issues/878) | | [enterprise-auth S7] Define enterprise auth audit events and bridge WorkOS Audit Logs | feat · p1 · a:auth+telemetry · e:enterprise-auth · s:triage | +| [880](https://github.com/rickylabs/netscript/issues/880) | | [enterprise-auth S9] Introduce an enterprise auth secret-reference and rotation lifecycle | feat · p1 · a:cli+auth · e:enterprise-auth · s:triage | +| [912](https://github.com/rickylabs/netscript/issues/912) | | [deploy-plugin DPB-20] deploy-container: OCI build/push + ContainerBuildPort + Dockerfile emission | feat · p1 · a:deploy · e:deploy-plugin · s:plan | +| [913](https://github.com/rickylabs/netscript/issues/913) | | [deploy-plugin DPB-21] Thin platform clients: fly, koyeb, sevalla, coolify, dokploy + live smokes | feat · p1 · a:deploy · e:deploy-plugin · s:plan | +| [914](https://github.com/rickylabs/netscript/issues/914) | | [deploy-plugin DPB-22] Container scaffold story (Story 3) + artifact goldens | feat · p2 · a:deploy · e:deploy-plugin · s:plan | +| [942](https://github.com/rickylabs/netscript/issues/942) | | [frontend-contrib S20] Auth v1 frontend (account + session widget + signin starter) | feat · p1 · a:auth · e:frontend-contrib · s:plan | +| [943](https://github.com/rickylabs/netscript/issues/943) | | [frontend-contrib S21] AI frontend (durable chat route + assist launcher) | feat · p1 · a:plugin-ai · e:frontend-contrib · s:plan | + +### Milestone `0.0.12` — 11 open issues + +| # | U | Title | Labels (condensed) | +|---|---|-------|--------------------| +| [881](https://github.com/rickylabs/netscript/issues/881) | | [enterprise-auth S10] Generate Better Auth plugin schema and migrations | feat · p1 · a:cli+auth+database · e:enterprise-auth · s:triage | +| [882](https://github.com/rickylabs/netscript/issues/882) | | [enterprise-auth S11] Mount Better Auth interactive handlers and client integration | feat · p1 · a:cli+auth+service · e:enterprise-auth · s:triage | +| [883](https://github.com/rickylabs/netscript/issues/883) | | [enterprise-auth S12] Ship curated Better Auth capability profiles | feat · p1 · a:cli+auth · e:enterprise-auth · s:triage | +| [884](https://github.com/rickylabs/netscript/issues/884) | | [enterprise-auth S13] Define organization-aware identity and authorization policy contracts | feat · p1 · a:auth+service · e:enterprise-auth · s:triage | +| [885](https://github.com/rickylabs/netscript/issues/885) | | [enterprise-auth S14] Build an auth conformance, mocking, and scaffold test kit | feat · p1 · a:cli+auth+tooling · e:enterprise-auth · s:triage | +| [886](https://github.com/rickylabs/netscript/issues/886) | | [enterprise-auth S15] Authenticate machines, agents, CLIs, and MCP clients | feat · p1 · a:cli+auth+ai-core+service · e:enterprise-auth · s:triage | +| [915](https://github.com/rickylabs/netscript/issues/915) | | [deploy-plugin DPB-23] CF-PROBE: live Workers conformance, Miniflare fidelity, token story | feat · p1 · a:deploy · e:deploy-plugin · s:plan | +| [916](https://github.com/rickylabs/netscript/issues/916) | | [deploy-plugin DPB-24] deploy-cloudflare (workers variant) + Story 1 | feat · p1 · a:deploy · e:deploy-plugin · s:plan | +| [917](https://github.com/rickylabs/netscript/issues/917) | | [deploy-plugin DPB-25] Vercel probe + deploy-vercel (Build Output API) + Story 4 | feat · p1 · a:deploy · e:deploy-plugin · s:plan | +| [918](https://github.com/rickylabs/netscript/issues/918) | | [deploy-plugin DPB-26] AWS-PROBE-HTTP: live Lambda Web Adapter conformance | feat · p1 · a:deploy · e:deploy-plugin · s:plan | +| [919](https://github.com/rickylabs/netscript/issues/919) | | [deploy-plugin DPB-27] deploy-aws (lambda variant, HTTP scope) + Story 2 | feat · p1 · a:deploy · e:deploy-plugin · s:plan | + +### Milestone `0.0.13` — 44 open issues + +| # | U | Title | Labels (condensed) | +|---|---|-------|--------------------| +| [410](https://github.com/rickylabs/netscript/issues/410) | | [dashboard DDX-0] fresh-ui L3 blocks/ promotion + copy-source registry | feat · p1 · a:fresh-ui · e:dev-dashboard · w:v1 · s:plan · g:jsr | +| [411](https://github.com/rickylabs/netscript/issues/411) | | [dashboard DDX-1] @netscript/aspire command + app resource kinds | feat · p1 · a:aspire · e:dev-dashboard · w:v1 · s:plan | +| [412](https://github.com/rickylabs/netscript/issues/412) | | [dashboard DDX-2] plugin-dashboard-core scaffold + contract seam | feat · p1 · a:plugins · e:dev-dashboard · w:v1 · s:plan · g:jsr | +| [413](https://github.com/rickylabs/netscript/issues/413) | | [dashboard DDX-3] TelemetryQueryPort + aspire-otlp-http adapter | feat · p1 · a:plugins+telemetry · e:dev-dashboard · w:v1 · s:plan | +| [414](https://github.com/rickylabs/netscript/issues/414) | | [dashboard DDX-4] plugins/dashboard thin plugin + E2E join | feat · p1 · a:cli+plugins · e:dev-dashboard · w:v1 · s:plan · g:jsr | +| [415](https://github.com/rickylabs/netscript/issues/415) | | [dashboard DDX-5] Fresh build-console shell + app-registration + IA | feat · p1 · a:plugins+fresh-ui+fresh · e:dev-dashboard · w:v1 · s:plan | +| [416](https://github.com/rickylabs/netscript/issues/416) | | [dashboard DDX-6] Stack Map panel | feat · p1 · a:aspire+fresh-ui+fresh+config · e:dev-dashboard · w:v1 · s:plan | +| [417](https://github.com/rickylabs/netscript/issues/417) | | [dashboard DDX-7] Service Catalog + API Explorer panel | feat · p1 · a:cli+plugins+fresh-ui+fresh · e:dev-dashboard · w:v1 · s:plan | +| [418](https://github.com/rickylabs/netscript/issues/418) | | [dashboard DDX-8] S13: Live Flow — request journey across framework seams | feat · p1 · a:plugins+fresh-ui+fresh+telemetry · e:dev-dashboard · w:v1 · s:plan | +| [419](https://github.com/rickylabs/netscript/issues/419) | | [dashboard DDX-9] Run Inspector panel | feat · p1 · a:plugins+fresh-ui+fresh+telemetry · e:dev-dashboard · w:v1 · s:plan | +| [420](https://github.com/rickylabs/netscript/issues/420) | | [dashboard DDX-10] Plugin Control host + registry/overview | feat · p1 · a:plugins+aspire+fresh-ui+fresh · e:dev-dashboard · w:v1 · s:plan | +| [423](https://github.com/rickylabs/netscript/issues/423) | | [dashboard DDX-13] Introspection endpoint (/_netscript/*) | feat · p1 · a:cli+plugins+service+config · e:dev-dashboard · w:v1 · s:plan | +| [424](https://github.com/rickylabs/netscript/issues/424) | | [dashboard DDX-14] CLI surface + auto-launch | feat · p1 · a:cli+aspire · e:dev-dashboard · w:v1 · s:plan | +| [426](https://github.com/rickylabs/netscript/issues/426) | | [dashboard DDX-16] E2E dashboard join + panel smoke | test · p1 · a:cli+plugins · e:dev-dashboard · w:v1 · s:plan · g:e2e | +| [427](https://github.com/rickylabs/netscript/issues/427) | | [dashboard DDX-17] DashboardPanelContribution seam (.withDashboardPanel) | feat · p1 · a:plugins · e:dev-dashboard · w:v1 · s:plan · g:jsr | +| [428](https://github.com/rickylabs/netscript/issues/428) | | [dashboard DDX-18a] workers per-capability dashboard section | feat · p1 · a:plugins+fresh-ui+fresh · e:dev-dashboard · w:v1 · s:plan | +| [429](https://github.com/rickylabs/netscript/issues/429) | | [dashboard DDX-18b] sagas per-capability dashboard section | feat · p1 · a:plugins+fresh-ui+fresh · e:dev-dashboard · w:v1 · s:plan | +| [430](https://github.com/rickylabs/netscript/issues/430) | | [dashboard DDX-18c] triggers per-capability dashboard section | feat · p1 · a:plugins+fresh-ui+fresh · e:dev-dashboard · w:v1 · s:plan | +| [431](https://github.com/rickylabs/netscript/issues/431) | | [dashboard DDX-18d] streams per-capability dashboard section | feat · p2 · a:plugins+fresh-ui+fresh · e:dev-dashboard · w:v1 · s:plan | +| [432](https://github.com/rickylabs/netscript/issues/432) | | [dashboard DDX-19] Codegen-from-UI Add-resource action | feat · p2 · a:cli+plugins · e:dev-dashboard · w:defer · s:plan | +| [507](https://github.com/rickylabs/netscript/issues/507) | | feat(design): Dev Dashboard E2E Claude Design prototype + production design-sync system (tools/design-sync) | chore · p1 · a:tooling+fresh-ui · e:dev-dashboard · w:v1 · s:plan | +| [509](https://github.com/rickylabs/netscript/issues/509) | | fresh-ui: registry-wide pixel-perfect UI revamp (defaults, states, responsive/mobile, dark) + registry extensions | feat · p1 · a:fresh-ui · e:dev-dashboard · s:plan | +| [544](https://github.com/rickylabs/netscript/issues/544) | | [process-manager PM-33] DashboardPanelContribution "Process Control" panel | feat · p2 · a:fresh-ui · e:process-manager · w:defer · s:plan | +| [551](https://github.com/rickylabs/netscript/issues/551) | | [dashboard DDX-20] S3: Runtime-Config Monitor & Control (flagship) | feat · p1 · a:plugins+fresh-ui+config · e:dev-dashboard · w:v1 · s:triage | +| [552](https://github.com/rickylabs/netscript/issues/552) | | [dashboard DDX-21] S11: DB Migrations & Drift | feat · p2 · a:plugins+fresh-ui+database · e:dev-dashboard · w:v1 · s:triage | +| [553](https://github.com/rickylabs/netscript/issues/553) | | [dashboard DDX-22] S12: Dead-Letter Queues (queue + trigger) | feat · p2 · a:plugins+fresh-ui+queue · e:dev-dashboard · w:defer · s:triage | +| [554](https://github.com/rickylabs/netscript/issues/554) | | feat(triggers): TriggerDlqPort contract route (dashboard DLQ co-req) | feat · p2 · a:service · e:dev-dashboard · w:defer · s:triage | +| [555](https://github.com/rickylabs/netscript/issues/555) | | feat(queue): DeadLetterStore CLI + contract API (dashboard DLQ co-req) | feat · p2 · a:cli+queue · e:dev-dashboard · w:defer · s:triage | +| [556](https://github.com/rickylabs/netscript/issues/556) | | feat(runtime-config): mutation use-cases — set/unset + versioned current pointer bump (S3 write-back co-req) | feat · p2 · a:config · e:dev-dashboard · w:defer · s:triage | +| [557](https://github.com/rickylabs/netscript/issues/557) | | [dashboard DDX-23] seam-event flow plane: unified envelope + HTTP boundary events (S13 co-req) | feat · p2 · a:telemetry+service · e:dev-dashboard · w:defer · s:triage | +| [825](https://github.com/rickylabs/netscript/issues/825) | | feat(deploy): NetScript.Aspire.Packaging — .NET Aspire hosting integration (ATS-exported) for installer authoring | feat · p1 · a:aspire+deploy · e:deployment · w:v1 · s:plan | +| [831](https://github.com/rickylabs/netscript/issues/831) | | feat(deploy): PackagingModel + InstallGraphManifest compiler + Aspire publish step [SD-2] | feat · p2 · a:cli+aspire · e:deployment · w:v1 · s:plan | +| [832](https://github.com/rickylabs/netscript/issues/832) | | feat(desktop): supervisor host — embedded PM engine (per-user) / client mode (per-machine) [SD-1] | feat · p2 · a:plugins+service · e:deployment · w:v1 · s:plan | +| [833](https://github.com/rickylabs/netscript/issues/833) | | feat(deploy): installers — scopes, least-privilege ACLs, journaled operations, port registry [SD-3] | feat · p2 · a:cli+deploy · e:deployment · w:v1 · s:plan | +| [834](https://github.com/rickylabs/netscript/issues/834) | | feat(deploy): graph update transaction — N-artifact snapshots, barriers, quiescence [SD-4] | feat · p2 · a:cli+deploy · e:deployment · w:v1 · s:plan | +| [835](https://github.com/rickylabs/netscript/issues/835) | | feat(deploy): first-run provisioning phase [SD-5] | feat · p2 · a:cli · e:deployment · w:v1 · s:plan | +| [836](https://github.com/rickylabs/netscript/issues/836) | | feat(sdk): end-user health surface widget over the control plane [SD-6] | feat · p2 · a:fresh-ui+sdk · e:deployment · w:v1 · s:plan | +| [837](https://github.com/rickylabs/netscript/issues/837) | | docs/test(deploy): composition-modes doctrine + cross-mode conformance suite [SD-7] | test · p2 · a:docs+deploy · e:deployment · w:v1 · s:plan | +| [838](https://github.com/rickylabs/netscript/issues/838) | | test(deploy): graph deploy e2e — both scopes + full fault suite [SD-8] | test · p2 · a:deploy · e:deployment · w:v1 · s:plan · g:e2e | +| [839](https://github.com/rickylabs/netscript/issues/839) | | feat(deploy): Linux OS-enforced containment backstop — PDEATHSIG/cgroup spike [SD-H] | feat · p3 · a:deploy · e:deployment · w:defer · s:plan | +| [845](https://github.com/rickylabs/netscript/issues/845) | | feat(deploy): Windows hybrid tier — desktop window + PM-managed sidecars as Windows services & Scheduled Tasks | feat · p2 · a:cli+deploy · e:deployment · w:v1 · s:plan | +| [879](https://github.com/rickylabs/netscript/issues/879) | | [enterprise-auth S8] Add WorkOS RBAC/FGA authorization providers | feat · p1 · a:auth+service · e:enterprise-auth · s:triage | +| [945](https://github.com/rickylabs/netscript/issues/945) | | [frontend-contrib S23] auth-org backend capability (org-console prerequisite) | feat · p2 · a:auth · e:frontend-contrib · s:plan | +| [946](https://github.com/rickylabs/netscript/issues/946) | | [frontend-contrib S24] Convention generator (generate frontend) | feat · p3 · a:cli · e:frontend-contrib · s:plan | + +--- + +## 3. No-milestone and `Backlog / Triage` tables (61 issues) + +The three `(none)` issues are unmilestoned — a taxonomy violation per `AGENTS.md` ("assign an +explicit release milestone (`0.0.2`…`0.0.9` or `Backlog / Triage`)"). They are the single cheapest +board-hygiene fix in the roadmap. + +### Milestone `Backlog / Triage` — 58 open issues + +| # | U | Title | Labels (condensed) | +|---|---|-------|--------------------| +| [232](https://github.com/rickylabs/netscript/issues/232) | **EPIC** | epic: docs — march to 0.0.1-stable (coverage & accuracy) | umbrella · p1 · a:docs · s:plan | +| [234](https://github.com/rickylabs/netscript/issues/234) | | feat: HTTP/2 by default for NetScript services (feasibility + rollout) | feat · p2 · a:service · w:defer · s:research · rfc | +| [238](https://github.com/rickylabs/netscript/issues/238) | **EPIC** | epic: NetScript AI Stack — first-class AI runtime, chat & plugin seams (anchor #219) | umbrella · p1 · a:plugins+ai-core+plugin-ai · e:ai-stack · s:plan | +| [247](https://github.com/rickylabs/netscript/issues/247) | | [AI-stack E8] @netscript/ai: orchestration primitives (fan-out + bounded-cycle) | feat · p2 · a:ai-core · e:ai-stack · w:defer · s:plan · g:jsr | +| [248](https://github.com/rickylabs/netscript/issues/248) | | [AI-stack E9] @netscript/ai: OTel GenAI/MCP semconv telemetry adapter (./otel) | feat · p2 · a:ai-core+telemetry · e:ai-stack+telemetry-revamp · w:defer · s:plan · g:jsr | +| [256](https://github.com/rickylabs/netscript/issues/256) | | [AI-stack FB3] fresh-ui: paced-reveal streaming-UX hooks | feat · p2 · a:fresh-ui · e:ai-stack · w:defer · s:plan | +| [262](https://github.com/rickylabs/netscript/issues/262) | | [AI-stack P5] plugin-ai: opt-in --gateway centralized AI service | feat · p2 · a:plugin-ai · e:ai-stack · w:defer · s:plan · g:e2e/jsr | +| [266](https://github.com/rickylabs/netscript/issues/266) | | [AI-stack] usage/cost rollups + message feedback (product analytics) — track only | feat · p3 · a:telemetry · e:ai-stack · w:defer · s:triage | +| [271](https://github.com/rickylabs/netscript/issues/271) | | [AI-stack E12] @netscript/ai: skill-authoring approval-gate contract (create_skill/use_skill, deferred) | feat · p2 · a:ai-core · e:ai-stack · w:defer · s:plan | +| [272](https://github.com/rickylabs/netscript/issues/272) | | [AI-stack FB6] @netscript/fresh-ui: interactive MCP-App bridge (widget-action → tools/call → re-render, deferred) | feat · p2 · a:fresh-ui · e:ai-stack · w:defer · s:plan | +| [295](https://github.com/rickylabs/netscript/issues/295) | | Dogfood proof: Aspire Deno runtime (Layers A+B) verified against the full NetScript framework | test · p2 · a:cli+aspire+telemetry · w:defer · s:research | +| [301](https://github.com/rickylabs/netscript/issues/301) | **EPIC** | epic: Road to 0.0.1-stable | umbrella · p1 · a:tooling · s:plan | +| [302](https://github.com/rickylabs/netscript/issues/302) | | [S1] Positioning + netscript-bench | feat · p1 · a:tooling · w:defer · s:plan | +| [303](https://github.com/rickylabs/netscript/issues/303) | | [S2] Enterprise maturation + consolidation | refactor · p1 · a:tooling · w:v1 · s:plan | +| [307](https://github.com/rickylabs/netscript/issues/307) | | [S6] Stale-code + stale-file elimination | chore · p2 · a:tooling · w:v1 · s:plan | +| [309](https://github.com/rickylabs/netscript/issues/309) | | [S8] Release engineering + API-stability gates | feat · p1 · a:tooling · w:defer · s:plan | +| [313](https://github.com/rickylabs/netscript/issues/313) | **EPIC** | epic: migrate NetScript DB layer to Prisma Next (Postgres-first pilot, deferred) | umbrella · p2 · a:database · w:defer · s:plan · rfc | +| [314](https://github.com/rickylabs/netscript/issues/314) | | Prisma Next gap (a): MSSQL support (absent & unplanned) | feat · p2 · a:database+deps · w:defer · s:triage | +| [315](https://github.com/rickylabs/netscript/issues/315) | | Prisma Next gap (b): shipped Standard-Schema validator consumer | feat · p2 · a:database+deps · w:defer · s:triage | +| [316](https://github.com/rickylabs/netscript/issues/316) | | Prisma Next gap (c): MySQL engine (planned, no date) | feat · p3 · a:database+deps · w:defer · s:triage | +| [317](https://github.com/rickylabs/netscript/issues/317) | | Prisma Next gap (d): Turso/libsql driver (absent) | feat · p2 · a:database+deps · w:defer · s:triage | +| [318](https://github.com/rickylabs/netscript/issues/318) | | Prisma Next gap (e): beta/GA channel promotion off Early Access | chore · p2 · a:database+deps · w:defer · s:triage | +| [319](https://github.com/rickylabs/netscript/issues/319) | | Layer A tracking: Aspire TypeScript-AppHost Deno toolchain resolver (closes upstream aspire#16218) | feat · p2 · a:aspire · w:defer · s:research | +| [320](https://github.com/rickylabs/netscript/issues/320) | | Layer B tracking: Aspire Deno hosting (AddDenoApp / DenoAppResource) | feat · p2 · a:aspire · w:defer · s:research | +| [327](https://github.com/rickylabs/netscript/issues/327) | **EPIC** | epic: NetScript enterprise deployment framework (cloud-agnostic + bare-metal, CLI + Aspire) | umbrella · p2 · a:cli+aspire+deploy · e:deployment · s:plan | +| [345](https://github.com/rickylabs/netscript/issues/345) | | [Deploy-S9] Bare-metal enterprise hardening (stable): cross-host HA + external secret store + signing | feat · p2 · a:deploy · e:deployment · w:v1 · s:plan | +| [346](https://github.com/rickylabs/netscript/issues/346) | | [Deploy-S10] Aspire Kubernetes + Azure + Docker-image providers | feat · p2 · a:cli+aspire+deploy · e:deployment · w:v1 · s:plan | +| [348](https://github.com/rickylabs/netscript/issues/348) | | [Deploy-S12] One-click convergence + release-skill integration | feat · p2 · a:cli+deploy · e:deployment · w:v1 · s:plan | +| [350](https://github.com/rickylabs/netscript/issues/350) | | [Deploy-S14] WATCH: Pulumi #3838 Deno provider (IaC adapter feasibility) | chore · p3 · a:deploy · e:deployment · w:defer · s:triage | +| [400](https://github.com/rickylabs/netscript/issues/400) | **EPIC** | epic: NetScript Dev Dashboard — the Aspire/Scalar satellite that drives the framework (ships as a plugin, beta.6) | umbrella · p1 · a:plugins+aspire+fresh-ui+telemetry · e:dev-dashboard · w:v1 · s:plan | +| [451](https://github.com/rickylabs/netscript/issues/451) | | feat(sdk): in-process link-mode adapter for single-process service mounting | feat · p2 · a:sdk · e:deployment+unified-runtime · s:research · g:jsr | +| [453](https://github.com/rickylabs/netscript/issues/453) | | feat(desktop): tursodb single-writer relocation + in-process composition root | feat · p2 · a:cli+database · e:deployment+unified-runtime · s:research | +| [454](https://github.com/rickylabs/netscript/issues/454) | | feat(desktop): true single-process mode (option c) | feat · p2 · a:cli+sdk · e:deployment+unified-runtime · s:research | +| [455](https://github.com/rickylabs/netscript/issues/455) | | feat(desktop): offline-first via Turso Sync in the single-process host | feat · p3 · a:database · e:deployment+unified-runtime · s:research | +| [458](https://github.com/rickylabs/netscript/issues/458) | | feat(desktop): code-signing automation (macOS notarize / Windows signtool) | feat · p3 · a:cli · e:deployment · s:research | +| [499](https://github.com/rickylabs/netscript/issues/499) | | feat(ai-core): semantic recall adapter for AgentMemoryPort (E10) | feat · p2 · a:ai-core · e:ai-stack · w:defer · s:triage | +| [501](https://github.com/rickylabs/netscript/issues/501) | | feat(ai-core): schema-constrained structured output seam (generateObject) | feat · p3 · a:ai-core · e:ai-stack · w:defer · s:triage | +| [510](https://github.com/rickylabs/netscript/issues/510) | **EPIC** | Epic: NetScript Process Manager — bare-metal supervisor + admin console (pup/pm2 done right) | umbrella · p1 · a:cli+docs+plugins+telemetry+deploy · e:process-manager · w:v1 · s:plan · rfc | +| [545](https://github.com/rickylabs/netscript/issues/545) | | [process-manager PM-34] systemd --user + linger non-root install mode | feat · p3 · a:deploy · e:process-manager · w:defer · s:plan | +| [546](https://github.com/rickylabs/netscript/issues/546) | | [process-manager PM-35] Per-host multi-instance / clustering (template units + reusePort) | feat · p3 · a:plugins+deploy · e:process-manager · w:defer · s:plan | +| [695](https://github.com/rickylabs/netscript/issues/695) | | docs(tutorials): checkpoint-execution validation pass — run every track's checkpoints against a scaffolded app | docs · p2 · a:docs · e:docs-cut · s:triage | +| [820](https://github.com/rickylabs/netscript/issues/820) | | RFC: single deployment — enterprise installation layer, update lifecycle, PM foundation, single-runtime composition | p1 · a:deploy · s:research · rfc | +| [823](https://github.com/rickylabs/netscript/issues/823) | **EPIC** | epic: Unified Single-Runtime Deployment — Nitro v3 single deploy output (Next/Nuxt-class) | umbrella · p1 · a:fresh+deploy · e:unified-runtime · w:v1 · s:plan | +| [830](https://github.com/rickylabs/netscript/issues/830) | **EPIC** | epic: Desktop Singleton-Graph Deployment — install/update/supervise one artifact | umbrella · p2 · a:deploy · e:deployment · w:v1 · s:plan | +| [871](https://github.com/rickylabs/netscript/issues/871) | **EPIC** | Epic: Enterprise auth | umbrella · p1 · a:auth · e:enterprise-auth · s:triage | +| [887](https://github.com/rickylabs/netscript/issues/887) | | [enterprise-auth S16] Define outbound NetScript-as-IdP support | feat · p2 · a:auth+service · e:enterprise-auth · s:triage | +| [892](https://github.com/rickylabs/netscript/issues/892) | **EPIC** | Epic: Deploy plugin family | umbrella · p1 · a:plugins+deploy · e:deploy-plugin · s:plan | +| [921](https://github.com/rickylabs/netscript/issues/921) | | [deploy-plugin DPB-29] Deferred RFC: AWS event semantics, leaf backing graduation, Radius target graduation | feat · p2 · a:deploy · e:deploy-plugin · s:plan | +| [1244](https://github.com/rickylabs/netscript/issues/1244) | | chore(tooling): root docs:links does not scan docs/site — link-gate evidence gap | chore · p3 · a:agentic · s:triage | +| [1245](https://github.com/rickylabs/netscript/issues/1245) | | fix(fresh/query): island query types reject the package's own documented patterns | fix · p2 · a:packages · s:triage | +| [1249](https://github.com/rickylabs/netscript/issues/1249) | | fix(fresh/form): controlProps() is not element-assignable, and Zod 4 constraint derivation misses numbers and regex | fix · p2 · a:fresh · s:triage | +| [1255](https://github.com/rickylabs/netscript/issues/1255) | | fix(fresh): page.layer.delivery span attribute reports 'blocking' for deferring regions | fix · p3 · a:packages · s:triage | +| [1259](https://github.com/rickylabs/netscript/issues/1259) | | fix(cli): generated service aggregate health check fails on sqlite/libSQL — and intermittently on postgres | fix · p2 · a:cli+database · s:triage | +| [1273](https://github.com/rickylabs/netscript/issues/1273) | | ci: reserve the docker+postgres runtime tier for postgres-relevant changes — the saving #1158 set out to get | feat · p2 · a:tooling · s:triage | +| [1275](https://github.com/rickylabs/netscript/issues/1275) | **EPIC** | epic(docs): migration chapter — migrate-from guides, capability equivalence matrix, and end-to-end migration recipes | docs/umbrella · p2 · a:docs · s:triage | +| [1276](https://github.com/rickylabs/netscript/issues/1276) | **EPIC** | epic(quality): ratify and eliminate unsound types — no as-unknown-as, no arbitrary any, no conceded TS errors | umbrella · p1 · a:packages · s:triage | +| [1277](https://github.com/rickylabs/netscript/issues/1277) | **EPIC** | epic(docs-site): layout and UI polish pass — the desktop shell wastes space and has never had a design review | umbrella · p2 · a:docs · s:triage | +| [1335](https://github.com/rickylabs/netscript/issues/1335) | **EPIC** | Epic: Scaffold conformance — generated surfaces match current docs, exports and idiomatic usage | umbrella · p1 · a:cli+tooling · s:triage | + +### Milestone `(none)` — 3 open issues + +| # | U | Title | Labels (condensed) | +|---|---|-------|--------------------| +| [979](https://github.com/rickylabs/netscript/issues/979) | | fix(aspire): plugin API resources still pin host ports 8091–8094 | fix · p2 · a:plugins+aspire · s:triage | +| [980](https://github.com/rickylabs/netscript/issues/980) | | fix(cli): 'netscript service add' still pins an Aspire host port | fix · p3 · a:cli+aspire · s:triage | +| [1000](https://github.com/rickylabs/netscript/issues/1000) | | docs: Rename .NET Aspire to Aspire | s:triage · documentation | + +--- + +## 4. Open epics / umbrellas and their children + +20 open `type:umbrella` issues. Children are resolved two ways: the `epic:*` label group (machine +truth) and the body checkbox list (author truth). Where they disagree, the label group wins for +membership and the checkbox list is stale. + +#### #232 — epic: docs — march to 0.0.1-stable (coverage & accuracy) +- milestone: `Backlog / Triage` · labels: `area:docs, type:umbrella, status:plan, priority:p1` +- no machine-readable child list in body (prose-only umbrella) + +#### #238 — epic: NetScript AI Stack — first-class AI runtime, chat & plugin seams (anchor #219) +- milestone: `Backlog / Triage` · labels: `type:umbrella, area:plugins, area:ai-core, epic:ai-stack, area:plugin-ai, status:plan, priority:p1` +- `epic:ai-stack` open members (10): #247, #248, #256, #262, #266, #271, #272, #499, #501, #950 +- body checked children (20): #240, #241, #242, #243, #244, #245, #246, #249, #250, #251, #252, #253, #254, #255, #257, #258, #259, #260, #261, #263 +- body unchecked children (4): #247[open], #248[open], #256[open], #262[open] + +#### #301 — epic: Road to 0.0.1-stable +- milestone: `Backlog / Triage` · labels: `type:umbrella, area:tooling, status:plan, priority:p1` +- body checked children (1): #304 +- body unchecked children (12): #302[open], #303[open], #305[CLOSED], #306[CLOSED], #307[open], #309[open], #313[open], #327[open], #391[CLOSED], #399[CLOSED], #400[open], #401[CLOSED] + +#### #313 — epic: migrate NetScript DB layer to Prisma Next (Postgres-first pilot, deferred) +- milestone: `Backlog / Triage` · labels: `type:umbrella, wave:defer, status:plan, priority:p2, area:database, rfc` +- no machine-readable child list in body (prose-only umbrella) + +#### #327 — epic: NetScript enterprise deployment framework (cloud-agnostic + bare-metal, CLI + Aspire) +- milestone: `Backlog / Triage` · labels: `area:cli, type:umbrella, area:aspire, status:plan, priority:p2, area:deploy, epic:deployment` +- `epic:deployment` open members (21): #345, #346, #348, #350, #451, #453, #454, #455, #458, #825, #830, #831, #832, #833, #834, #835, #836, #837, #838, #839, #845 +- body checked children (13): #337, #338, #339, #340, #341, #342, #343, #344, #347, #349, #452, #456, #457 +- body unchecked children (9): #345[open], #346[open], #348[open], #350[open], #451[open], #453[open], #454[open], #455[open], #458[open] + +#### #400 — epic: NetScript Dev Dashboard — the Aspire/Scalar satellite that drives the framework (ships as a plugin, beta.6) +- milestone: `Backlog / Triage` · labels: `type:umbrella, area:plugins, area:aspire, wave:v1, area:fresh-ui, area:telemetry, status:plan, priority:p1, epic:dev-dashboard` +- `epic:dev-dashboard` open members (29): #410, #411, #412, #413, #414, #415, #416, #417, #418, #419, #420, #423, #424, #426, #427, #428, #429, #430, #431, #432, #507, #509, #551, #552, #553, #554, #555, #556, #557 + +#### #510 — Epic: NetScript Process Manager — bare-metal supervisor + admin console (pup/pm2 done right) +- milestone: `Backlog / Triage` · labels: `area:cli, area:docs, type:umbrella, area:plugins, wave:v1, area:telemetry, status:plan, priority:p1, rfc, area:deploy, epic:process-manager` +- `epic:process-manager` open members (39): #511, #512, #513, #514, #515, #516, #517, #518, #519, #520, #521, #522, #523, #524, #525, #526, #527, #528, #529, #530, #531, #532, #533, #534, #535, #536, #537, #538, #539, #540, #541, #542, #543, #544, #545, #546, #827, #828, #844 + +#### #823 — epic: Unified Single-Runtime Deployment — Nitro v3 single deploy output (Next/Nuxt-class) +- milestone: `Backlog / Triage` · labels: `type:umbrella, wave:v1, area:fresh, status:plan, priority:p1, area:deploy, epic:unified-runtime` +- `epic:unified-runtime` open members (4): #451, #453, #454, #455 + +#### #830 — epic: Desktop Singleton-Graph Deployment — install/update/supervise one artifact +- milestone: `Backlog / Triage` · labels: `type:umbrella, wave:v1, status:plan, priority:p2, area:deploy, epic:deployment` +- `epic:deployment` open members (21): #327, #345, #346, #348, #350, #451, #453, #454, #455, #458, #825, #831, #832, #833, #834, #835, #836, #837, #838, #839, #845 + +#### #871 — Epic: Enterprise auth +- milestone: `Backlog / Triage` · labels: `type:umbrella, area:auth, status:triage, priority:p1, epic:enterprise-auth` +- `epic:enterprise-auth` open members (16): #872, #873, #874, #875, #876, #877, #878, #879, #880, #881, #882, #883, #884, #885, #886, #887 +- body unchecked children (16): #872[open], #873[open], #874[open], #875[open], #876[open], #877[open], #878[open], #879[open], #880[open], #881[open], #882[open], #883[open], #884[open], #885[open], #886[open], #887[open] + +#### #892 — Epic: Deploy plugin family +- milestone: `Backlog / Triage` · labels: `type:umbrella, area:plugins, status:plan, priority:p1, area:deploy, epic:deploy-plugin` +- `epic:deploy-plugin` open members (29): #893, #894, #895, #896, #897, #898, #899, #900, #901, #902, #903, #904, #905, #906, #907, #908, #909, #910, #911, #912, #913, #914, #915, #916, #917, #918, #919, #920, #921 +- body unchecked children (29): #893[open], #894[open], #895[open], #896[open], #897[open], #898[open], #899[open], #900[open], #901[open], #902[open], #903[open], #904[open], #905[open], #906[open], #907[open], #908[open], #909[open], #910[open], #911[open], #912[open], #913[open], #914[open], #915[open], #916[open], #917[open], #918[open], #919[open], #920[open], #921[open] + +#### #922 — Epic: Frontend contribution layer — plugins that ship UI +- milestone: `0.0.7` · labels: `type:umbrella, area:plugins, area:fresh, status:plan, priority:p1, epic:frontend-contrib` +- `epic:frontend-contrib` open members (24): #923, #924, #925, #926, #927, #928, #929, #930, #931, #932, #933, #934, #935, #936, #937, #938, #939, #940, #941, #942, #943, #944, #945, #946 +- body unchecked children (24): #923[open], #924[open], #925[open], #926[open], #927[open], #928[open], #929[open], #930[open], #931[open], #932[open], #933[open], #934[open], #935[open], #936[open], #937[open], #938[open], #939[open], #940[open], #941[open], #942[open], #943[open], #944[open], #945[open], #946[open] + +#### #1126 — Epic: OpenAPI→MCP service introspection — agent-legible service APIs +- milestone: `0.0.5` · labels: `type:umbrella, area:tooling, status:plan, priority:p1, area:service, epic:openapi-mcp` +- `epic:openapi-mcp` open members (4): #1137, #1138, #1139, #1140 +- body checked children (1): #1127 +- body unchecked children (13): #1128[CLOSED], #1129[CLOSED], #1130[CLOSED], #1131[CLOSED], #1132[CLOSED], #1133[CLOSED], #1134[CLOSED], #1135[CLOSED], #1136[CLOSED], #1137[open], #1138[open], #1139[open], #1140[open] + +#### #1169 — epic: guarantee a one-pass publish — eliminate the non-deterministic failures that made 0.0.4 take three canaries and six reruns +- milestone: `0.0.5` · labels: `type:umbrella, area:tooling, status:triage, priority:p1, epic:harness-v3` +- `epic:harness-v3` open members (2): #1163, #1166 + +#### #1275 — epic(docs): migration chapter — migrate-from guides, capability equivalence matrix, and end-to-end migration recipes +- milestone: `Backlog / Triage` · labels: `area:docs, type:docs, type:umbrella, status:triage, priority:p2` +- no machine-readable child list in body (prose-only umbrella) + +#### #1276 — epic(quality): ratify and eliminate unsound types — no as-unknown-as, no arbitrary any, no conceded TS errors +- milestone: `Backlog / Triage` · labels: `type:umbrella, status:triage, priority:p1, area:packages` +- no machine-readable child list in body (prose-only umbrella) + +#### #1277 — epic(docs-site): layout and UI polish pass — the desktop shell wastes space and has never had a design review +- milestone: `Backlog / Triage` · labels: `area:docs, type:umbrella, status:triage, priority:p2` +- no machine-readable child list in body (prose-only umbrella) + +#### #1278 — Type soundness ratification: eliminate unsound and arbitrary types across the public surface and the docs +- milestone: `0.0.6` · labels: `area:docs, type:umbrella, status:triage, priority:p1, area:packages, area:contracts` +- no machine-readable child list in body (prose-only umbrella) + +#### #1279 — docs: migration chapter — per-framework guides, compatibility matrix, and e2e migration recipes +- milestone: `0.0.6` · labels: `area:docs, type:umbrella, status:triage, priority:p2` +- no machine-readable child list in body (prose-only umbrella) + +#### #1335 — Epic: Scaffold conformance — generated surfaces match current docs, exports and idiomatic usage +- milestone: `Backlog / Triage` · labels: `area:cli, type:umbrella, area:tooling, status:triage, priority:p1` +- body unchecked children (1): #1328[CLOSED] + +### 4.1 Umbrella findings (facts) + +- **Stale checkbox state (verified against closed set):** + - `#301` "Road to 0.0.1-stable" — 5 unchecked children are already CLOSED: #305, #306, #391, #399, #401. + - `#1126` "OpenAPI→MCP" — 9 unchecked children are already CLOSED: #1128–#1136. Only #1137, #1138, #1139, #1140 remain open. The epic reads far less complete than it is. + - `#1335` "Scaffold conformance" — its only listed sub-issue #1328 is **CLOSED** (2026-08-07, `status:shipped`, `canary:0.0.5-canary.15`). +- **No umbrella has a checked box pointing at a still-open issue** (no false-complete rows). +- **Six umbrellas carry no machine-readable child list at all** — #232, #313, #400 (label group only), #510 (label group only), #1275, #1276, #1277, #1278, #1279. For #1276/#1277/#1278/#1279 there is *no* `epic:` label and *no* checkbox list, so their scope exists only as prose. These are the highest-risk duplicate-filing surfaces. +- **`epic:*` label groups that have no umbrella issue of their own:** `epic:desktop-frontend` (only #859), `epic:docs-cut` (only #695), `epic:telemetry-revamp` (only #248, which is also `epic:ai-stack`). These are orphaned single-member epic labels. +- **Cross-epic double membership:** #451, #453, #454, #455 carry both `epic:deployment` and `epic:unified-runtime`; #830 is itself an umbrella *and* a member of `epic:deployment` (child of #327). #248 sits in both `epic:ai-stack` and `epic:telemetry-revamp`. + +### 4.2 Duplicate / near-duplicate umbrella pairs (dedup-critical) + +These are the clearest duplicate filings on the current board. Confirmed by reading both bodies. + +| Backlog copy | Milestoned copy | Evidence | +|---|---|---| +| **#1276** `epic(quality): ratify and eliminate unsound types` (Backlog/Triage) | **#1278** `Type soundness ratification` (0.0.6) | Same owner directive dated 2026-08-04, same evidence set (`query-bridge.md` `as unknown as IslandQueryClient` + TS2551/TS2345, `BaseContractProcedure = Readonly<{ ~orpc: any }>`, `observedEvents: any[]`, the 7 `quality:scan` allowances, `packages/cli/src/public/public-api.ts` ×5, `plugins/workers/streams/producer.ts`). #1276 organizes as tranches T1–T6; #1278 as inventory A/B/C/D. Both subsume #1245 and #1249. **One must be closed as duplicate or explicitly demoted to the epic-of-record.** | +| **#1275** `epic(docs): migration chapter` (Backlog/Triage) | **#1279** `docs: migration chapter` (0.0.6) | Identical title semantics: "migrate-from guides, capability equivalence matrix, end-to-end migration recipes" vs "per-framework guides, compatibility matrix, e2e migration recipes". Both `type:umbrella`. | + +Additional overlaps that are *not* strict duplicates but will produce duplicate slices if planned independently: + +- **#823** (`epic:unified-runtime`, Nitro v3 single deploy output) vs **#327** (`epic:deployment`, enterprise deployment framework) vs **#830** (`epic:deployment`, desktop singleton graph) — #823's entire open membership (#451, #453, #454, #455) is *inside* #327's unchecked child list. Three umbrellas, one child set. +- **#892** (`epic:deploy-plugin`, 29 open children) vs **#327**/**#830** — deploy plugin family vs deployment framework; no cross-reference in either body. +- **#400** (`epic:dev-dashboard`, 29 open children) vs **#922** (`epic:frontend-contrib`, 24 open children) — #922 names #400 as its first consumer ("this epic's core lands in the same beta.13 cut as its first consumer"), and #427/#432 are explicitly "KEEP-and-re-baseline per the RFC's supersession map". The re-baseline has not happened; both sets are still open verbatim. +- **#1335** (scaffold conformance) vs **#1333** (scaffold frontend modernization) — #1335's body names #1333's work as "Frontend scaffold modernization and dynamic app naming" but as a plain-text row, not a linked checkbox. A dedup pass must not re-file it. + +--- + +## 5. Milestone-shape observations (facts, then hypotheses) + +### 5.1 Facts + +- **0.0.5 is the active release and still has 21 open issues**, including four `p0` (#1208, #1326, #1329, #1333) and one `status:impl` (#1338). Recent merged PRs (#1346, #1344, #1342, #1341, #1340 in local `git log`) show canary.14/canary.15 already cut, so 0.0.5 is mid-canary with p0 work outstanding. +- **0.0.2 still holds 5 open issues** (#175, #767, #768, #863, #864) although 0.0.3, 0.0.4 and most of 0.0.5 have shipped. #175 carries **no labels at all**. +- **0.0.8 (48) and 0.0.13 (44) are the two largest forward milestones**; 0.0.13 is almost entirely `epic:process-manager` + `epic:deploy-plugin` tail, i.e. a dumping ground rather than a release plan. +- **0.0.6 (22) mixes** three umbrellas (#1278, #1279), docs-leverage phase 3 (#1210), MCP corpus work (#1201, #1260), a blocked Aspire item (#1280), and the post-canary verification (#1343). +- **`status:` distribution is not release-shaped**: 162 `status:plan` and 82 `status:triage` against a single `status:impl`. 82 triage items means roughly a third of the board has never been groomed. +- **Exactly two `status:blocked` issues**, both in 0.0.6: **#1280** (Aspire TypeScript AppHost lacks custom health-check registration; Deno KV Connect exposes no health endpoint — both upstream) and **#1320** (`deps: collapse to a single Zod instance — blocked on @ag-ui/core hard ^3 and kvdex`). +- **19 open `priority:p0`**, distributed **0.0.5 = 5** (#1208, #1326, #1329, #1333, #1338), **0.0.7 = 9** (#923–#931, the entire frontend-contrib Wave-0 + contract spine), **0.0.8 = 5** (#872, #893, #895, #897, #898). Zero p0 in Backlog. So p0 pressure is concentrated in exactly three milestones and 0.0.7's p0 block is a single epic's critical path. +- **Board-hygiene gaps (exact):** issues with **no `priority:`** label — #175, #950, #1000. Issues with **no `status:`** label — #175, #950. Issue with **no labels at all** — **#175**. Issues with **no milestone** — #979, #980, #1000. Legacy non-namespaced labels still in use: `rfc` on #234, #313, #510, #820; `documentation` on #1000 only. + +### 5.2 Hypotheses (flagged as such — not verified) + +- The Backlog/Triage bucket (58) functions as a second epic registry, not a triage queue: 11 of the 20 open umbrellas live there. A roadmap that treats Backlog as "unplanned" will mis-scope ~200 transitively-owned children. +- 0.0.13 looks like a horizon milestone rather than a dated cut; its 44 issues are almost all `epic:process-manager`/`epic:deploy-plugin` leaves whose parents are unmilestoned in Backlog. + +--- + +## 6. Detailed body summaries for the 31 pre-plan key issues + +**Two of the named issues are CLOSED — current GitHub state overrides the pre-plan:** + +| # | State | Detail | +|---|---|---| +| **1328** | **CLOSED 2026-08-07** | `fix(scaffold): generated check misses TSX/plugin runtimes while bare lint/fmt report 154 scaffold-owned findings` — milestone 0.0.5, labels `type:fix, area:cli, area:tooling, priority:p1, status:shipped, canary:0.0.5-canary.15`. Still referenced as an *open* dependency by #1333 ("Related: … #1328") and as the only sub-issue checkbox of #1335. | +| **1184** | **CLOSED 2026-08-04** | `sagas: generated runtime glue registers no KV adapter — saga runner crashes on a default scaffold` — milestone 0.0.5, labels `type:fix, status:ci-fail, priority:p1, area:kv, area:sagas, canary:0.0.5-canary.2`. This is the *precedent* #1325 invokes ("the triggers sibling of the saga generated-glue defect fixed in #1184"). | + +The remaining 29 are open; summaries follow. + +### 6.1 Scaffold / generated-surface conformance cluster + +#### #1333 — `fix(scaffold/frontend): make the default app an idiomatic eis-chat-grade reference and derive its name from the project` +- **Milestone** 0.0.5 · `type:fix, area:cli, area:fresh-ui, area:fresh, status:triage, priority:p0` +- **Contract.** The scaffold ships strong frontend capability but its *default product surface* does not make the idiomatic patterns canonical. Evidence: a real Wave 6 agent (`rickylabs/loom`) received the Fresh-UI registry, `/design`, app `AGENTS.md`/`WEB-LAYER.md` and advanced templates, yet built routes with hand-rolled tables/buttons/forms/CSS, direct service calls, and a **676-line island**. `eis-chat` is named as the intended ceiling (app-owned registry components, layered `definePage`, `withForm`, QueryIsland/query factories, cache hydration, optimistic mutation, partial navigation, generated DB schemas, error contracts, live StreamDB, telemetry, route-local organization). Second defect: `SCAFFOLD_DEFAULTS.APP_NAME = 'dashboard'` is hardcoded, so every non-interactive scaffold produces a "dashboard". +- **Acceptance (10 boxes).** Default routes use app-owned Fresh-UI components; canonical resource flow = route contract → typed SDK/query factory → layered page builder → QueryIsland hydration/cache-first → optimistic mutation/rollback; managed forms + loading/error/empty/success + partial navigation + telemetry + auth-ready boundary in executable starter code; generated DB schemas feed versioned contracts; `/design` and `/design/composition` named as the living reference; resource-local `(_components)`/`(_islands)`; existing examples upgraded not deleted; omitted `--app-name` derives a project-appropriate name (explicit flag stays authoritative); Fresh scaffold golden/runtime tests; **a measured agent smoke** that adopts-or-explicitly-rejects the built-ins. +- **Dependencies.** "Related: #1071, #1073, #1208, #1210, **#1328**" — #1328 is now CLOSED, so that dependency is discharged. + +#### #1335 — `Epic: Scaffold conformance — generated surfaces match current docs, exports and idiomatic usage` +- **Milestone** Backlog / Triage · `type:umbrella, area:cli, area:tooling, status:triage, priority:p1` +- **Contract.** No single conformance audit proves every scaffolded file uses current exports, current conventions, and the primitives the docs recommend. Explicitly *does not* duplicate #1328 (quality gates + 154 findings) or the frontend modernization issue; it owns the repository-wide inventory. +- **Scope.** Inventory every generated file and conditional variant across CLI scaffolding, apps, services, contracts, databases, plugins, background runtimes, Aspire config, examples, design routes, agent guidance, tasks, READMEs. Per file record: generator/template source; current public export demonstrated; canonical docs link; classification (required / optional example / generated artifact / removable noise); check-lint-fmt-runtime coverage; golden + published-canary conformance. +- **Acceptance.** Machine-readable generated-surface inventory mapping template → emitted file → docs → public export; every example checked against current exports; stale/duplicated/misleading noise removed; specialized issues land before rows are ticked; golden tests + scaffold runtime E2E; published-canary smoke proving local-source and published output match; a measured unfamiliar-agent run. +- **Dependencies.** Sub-issue list = `#1328` (CLOSED) + "Frontend scaffold modernization and dynamic app naming" (= #1333, unlinked) + "further surface-specific issues". States: "This is an umbrella. No implementation PR should close it directly." + +#### #1325 — `fix(triggers): generated background runtime omits the Redis adapter and crash-loops on the default Aspire cache` +- **Milestone** 0.0.5 · `type:fix, area:plugins, area:aspire, status:triage, priority:p1` +- **Contract.** `plugins/triggers/src/adapter/resources/glue/runtime.stub.ts` emits `triggers/runtime.ts` with **no `import '@netscript/kv/redis'`**. Against the default Aspire Redis/Garnet cache the generated trigger runtime crash-loops until the user hand-edits a generated file (regeneration-unsafe). +- **Repro.** Scaffold on `0.0.5-canary.13` → install triggers → keep default Aspire cache → generate plugin resources → start AppHost → trigger resource fails (no adapter for detected Redis/Garnet provider) → add the import → restarts fine. +- **Acceptance.** Generated trigger runtime resolves/registers the configured KV adapter; default fresh scaffold starts with no manual edit; both Redis/Garnet and `CACHE_PROVIDER=denokv` covered; **RED-first generated-output test**; scaffold runtime E2E installs *every* KV-backed first-party background runtime and proves each reaches healthy; the invariant is shared/enumerated so a saga fix cannot ship while the trigger sibling is broken. +- **Dependencies.** Declared as "the triggers sibling of the saga generated-glue defect fixed in **#1184**" (#1184 CLOSED 2026-08-04, canary.2). The generalization requirement is the real deliverable. + +#### #1327 — `fix(cli): db migrate reports success in headless mode without creating the migration implied by the command` +- **Milestone** 0.0.5 · `type:fix, area:cli, area:database, status:triage, priority:p1` +- **Contract.** After a schema change, `netscript db migrate` in a non-TTY session can exit 0 having only *deployed existing* migrations — creating none for the change, and not treating that absence as failure. Violates the artifact semantics established by `db init` and `db generate`: success must mean the named artifact-producing operation happened and is verifiable from its artifact. +- **Acceptance.** Consistent artifact semantics across `db init`/`db generate`/`db migrate`; schema-change + success ⇒ verified migration artifact; headless inability to create ⇒ explicit non-zero with actionable next command; deploy-only behavior gets an unambiguous name and cannot masquerade; output separates created vs applied; E2E covers TTY and non-TTY and verifies files **plus** DB state, not exit codes. +- **Environment.** WSL2, non-interactive OpenCode session, `0.0.5-canary.13`, PostgreSQL/Prisma scaffold. + +#### #1332 — `docs(data/contracts): show generated DB schemas as the normative predecessor to API contracts in DB-backed products` +- **Milestone** 0.0.5 · `type:docs, area:docs, area:database, area:contracts, status:triage, priority:p1` +- **Contract.** Docs teach contract-first starting from a hand-written Zod API contract. In a DB-backed product, `db generate` already emits model schemas via `@database/zod`; mirroring them by hand is drift. Reproduced in Wave 6 Loom (contracts import no generated DB schema, hand-write Workflow/Node/Edge/Run/Step even though the import map exposes `@database/zod`); `eis-chat` by contrast imports generated `ChannelModelSchema`. Both truths must survive: DB-less products author the contract first; once a DB model exists, reuse/narrow/extend the generated schema is normative. +- **Locations.** Homepage contract/type-flow diagram + tabs, `docs/site/explanation/contracts.md`, `docs/site/web-layer/route.md`, cross-links from DB generation into server/builders/route docs. +- **Acceptance.** Type-flow diagram gains optional predecessor `DB model → db generate → @database/zod → narrowed/extended versioned API schema → handler/OpenAPI/SDK/page`; three-tab example gains optional Tab 0; examples explicitly omit persistence-only/private fields; both paths described with DB-backed identified as the norm when generated schemas exist; bidirectional links; all examples type-check against current generated exports incl. a multi-model relation case; a docs test/fixture prevents import-path drift. +- **Dependencies.** "Related: #1254 (shipped the multi-model generated schema barrel), #1210." + +### 6.2 Streams / durable-runtime cluster + +#### #1326 — `fix(streams): DurableStreamProducer permanently drops writes after an initial connection failure; reconnect is never attempted` +- **Milestone** 0.0.5 · `type:fix, area:plugins, status:triage, priority:p0` +- **Contract.** In `packages/plugin-streams-core/src/application/create-durable-stream.ts`, `#connect` runs **once**; on failure it sets `#connectError` and returns, and `#appendEvent` drops every event while that field is set. No timer, retry policy, or reconnect transition clears it. The log line promises "until reconnect" — a transition that cannot occur. A transient stream-service startup race therefore permanently disables live publication for the process lifetime. +- **Acceptance.** Initial *and* later transport failures enter a documented reconnect state; explicit retry/backoff, cancellation, readiness and shutdown semantics; explicit buffer bounds and overflow behavior (no silent loss); producer recovers when the server starts after it; tests for initial outage / mid-session outage / recovery / event ordering / shutdown-during-backoff; OTEL spans+metrics for connection state, retries, dropped/buffered events, recovery, using the standardized stream event envelope; **operator messages never promise a transition the implementation cannot perform**. +- **Dependency.** The "standardized stream event envelope" is defined by #1329 — these two must be planned as a pair. + +#### #1329 — `fix(streams): documented SSE consumer shape differs from the wire protocol and does not specify the standard event/OTEL envelope` +- **Milestone** 0.0.5 · `type:fix, area:docs, area:plugins, area:telemetry, status:triage, priority:p0` +- **Contract.** `docs/site/durable-workflows/streams.md` teaches `source.onmessage` parsing one `{ key, value? }` change. The real `0.0.5-canary.13` wire emits **named `data` events whose payload is a JSON array of changes**, plus named **`control`** events carrying offsets. The documented code receives nothing. Deeper: there is no single documented standardized NetScript stream event envelope, and no statement of how correlation + W3C trace context cross the SSE boundary. Real consumer evidence: `rickylabs/loom` `apps/dashboard/islands/LoomCanvas.tsx` uses named `data` events and array payloads after runtime reverse-engineering. Endpoint: `/v1/stream//?live=sse&offset=0_0`. +- **Acceptance (8).** One exported versioned schema defining every SSE event name and payload (data batches, control/offset frames, errors, heartbeats); server emission + generated consumers + Fresh helpers + docs derived from or conformance-tested against it; the official example works unchanged against a real service; replay offsets/ordering/batching/deletion/reconnect/malformed-frame documented; each data envelope carries standardized correlation identity + `traceparent`/`tracestate`; **Aspire OTEL proof of producer → durable stream → SSE consumption in one correlated trace**; contract tests fail on event-name/envelope/cardinality/telemetry-field drift; complete shapes appear in task docs and generated reference API docs. + +### 6.3 Docs-leverage program (#1208 → phase 2 → #1210) and MCP corpus + +#### #1208 — `docs(tutorials): no tutorial demonstrates the page builder …` +- **Milestone** 0.0.5 · `type:docs, area:docs, status:plan, priority:p0` +- **Contract.** Owner-filed 2026-08-04, urgent — an agent launch waits on canary.2 and will consume these docs *through the MCP*, which multiplies the harm. **Phase 1 (this issue, merges FIRST):** primary tutorials demonstrate the page builder as the default way to build pages, with real type-checked examples exercising `withResource`, `withLayer`, `withLayout`, `withForm`, cache-first against the SDK, server+client dehydration, traces, and contract-first route implementation — the feature inventory verified against the **actual exported surface via `deno doc`**, not from memory. Every example compiles against published entrypoints with verification commands quoted. Tutorials stop teaching hand-rolled patterns where a page-builder feature exists (each replaced usage named). +- **Phase 2** (separate follow-up after phase 1 merges): full inconsistency-and-underleverage sweep across all tutorials, tracked as a checklist comment on #1208 when phase 1 lands. **This phase-2 issue does not exist yet on the board** — dedup risk: a roadmap may re-file it. +- **Verification.** Docs checks green; examples type-check; changed-file audit clean (docs lane — no `packages/`/`plugins/` source). + +#### #1210 — `docs(web-layer): differentiator deep-dives + competitive tutorial benchmark` +- **Milestone** 0.0.6 · `type:docs, area:docs, status:plan, priority:p1` +- **Contract.** Phase 3 of the docs-leverage program. Two named exemplars, explicitly not the full scope — **every** page-builder API gets the same treatment. `withResource` deep-dive: request deduplication across layers, shared resources refined per layer, idiomatic for auth and context/URL-aware queries — "the owner has never seen it used in a single agent demo; the underleverage is measured, not hypothetical." **Partials**: NetScript's Fresh partial builder + helpers + API mechanism vs the ceremony bare Fresh requires; composes with deferred loaders. +- **Acceptance.** Web Layer manual gains a **per-API sub-page structure** (one page per API — `withResource`, `withLayer`, `withLayout`, `withForm`, Partials, dehydration, traces, contract-first routes, cache-first SDK data, and every other API discovered from the real `deno doc` surface), each with what-it-replaces, the idiomatic patterns, and a type-checked example; a **competitive benchmark** against Next.js / Nuxt / SvelteKit / Rails-class tutorial flows recording where our tutorials undersell and where our differentiators have no peer equivalent; cross-links from the phase-1 tutorials; examples verified against published entrypoints; docs checks green; docs-lane file audit clean. +- **Sequencing.** After #1208 phase 1 merges; phase 2 may run in parallel where surfaces don't overlap. Refs #1208, #1201. +- **Byproduct.** #1210 authoring is the discovery source for #1245 and #1249 (framework bugs found by writing the deep-dives). Run trail lives on PR #1215 (`orchestrator/docs-mainpages`, `d1-d4-report.md`), branch `docs/web-layer-deep-dives` baseline `24dbcaaa7`. + +#### #1260 — `mcp: include SDK guidance in the shipped search_docs corpus` +- **Milestone** 0.0.6 · `type:feat, area:docs, area:tooling, area:agentic, status:triage, priority:p2` +- **Contract.** The CLI's default MCP composition embeds only the MCP package README (`packages/mcp/cli.ts`) plus `EMBEDDED_SKILL_FILES["help.md"]` (`packages/cli/src/public/features/agent/mcp/run-agent-mcp.ts`). No SDK/package documentation corpus, so `search_docs` cannot answer cache-invalidation / Fresh-hydration / optimistic-mutation questions even when the prose exists elsewhere. Observed in the maintainer session recorded in **#1253**; the *runtime* export-corpus failure is fixed there — this issue is deliberately scoped to the **prose corpus-coverage gap** so it is not mislabeled as a ranking defect. +- **Acceptance.** Shipped CLI MCP composition includes a bounded, release-matched SDK doc corpus; `search_docs` returns relevant SDK-level results for those three query classes; coverage proven **through the real CLI stdio interface from a scaffolded project**; freshness + release-staging gates analogous to the export corpus; result size and public-only filtering explicit and tested. +- **Refs.** #1253, #1218, #1201, #1197. + +#### #1201 — `mcp: serve the generated export surfaces, not just prose docs` +- **Milestone** 0.0.6 · `type:feat, area:docs, area:tooling, area:agentic, status:plan, priority:p2` +- **Contract.** `docs-corpus-port.ts` models a document as title/description/headings with slugged sections ranked by token match — a **Markdown prose** corpus. The generated per-package export surfaces (`deno doc` output: every export, subpath, signature, JSDoc) are a different corpus type with **no MCP path**. Argument is asymmetric value: the prose has a map (`llms.txt`, headings); the export surface is 36 flat files with no map. Quoting the docs bundle's own note: *"The generated surfaces are excellent once you know what you want and useless for discovering what exists."* +- **Measured evidence** (wave-4 attribution-control run, 452 tool calls, complete product): `docs/deno-doc/` touched by **17** commands, `docs/pages/` 5, `llms.txt`/`llms-full.txt` 1, **MCP calls 0**. The most-used doc surface is the only one with no MCP path. +- **Strategic role.** "The last blocker to a mirror-free workspace" — wave runs ship an 8 MB docs mirror into every agent workspace; while a greppable mirror exists, `bash` stays the path of least resistance and MCP competes with a trusted tool. +- **Four question forms to answer.** (1) which package/subpath exports ``; (2) what does `` export grouped by subpath; (3) signature + JSDoc of ``; (4) what exports look like ``. +- **Acceptance.** Distinct corpus type (not Markdown pretence); all four forms answerable with no docs mirror present; bounded retrieval (symbol + signature, not the whole file); version-pinned like the embedded prose corpus; **a workspace with no `docs/` directory at all** answers "which subpath exports this helper" end to end; verified by re-measuring a real agent run with MCP calls non-zero and `deno-doc` grep counts at zero *because the files are not there*. +- **Dependency.** Explicit follow-up to #1197. + +#### #1102 — `feat(mcp): make capability discovery an intent-aware primary agent workflow` +- **Milestone** 0.0.5 · `type:feat, area:docs, area:tooling, status:triage, priority:p1` +- **Contract.** Current MCP retrieval is a flat lexical pass: tokenize → count in title ×12, headings ×5, body ×1 → sort → return one body-adjacent snippet. No concept/synonym expansion, field-length normalization, section-level ranking, link-graph traversal, task sequence, or code-block extraction. MCP instructions only route agents to docs search for *troubleshooting symptoms*, never as the default "understand this unfamiliar framework before implementing" workflow. Wave-four baseline: 0 docs-MCP calls across three runs. +- **Required shape.** An intent-aware guidance flow (`find_guidance` named as illustrative) returning: ordered recommended pages **and sections**; why each matches; a prerequisite → implementation → verification sequence; relevant code blocks with language + source slug/section; related capability/reference links; confidence/fallback. The #1068/#1079 task router becomes *input* to this flow rather than a separate text artifact. +- **Retrieval requirements.** BM25 + curated concept aliases, a small local embedding model, or another deterministic hybrid — contract over algorithm: concept mismatch must work ("avoid hitting my service every render" → cache-first queries/`staleTime`); section-level ranking; link-based next/prerequisite traversal; independently retrievable code fences/Vento examples; filesystem and embedded corpora behave identically; token-bounded and offline-capable. +- **Evaluation corpus (checked in).** Five named intents with expected top-three destinations: validated route-bound form → `web-layer/form`; keep server data fresh without polling → `web-layer/query` / live-dashboard cache-first chapters; add a capability NetScript does not ship → custom plugin authoring guide; use a Prisma-supported DB NetScript does not wrap → second-database unsupported-driver section; build a real service-backed UI → the #1068 task-router sequence. Measure top-k recall and deterministic bounds. +- **Explicit boundary.** "Do not use a future agent run as an acceptance checkbox" — observational adoption is tracked **only** in #1090. + +#### #1197 — `agentic: the agent-init harness had zero adoption on 0.0.4` +- **Milestone** 0.0.5 · `type:fix, area:tooling, area:agentic, status:plan, priority:p1` +- **Contract.** 0.0.4 shipped #1023 (three-skill split) and #1024 (tooling bundle) to close the discovery gap. The first measured agent run on published 0.0.4 used **none of it**. Measured from the full OpenCode event log of the wave-4 DeepSeek attribution-control run (2h51m, 452 tool calls, complete product): MCP server **0**, `netscript plugin doctor` **0**, `aspire otel logs|spans|traces` **0**, all five installed skills **0**, `help.md` **0**, the 11 `.llm/tools/*` from #1024 **0**, `netscript agent drift record` **0**. Instead: 340 bash (75%), 52 read, 31 write, 23 edit, 1 skill (`playwright-cli`, not a NetScript skill), **35 hand-rolled `curl` probes**, 37 `aspire logs`, 125 grep/rg. It recorded five drift entries including three framework defects having run neither prescribed command; one defect cost 75 minutes. +- **Sixth consecutive zero.** Wave three measured zero MCP calls across all three runs — that is what motivated #1023/#1024; this is the first run on the release containing them and the number is still zero. +- **Structural finding.** The drift gate is real *and unreachable*: generated `AGENTS.md` says `agent drift record`/MCP `record_drift` refuse without a `plugin doctor --resource` receipt from the last 15 minutes, but agents record drift in a **markdown file** because that is what every brief and journal template asks for. "A gate on an unused tool constrains nothing." +- **Acceptance.** Routing to a diagnostic surface **at the moment of failure**, not by prior instruction; MCP usage on a measured run non-zero **or** the MCP server is not installed by default; drift recording either flows through the gated path or the gate is removed as unenforceable; verified by **re-measuring a real agent run** and comparing tool-call counts against this one; a repeatable extraction script lands with the fix. +- **Related.** #1163 (0.0.6) owns the internal twin (a milestone run executing from artifacts alone). + +#### #1090 — `verify(wave-five): does the shipped agent surface actually change agent behaviour?` +- **Milestone** 0.0.5 · `type:test, area:cli, area:agentic, status:triage, priority:p1` +- **Contract.** A pure **verification** issue holding four acceptance criteria that no PR can satisfy because they observe a future agent run — relocated (not ticked, deleted or weakened) from #1072 box 4, #1073 box 3, #1071's falsifiable check, and #1068. To verify in wave five: non-zero MCP diagnostic tool usage (#1072); an agent building a data screen runs `ui:add` or records why not (#1073); the **#1071 falsifiable check** — same brief/version/bundle/budget, varying only the app-scoped conventions file, six agents per arm, blind scoring: does the agent inspect the component barrel and golden example before writing its route, and does the product import app-owned primitives rather than recreating them; an agent asked to build a service-backed UI reaches a Web Layer page before writing a route (#1068). +- **Thesis.** Wave four's lesson: *capability present is not capability activated* (`ui:add` existed, documented, unused; `agent init` diagnostic surface largely unused; `fresh-ui.txt` bundled, linked three times, a declared dependency — never opened). +- **Provenance.** Raised by the PR-D supervisor during 0.0.4 release orchestration 2026-08-03; relocated to 0.0.5 by the orchestrator. +- **Note.** #1090, #1102, #1197, #1201 form one measurement chain: #1102/#1201 build the capability, #1197 demands the re-measurement, #1090 holds the observational boxes. Planning them separately will duplicate the measurement harness three times. + +#### #1343 — `verify(0.0.6): prove installed-consumer scaffold smoke against post-fix canary` +- **Milestone** 0.0.6 · `type:test, area:cli, area:tooling, area:agentic, gate:e2e, status:triage, priority:p1` +- **Contract.** One publication-dependent observation relocated from **#1024** by owner decision 2026-08-07. **#1342** supplies the implementation/lifecycle repair; this issue owns only the published-artifact proof. +- **Acceptance (single box).** From a clean directory **outside** the NetScript framework checkout, install and invoke the exact post-fix canary containing #1342 with **no local-source fallback**, run the full installed-consumer scaffold E2E smoke successfully, and preserve a receipt containing exact package version + provenance, command and working root, per-step verdicts, raw exit code, and cleanup/leak outcome. +- **Boundaries (explicit).** Do not duplicate #1024's five criteria already completed by #1092; do not reopen #1328's scaffold-owned quality implementation; does not publish a canary or change release order. +- **Cross-check.** `#1342` appears merged in local `git log` as `1455231b0 fix(scaffold): make generated quality gates own executable source (#1342)`, so the implementation half has landed and only the published-canary proof remains. + +### 6.4 Type-soundness cluster + +#### #1278 — `Type soundness ratification …` (0.0.6, `type:umbrella`) +- **Thesis.** "Type soundness is a primary NetScript selling point. Today the repo and the docs both concede places where it does not hold." Owner directive 2026-08-04: group them all, fix them all; sub-items are checkboxes so deferral is visible rather than silent. Sharpest exemplar: `docs/site/web-layer/query-bridge.md` requires `const queryClient = createNetScriptQueryClient() as unknown as IslandQueryClient;` because `createNetScriptQueryClient()` returns a real TanStack `QueryClient` typed as the narrower `QueryClientPort` (declares `fetchQuery`, not `prefetchQuery`, not assignable to `dehydrateQueryClient`'s parameter) — errors TS2551 and TS2345. **"A documented cast is a framework bug with a paragraph attached."** +- **Inventory A (docs).** `[x]` `web-layer/query-bridge.md` (the only checked box); `[ ]` `tutorials/chat/06-live-streaming.md` `as any`; `[ ]` `reference/contracts/index.md` `BaseContractProcedure = Readonly<{ ~orpc: any }>` **published as the contract surface**; `[ ]` `reference/triggers/index.md` + `examples_test.ts` `const observedEvents: any[]`; `[ ]` sweep for remaining `as any` / `as unknown as` / `@ts-ignore` in docs snippets. +- **Inventory B (12 production assertion sites, most carrying `quality-allow` rationale).** `packages/cli/src/public/public-api.ts` (**5** — "public facade bridges duplicated internal and exported plugin port identities pending package-boundary unification"; the largest and most public cluster); `packages/fresh/src/application/builders/define-page/builder/route-support.ts` (`RuntimePageConfig['route']` cast — needs a conditional-preserving signature); `packages/fresh/src/application/form/_internal/runtime-types.ts` (2); `plugins/workers/streams/producer.ts` (`as ExecutionMutationHook` — fix by declaration merging or an upstream PR, not a cast); `packages/cli/.../new-plugin-use-case.ts`, `.../public-command-dependencies.ts`, `.../aspire/helpers/_utils.ts`, service-manifest loader `as ServiceManifest`. +- **Inventory C (guard rails).** A lint/check gate failing on new `as any` / `as unknown as` / `@ts-ignore` outside an allowlist that requires a linked issue id (same fail-closed shape as the version-drift and no-op-plugin gates); the gate covers **docs snippets**, not only source; `deno doc --lint` clean on every published package. +- **Inventory D (out of scope).** ~19 `*-contract-soundness_test.ts` files whose `@ts-expect-error`s **are** the soundness assertions — must stay, and the guard rail must not flag them. +- **Acceptance.** No `as any`/`as unknown as` on a public path without a linked open milestoned debt issue; no documented example requires a cast; regression gate live and fail-closed; each deferred sub-item unchecked with an explicit milestone. + +#### #1276 — `epic(quality): ratify and eliminate unsound types …` (Backlog / Triage, `type:umbrella`) +- **Near-duplicate of #1278** (see §4.2). Same 2026-08-04 owner directive, overlapping evidence, different organization. +- **Additional measured numbers #1278 does not state:** **56** `as unknown as` occurrences across `packages/` + `plugins/`; **8** `deno-lint-ignore no-explicit-any` suppressions; **7 ratified `quality:scan` allowances** (6 in `packages/cli`, 1 in `plugins/workers/streams/producer.ts`) with their exact rationale strings. Also cites precise doc line numbers: `query-bridge.md:259` (cast) with the compiler refusal printed at lines 276–277; `reference/contracts/index.md:32`; `reference/triggers/index.md:310`. +- **Tranches (deferrable independently).** T1 public surface first (eliminate `any` from exported types); T2 the documented workarounds (#1245 + #1249, docs update lands with the fix); T3 the 7 ratified allowances ("pending package-boundary unification is a plan, not a resting state"); T4 production `as unknown as`; T5 test-side casts; T6 keep it fixed — extend `quality:scan` so a new `any` in an **exported** type or an unregistered `as unknown as` fails CI (today `quality:scan` covers `packages/cli/src` + `plugins` only). +- **Subsumes.** #1245, #1249, #1255 (`page.layer.delivery` span attribute misreport). +- **Constraints.** Framework source → WSL Codex slices per CLAUDE.md; docs updates follow each fix on the docs lane. "No suppression-as-fix": a new `deno-lint-ignore`/`as unknown as` introduced to green a gate is a review-blocking finding. + +#### #1245 — `fix(fresh/query): island query types reject the package's own documented patterns` +- **Milestone** Backlog / Triage · `type:fix, area:packages, status:triage, priority:p2` +- **Three boundaries, established by running the checker (not reading types):** (1) `initialDataUpdatedAt` is documented as the route for `cachedAt` but is absent from `IslandQueryOptions` → **TS2353**; entry age cannot reach the island as documented. (2) `createNetScriptQueryClient()` returns a real `QueryClient` typed as the narrower `QueryClientPort`, making the package's own dehydration recipe untypeable without a bridge → **TS2551 + TS2345**. (3) `IslandQueryResult` omits `isRefetching`/`isFetching` → **TS2339**. +- **Also.** `getIslandQueryClient()`'s `@throws` JSDoc describes a guard the implementation lacks (docs wording corrected on the deep-dives branch); live-dashboard ch.4's island/dehydration snippets fail `deno check` against declared types — the phase-1 fixture passed **only because it mocked the SDK surface**. +- **Provenance.** Found during #1210 phase-3 authoring, batch 2; evidence in the run trail on PR #1215 (`d1-d4-report.md` "## Batch 2"). +- **Lane.** Framework source → WSL Codex slice per doctrine. `web-layer/query-bridge.md` documents the boundaries honestly and should be simplified when these land. + +#### #1249 — `fix(fresh/form): controlProps() is not element-assignable, and Zod 4 constraint derivation misses numbers and regex` +- **Milestone** Backlog / Triage · `type:fix, area:fresh, status:triage, priority:p2` +- **Defect 1.** `ControlProps` declares `readonly role?: string` (`packages/fresh/src/application/form/_internal/prop-types.ts`) while Preact JSX declares `role?: Signalish`, so the canonical `` fails `deno check` with **TS2322** on `role` only. Reproduced with `jsx: "precompile"` / `jsxImportSource: "preact"` (the compiler options `packages/fresh/deno.json` and the scaffolded app use), preact 10.29.7. Adding `role={undefined}` after the spread compiles; naming props individually compiles. This is *why* `@netscript/fresh-ui` ships `getInputProps`/`getSelectProps`/`getTextareaProps` in `registry/components/ui/control-props.ts`. Package tests only read individual properties off `controlProps()`, never spread it, so nothing catches it. Suggested fix: narrow `ControlProps['role']` to the ARIA role union or `JSX.HTMLAttributes['role']`. +- **Defect 2.** `packages/fresh/src/application/form/schema-adapter/zod-constraints.ts` switches on check kinds `'min'`, `'max'`, `'multipleOf'` and handles `'string_format'` only for `format === 'url'`. Zod 4.4.3 emits different names: string `min_length`/`max_length` (`minimum`/`maximum`), `.regex()` → `string_format` with `format:'regex'` (`pattern`), number `greater_than`/`less_than` (`value` + `inclusive`), `multiple_of` (`value`), array `min_length`/`max_length`. Result: `z.string().regex(...)` gets **no `pattern`**; `z.number().min/max/multipleOf` gets **no `min`/`max`/`step`**. Strings and arrays work today only accidentally. Impact is presentational (server validation still enforces), but `formProps` sets `noValidate: true`, so a consumer gets neither native enforcement nor the attributes. Suggested fix: extend `readCheckKind`'s switch to the Zod 4 names with `inclusive` handling, read `pattern` off `_zod.def`, plus a regression test covering all five cases. +- **Provenance.** Found writing the `withForm` deep-dive (#1210 phase 3), proven with controls against `docs/web-layer-deep-dives` baseline `24dbcaaa7`. Documented as honest boundaries in `docs/site/web-layer/form.md` on that branch; prose should be simplified once fixed. + +### 6.5 Plugin architecture / frontend-contribution cluster + +#### #922 — `Epic: Frontend contribution layer — plugins that ship UI` +- **Milestone** 0.0.7 · `type:umbrella, area:plugins, area:fresh, epic:frontend-contrib, status:plan, priority:p1` · **24 open children (#923–#946)** +- **Contract.** A plugin becomes a full-stack unit: the same package contributing services, workers, schemas and Aspire wiring gains a `frontend/` directory contributing **pages, islands, zone components, nav entries and theme CSS** to any NetScript Fresh host app, discovered like every other axis (manifest pointer + generated type-checked registry) and mounted through upstream Fresh 2.3 primitives. +- **Design record.** RFC **#890** (draft PR carrying `rfc.md` + `.llm/runs/plan-frontend-contrib--seed/`, rev 3, twice reviewed: Sol·high adversarial 20/20 integrated, Kimi K3 docs-forecast 17/17 integrated). +- **Five pillars.** (1) Contracts — `@netscript/plugin-frontend-core/contracts/v1`: envelope + `(family, major)` versioning, `app` family (route/island/zone/nav/theme), identity quartet, `HostSurfaceDescriptor`, request/client context split, budgets. (2) Discovery — pointer axis in `@netscript/plugin`; transactional generated replace-set in `.netscript/generated/` with `frontend.check.ts` as the install gate. (3) Host runtime — `@netscript/fresh/plugins`: post-`fsRoutes` composition mounting, literal route loaders + normalizer, island specifier registration, `PluginZone`, nav feed, deny-by-default procedure gateway. (4) DX/lifecycle — `plugin new --with frontend`, `netscript plugin dev`, doctor taxonomy, quarantine states, `AppTarget` starters, `defineFrontendTestSuite` + budgets. (5) Consumers — first-party dogfood panels, auth v1, ai durable chat; dashboard (#400) and deploy epics consume in their own runs. +- **Sequencing law.** Wave-0 proofs S1–S5 (#923–#927) land before any public contract freezes — those mechanisms were the adversarial review's blocker findings. "First pull: the p0s (#923–#931)." +- **Waves.** Wave 0 proofs #923–#927 (p0, beta.13); Wave 1 contracts+spine #928–#931 (p0) + #932, #933 (p1); Wave 1b gateway #934 (p1); Wave 2 DX/lifecycle #935–#938, #940, #944; Wave 3 consumers #939, #941, #942, #943 (beta.15); Completion #945, #946 (beta.17). +- **Acceptance gates.** All Wave-0 proofs recorded with pass/fail evidence before contract-freeze slices merge; extended `scaffold.runtime` (install → render → hydrate → remove) green with a frontend-contributing plugin; every sub-issue closed or explicitly re-homed with a pointer. +- **Unblocks / supersession.** Dev dashboard panels (#400), auth UI, ai surfaces, deploy consoles. "Refs #427, #432 — both KEEP-and-re-baseline per the RFC's supersession map; no issues closed by this epic's filing." **Milestone drift (measured):** #922 says beta.13/beta.15/beta.17, but its children sit in **0.0.7** (#923–#941, 19 issues), **0.0.9** (#944), **0.0.11** (#942, #943), **0.0.13** (#945, #946). The epic itself is 0.0.7. Wave labels in the body and actual milestones no longer agree, and #944 (a Wave-2 item) is milestoned *later* than Wave-3 siblings would suggest. + +#### #928 — `[frontend-contrib S6] @netscript/plugin-frontend-core contracts/v1` +- **Milestone** 0.0.7 · `type:feat, area:plugins, epic:frontend-contrib, status:plan, priority:p0` · Part of #922 +- **Scope.** New **Archetype-1** package: `FrontendManifestEnvelope` + `(family, major)` versioning, app-family kinds (route/island/zone/nav/theme), identity quartet, `HostSurfaceDescriptor`, `PluginRequestContext`/`PluginClientContext` split, `FrontendRequires`, `FrontendBudgets`, `defineFrontend` (authoring form: contract default, string `MessageRef` shorthand, singular theme). Design source RFC #890 §5 + `.llm/runs/plan-frontend-contrib--seed/design/canonical/` rev 3. +- **Acceptance (gates only).** `deno doc --lint` clean + JSR publish dry-run green; envelope negotiation tests (old-host/new-plugin × new-host/old-plugin); `quality:scan` + `arch:check` green. + +#### #934 — `[frontend-contrib S12] Generated deny-by-default procedure gateway` +- **Milestone** 0.0.7 · `type:feat, area:fresh, epic:frontend-contrib, status:plan, priority:p1` · Part of #922 · **its own reviewed wave (1b)** +- **Scope.** Generated per-procedure route table from `requires.procedures` × contract metadata at `/api/plugins//`; server-side auth via principal port; **no blind credential forwarding**; CSRF/origin checks, limits, timeouts, abort, manual redirects, header allowlist, audit line; streaming per metadata (AI durable chat stays on its specialized adapter). +- **Acceptance.** P5 threat-model checklist item-by-item negative tests; a non-granted procedure returns 404/deny (no wildcard reachability test). + +#### #942 — `[frontend-contrib S20] Auth v1 frontend (account + session widget + signin starter)` +- **Milestone** **0.0.11** · `type:feat, area:auth, epic:frontend-contrib, status:plan, priority:p1` · Part of #922 (Wave 3, "beta.15") +- **Scope.** `plugins/auth` frontend over the **real five procedures**: live `/auth/account` route + `app.topbar.end` session widget + `SessionMenu` island; scaffolded signin/callback starters; **org console explicitly excluded** (waits on the auth-org capability, #945). +- **Acceptance.** Works against all three adapters (better-auth / workos / kv-oauth) with capability degradation; `defineFrontendTestSuite` green. + +#### #946 — `[frontend-contrib S24] Convention generator (generate frontend)` +- **Milestone** **0.0.13** · `type:feat, area:cli, epic:frontend-contrib, status:plan, priority:p3` · Part of #922 (Completion wave, "beta.17") +- **Scope.** Phase-2 sugar: derive manifest contribution lists from the `frontend/` file tree; the explicit manifest **stays the contract**; export-map maintenance already owned by `plugin dev`. +- **Acceptance.** Generated lists **byte-match** a hand-written manifest for the dogfood plugin. + +#### #1093 — `plugin core: discovery hardcodes official plugins' factory functions — third-party plugins cannot participate` +- **Milestone** 0.0.6 · `type:fix, area:plugins, status:triage, priority:p2` +- **Contract.** `packages/plugin/src/sdk/discovery/ast-extractor.ts:6-7` holds a table mapping **official plugins' factory functions to axes** (`{ callee: 'defineSaga', axis: 'sagas' }`, `{ callee: 'defineWebhook', axis: 'triggers' }`). A third-party plugin shipping `defineChannelSync` gets no AST discovery and there is no seam to register one — the core must be edited to add a plugin, which is exactly what the contribution model exists to avoid. Fails silently: the author concludes their plugin is wrong. +- **Why it matters beyond its size.** "Write your own plugin when the capability is missing" is load-bearing product story; `rickylabs/eis-chat` — a real product in daily production use — ships its own `plugins/channel-sync` and is the target agent demo runs are held against. +- **Scope, deliberately narrow.** In scope: the callee→axis table. Judgement call: `packages/config/src/paths/mod.ts:28-32,103-105` (`WORKERS`/`SAGAS`/`TRIGGERS` typed path constants) — decide and record whether it is scaffold-layout convention or plugin knowledge. **Explicitly out of scope:** JSDoc/usage examples, first-party plugins naming themselves, test fixtures. +- **Proposal.** Move the callee→axis declaration into the plugin that owns it (alongside `scaffold.plugin.json`/`officialSource`); core reads contributions. Official plugins then declare their factories exactly as a third-party plugin would — "the only way we find out whether the seam actually works." +- **Acceptance.** No core package requires editing to add a plugin's discovery; `defineSaga`/`defineWebhook` axes declared by `plugins/sagas`/`plugins/triggers`; a **third-party plugin fixture** discovered end to end, proven by a test that fails on today's `main`; the `packages/config` question decided and recorded; a doctrine check (`arch:check` or guard test) fails if a core package gains a branch on a specific plugin name. +- **Provenance.** Wave-four review 2026-08-03, raised while reviewing #1076 — the issue explicitly records that **#1076 itself is clean** and the hardcoding predates it. `packages/plugin/src/cli/application/registry-emitter.ts:13` already states the correct rule, so this is a gap against existing doctrine. + +### 6.6 Enterprise auth cluster (children of #871) + +#### #884 — `[enterprise-auth S13] Define organization-aware identity and authorization policy contracts` +- **Milestone** 0.0.12 · `type:feat, area:auth, area:service, epic:enterprise-auth, status:triage, priority:p1` · Part of #871 +- **Contract.** Shared vendor-neutral contracts the vendor integrations need: canonical organization, membership, connection, role/group, assurance and policy-decision types, plus an authorization request over subject × organization × resource × action × context. The simple scope/role route guard is preserved **as an adapter, not the ceiling**. +- **Acceptance gates (9).** Identity contract distinguishes user / linked account / organization / membership / backend / provider / connection identifiers; authorization input includes tenant, subject, resource, action-relation, environment context, assurance/freshness; decision includes allow-deny, safe reason, provider, policy/model version, audit correlation; unknown tenants/memberships/claims/policy-providers/timeouts **fail closed**; claim normalization covers Entra app roles/groups, WorkOS roles/permissions, Better Auth org roles without conflating sources; Entra B2B guests use tenant-qualified immutable `tid`+`oid` and never key authorization by `#EXT#` UPN text; subject vocabulary distinguishes humans, service principals, autonomous machines, CLIs and user-delegated agents with an explicit actor/delegation chain; existing path scope/role guards adapt without breaking simple apps; tenant isolation has model/property tests across storage, cache, routing, authorization. +- **Dependencies.** None — **foundational for EA-04 … EA-07, EA-11, EA-14, EA-15.** Delivery shape: new vendor-neutral core/service contracts and adapters. + +#### #885 — `[enterprise-auth S14] Build an auth conformance, mocking, and scaffold test kit` +- **Milestone** 0.0.12 · `type:feat, area:cli, area:auth, area:tooling, epic:enterprise-auth, status:triage, priority:p1` · Part of #871 +- **Contract.** Expand the two current object builders into a security-focused test kit used by all auth backends and generated projects: fake OIDC/JWKS, signed tokens, WorkOS/Better Auth sessions and webhooks, SCIM events, Conditional Access claims challenges, multi-backend identities, clock/replay controls, negative cases. +- **Acceptance gates (7).** Shared suite per backend for capability truth / authentication / expiry / refresh / revoke / malformed input / unsupported operations; fake OIDC/JWKS supports rotation, bad issuer-audience-signature, nonce/state/replay, clock skew, refresh reuse, claims challenges; WorkOS session+webhook, Better Auth handler+plugin, Directory Sync/SCIM fixtures deterministic with **no live credentials**; multi-backend routing, linking, logout, provider outage and tenant-isolation scenarios reusable; generated-project smoke profiles cover direct Entra, WorkOS enterprise, Better Auth application auth and selected coexistence; docs/examples use the same fixtures and distinguish mocked conformance from live-provider acceptance; CI exposes a compact failure matrix by backend × capability × security invariant. +- **Dependencies.** Initial contract follows EA-00 and EA-12; expands alongside every adapter. Delivery shape: new shared testing package/fixtures + scaffold E2E profiles. + +### 6.7 Aspire / infrastructure + +#### #979 — `fix(aspire): plugin API resources still pin host ports 8091–8094` +- **Milestone** **none** · `type:fix, area:plugins, area:aspire, status:triage, priority:p2` +- **Contract.** Split out of #952 / PR #978, which made host-port pinning opt-in for the example service and the app but **deliberately left plugin API resources pinned**. Two workspaces that both install plugins collide on `:8091–:8094`, and `aspire start --isolated` cannot randomize them away. +- **Why not fixed in #978 (the real dependency chain).** The `scaffold.runtime` E2E suite **live-probes those exact ports**: `packages/cli/e2e/src/application/gates/scaffold/runtime-gates.ts` L140–L235 probes `http://127.0.0.1:8091/health/live`, `:8092/api/v1/sagas/sagas`, `:8093/health`, `:8094/health/live`, and passes `--allow-net=127.0.0.1:8091,127.0.0.1:8092` into the generated project. **~20 tutorial and explanation passages under `docs/site/**` also `curl` those ports.** +- **Prerequisites.** (1) `scaffold.runtime` gates resolve each plugin resource's endpoint from the Aspire resource service or dashboard API instead of hardcoding `127.0.0.1:`, including the `--allow-net` grant handed to the generated project. (2) Docs move from "`curl :8091`" to "read the URL the dashboard shows". +- **Then.** Drop `Port` from the plugin entries the scaffolder writes; the generator seam already exists — `packages/cli/src/kernel/templates/aspire/helpers/register/render-http-endpoint.ts` emits `{ env: 'PORT' }` when an entry carries neither `HostPort` nor `Port`, and `generate-register-plugins.ts` already calls it. +- **Sibling.** #980 (`netscript service add` still pins an Aspire host port, p3, also unmilestoned) is the same defect on a different command. + +#### #1280 — `aspire: backing services report no real health check — blocked on TypeScript AppHost custom health-check support` +- **Milestone** 0.0.6 · `type:fix, area:aspire, area:database, status:blocked, priority:p2` +- **Contract.** Split from #1251 after verification. #1251's other rows (SQLite in the graph, `deno-kv` modelled with a resolved value and URL, no unresolved-parameter banner, a graph test per provisioned backing service) stay on #1251 for 0.0.5. **This row is proven undeliverable:** (1) Aspire's own health-checks documentation states verbatim that *"TypeScript AppHost support for registering custom health checks with `builder.Services.AddHealthChecks()` is not yet available"* — the C# `AddCheck` + `WithHealthCheck` path has no TypeScript equivalent. (2) The HTTP path cannot substitute: `withHttpHealthCheck('/health')` exists in TypeScript but requires the resource to serve HTTP, and **Deno KV Connect 0.11.0 exposes only authenticated POST `/`, `/snapshot_read`, `/atomic_write`, `/watch`** — no health endpoint. A generated `/health` probe would report green while checking nothing, the exact `healthStatus`-without-`healthReports` failure mode #1251 set out to kill. A prototype was built and deliberately discarded (PR #1266 research artifacts). +- **Acceptance (unblocks when an upstream condition changes).** Every provisioned backing service reports at least one **real** health check so `healthStatus` is never green off an empty report set; no generated probe passes against an endpoint that does not implement it. +- **Watch conditions.** Aspire TypeScript AppHost gaining custom health-check registration, **or** Deno KV Connect exposing a health endpoint. + +--- + +## 7. Dedup checklist for roadmap authors + +Before filing anything, check against these clusters — each already has an owner issue: + +| Proposed topic | Existing issue(s) — do not re-file | +|---|---| +| Scaffold default app is not idiomatic / app name hardcoded | **#1333** | +| Repo-wide generated-surface conformance inventory | **#1335** | +| Generated background runtime missing a KV adapter | **#1325** (triggers); #1184 CLOSED (sagas) | +| `db migrate` false-green in headless | **#1327** | +| Docs should start from generated DB schemas | **#1332** | +| Durable stream producer never reconnects | **#1326** | +| SSE event envelope / telemetry propagation | **#1329** | +| Tutorials don't show the page builder | **#1208** (phase 1) — phase 2 sweep **is not yet an issue** | +| Per-API page-builder deep dives + competitor benchmark | **#1210** | +| MCP corpus lacks SDK prose | **#1260** | +| MCP has no export-surface corpus | **#1201** | +| MCP retrieval is lexical, not intent-aware | **#1102** | +| Agents don't use the shipped agent surface | **#1197** (fix) + **#1090** (measurement) | +| Published-canary installed-consumer smoke | **#1343** | +| Type soundness / `as unknown as` / `any` in public types | **#1276** *and* **#1278** (duplicates — pick one) | +| Island query type gaps (`initialDataUpdatedAt`, `QueryClientPort`, `isRefetching`) | **#1245** | +| `controlProps` role assignability + Zod 4 constraint derivation | **#1249** | +| Plugins shipping UI (routes/islands/zones/nav/theme) | **#922** + #923–#946 | +| Plugin discovery hardcodes official factories | **#1093** | +| Org-aware identity/authorization contracts | **#884** | +| Auth conformance/mocking test kit | **#885** | +| Aspire plugin resources pin host ports | **#979** (+ **#980** for `service add`) | +| Backing services have no real health check | **#1280** (blocked upstream) | +| Migration chapter docs | **#1275** *and* **#1279** (duplicates — pick one) | +| Docs-site layout/UI polish | **#1277** | +| Single Zod instance | **#1320** (blocked on `@ag-ui/core` ^3 + kvdex) | + +--- + +## 8. Method and limits + +- All issue data from `gh issue list --repo rickylabs/netscript --state open --limit 500 --json number,title,labels,milestone,createdAt,updatedAt,body,assignees,url` on 2026-08-08; closed-set cross-check from the same command with `--state closed --limit 1200` (385 rows). +- Milestone counters from `gh api repos/rickylabs/netscript/milestones --paginate`; they include PRs, which is why they exceed the issue-only measurement. +- Umbrella child resolution is *mechanical*: `epic:*` label membership plus regex over `- [ ] #N` / `- [x] #N` in the umbrella body. Umbrellas that list children in prose (#232, #313, #1275, #1276, #1277, #1278, #1279) have **no** machine-readable child set and are flagged as such — their true scope is unknown to this snapshot and must be read manually before planning. +- GitHub's native sub-issue graph was **not** queried (the MCP/`gh` surface used here exposes body text and labels only). If sub-issue links exist beyond the checkbox lists, this snapshot under-reports them. +- Body summaries in §6 are condensations of the issue text as written; where a body states a measurement (tool-call counts, file:line, TS error codes), the measurement is reproduced verbatim and is the issue author's claim, not independently re-verified in this pass. diff --git a/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/github-conventions.md b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/github-conventions.md new file mode 100644 index 0000000000..ed9540ac06 --- /dev/null +++ b/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/research/github-conventions.md @@ -0,0 +1,516 @@ +# GitHub board conventions — rickylabs/netscript (ground truth, 2026-08-08) + +Scope: milestones, labels, issue forms, house issue body shape, RFC process. All GitHub reads via +`gh api` / `gh issue view` (read-only) on 2026-08-08; all file citations are the local worktree at +`/home/codex/repos/netscript-fable5-remediation-plan` (branch `plan/fable5-remediation-roadmap`, +HEAD `fac9e3390`). + +Canonical prose authority: `.agents/skills/netscript-pr/SKILL.md` (384 lines) — it self-declares as +"the single canonical reference for NetScript's GitHub process" (line 374). `.github/labels.yml` is +the machine-readable mirror; `CONTRIBUTING.md` is the human mirror. + +--- + +## 1. Milestones (live, `gh api repos/rickylabs/netscript/milestones`) + +**No milestone in the repo has a due date** — `due_on` is `null` for all 25 (open and closed). +Milestone *numbers* are API ids, not the titles; the board addresses milestones by **title**. + +### Open milestones (13) + +| # | Title | Open | Closed | Description (verbatim, trimmed) | +|---|---|---|---|---| +| 3 | `Backlog / Triage` | 58 | 15 | "Holds only upstream-blocked or undecided work plus epic/umbrella issues; children carry the beta.12–beta.18 train milestones." | +| 14 | `0.0.2` | 5 | 53 | "Stabilisation release. Defects found by the four-agent build experiment (#951-#972) plus release and tooling fixes. No feature work — the Process Manager epic moved to beta.13." | +| 15 | `0.0.3` | 0 | 38 | "Fixes-only stabilisation release. Defects found by the round-two agent build experiment plus release and tooling fixes. No feature work — Process Manager core, the frontend contribution layer and deploy plugin W1-W3 moved to 0.0.4." | +| 22 | `0.0.4` | 0 | 63 | "Stability, documentation and agent-harness release. Everything needed for reliable agent demo runs: no known-broken primitives, accurate docs, and an agent harness that earns its place. Cut before wave-five agent runs." | +| 23 | `0.0.5` | 22 | 160 | "Agent-surface release. Capability discovery and tooling that make the framework legible to autonomous agents — building on 0.0.4 stability. Includes a tailored OpenAPI-to-MCP projection so agents can query their own scaffolded API surface instead of hand-rolling requests." | +| 25 | `0.0.6` | 23 | 0 | "Post-0.0.5 follow-up release: deferred agent-surface work and non-frontend items re-triaged from the former mixed 0.0.6 milestone." | +| 24 | `0.0.7` | 20 | 6 | "Frontend Contribution Layer — plugins that ship UI. RFC #890, epic #922. Waves 0-2: disposable proofs, contracts + spine, DX + lifecycle. Wave 3 consumers (auth v1, AI chat, panels, auth-org, convention generator) follow in later milestones." | +| 16 | `0.0.8` | 50 | 4 | "Enterprise auth wave-1 (Entra OIDC, multi-backend routing)" | +| 17 | `0.0.9` | 15 | 0 | "Deploy containers W4 + auth WorkOS broker wave (SSO/SCIM/Audit) + frontend-contrib polish" | +| 18 | `0.0.10` | 2 | 0 | "Deploy clouds W5 (CF/Vercel/AWS + thin adapters) + auth machine/agent/Better Auth track" | +| 19 | `0.0.11` | 10 | 0 | "Desktop graph (#830) + Aspire packaging/Windows tier + WorkOS RBAC/FGA" | +| 20 | `0.0.12` | 11 | 0 | "Dev dashboard (thin, contribution-based) + auth/deploy tail" | +| 21 | `0.0.13` | 44 | 0 | "Cascaded from beta.18 when beta.12 became the stabilisation release." | + +### Closed milestones (12) — the pre-rename `0.0.1-beta.N` train + +`0.0.1-beta.1` (#1, 38 closed), `0.0.1-stable` (#2, 6), `0.0.1-beta.2` (#4, 10), `0.0.1-beta.3` +(#5, 24), `0.0.1-beta.4` (#6, 6), `0.0.1-beta.5` (#7, 21), `0.0.1-beta.6` (#8, 40), `0.0.1-beta.7` +(#9, 60), `0.0.1-beta.8` (#10, 48), `0.0.1-beta.9` (#11, 50), `0.0.1-beta.10` (#12, 53), +`0.0.1-beta.11` (#13, 35). All have 0 open issues. `0.0.1-beta.1`/`0.0.1-stable` carry long LOCKED +acceptance-bar descriptions (cut criteria enumerated in the milestone description itself) — evidence +that milestone descriptions are used as **cut bars**, not just labels. + +### Rules from the skill (`netscript-pr/SKILL.md` §Milestones, lines 353-359) + +- "Assign **every** open issue and PR to the explicit release that owns it: `0.0.2` through `0.0.9`, + or `Backlog / Triage` when it is accepted but unscheduled." +- "The `wave:*` labels are scheduling bands and may span several releases; they do **not** determine + a milestone." +- "Epics and umbrellas use the cut in which their coordinated scope is expected to complete." + +**Conflict (live > skill):** the skill's stated milestone range is `0.0.2`…`0.0.9` (also stated in +`AGENTS.md` obligation 2), but the live board has `0.0.10`–`0.0.13` as well. Live wins: a new issue +may legitimately target `0.0.10`+. + +**Drift note (fact):** `Backlog / Triage`'s description claims children carry "beta.12–beta.18 train +milestones" — those milestone titles no longer exist (renamed to `0.0.6`–`0.0.13`). Stale text. + +**Practical target for a Fable-5 remediation plan (hypothesis):** near-term defect/docs work lands +`0.0.5` (still open, 22 open) or `0.0.6` (23 open, 0 closed — the live "next" bucket); epics/umbrellas +without a scheduled cut land `Backlog / Triage` (e.g. #1335, an umbrella, sits there). + +--- + +## 2. Labels + +### 2.1 File: `.github/labels.yml` (249 lines) — declared taxonomy + +Header rules (lines 8-14), verbatim intent: + +- Exactly **ONE** `status:` label on an open issue/PR at a time (it is the board column). +- On completed closure, replace the phase label with `status:shipped`; for not-planned/wontfix + closure, remove the `status:` label entirely. +- `type:`/`area:`/`priority:` are additive. +- "Add new labels here first, then create them; do **NOT** delete existing labels (that strips them + off live issues) — deprecate in this file and propose removal to the maintainer." + +Declared sets (name → color → description): + +- **type:** (`c5def5` unless noted) `type:feat`, `type:fix`, `type:docs`, `type:chore`, + `type:refactor`, `type:perf`, `type:test`, `type:umbrella` (`5319e7`, "Coordinating PR for a + multi-slice effort"), `type:sub-pr` (`0e8a16`). +- **status:** (`fbca04` unless noted) `triage`, `research`, `plan`, `plan-eval`, `impl`, `impl-eval`, + `augment-review`, `ci-fail`, `ready-merge` (`0e8a16`), `shipped` (`5319e7`, terminal), + `close-gate-override` (`b60205`, "Audited exception to the closing-keyword acceptance gate"). +- **priority:** `p0` (`b60205`, "Critical / release blocker"), `p1` (`d93f0b`), `p2` (`fbca04`), + `p3` (`0e8a16`). +- **area:** (`bfdadc` unless noted) `cli`, `fresh`, `fresh-ui`, `plugins` (`5319e7`), `auth`, + `aspire`, `deploy`, `database`, `kv`, `sdk`, `service`, `config`, `telemetry`, `ai-core` + (`1d76db`), `plugin-ai` (`1d76db`), `tooling`, `deps`, `docs`. +- **ci:** `ci:full`, `ci:skip-e2e`, `ci:skip-scaffold`, `docs-eval:skip`, plus `gate:e2e`, `gate:jsr` + (all `d4c5f9`). Precedence comment (lines 151-154): "`ci:full` > `ci:skip-*` > docs-only + auto-detection. Skip labels NEVER affect the cheap required trio (quality / check-test / + deps-report) in ci.yml, nor the release gates (publish / e2e-cli-prod*)." Consumed by the + `classify` job in `.github/workflows/e2e-cli.yml`. +- **epic:** (`5319e7`) `ai-stack`, `telemetry-revamp`, `dev-dashboard`, `docs-cut`, `deployment`, + `roadmap-reforecast`, `harness-v3`, `process-manager`, `agentic-combo`, `frontend-contrib` + (`1d76db`), `openapi-mcp`. +- **wave:** (`c2e0c6`) `wave:v1`, `wave:v1-min`, `wave:defer`. +- **flags:** `rfc` (`5319e7`), `breaking` (`b60205`), `good first issue`, `help wanted`. +- **GitHub-standard retained:** `bug`, `enhancement`, `documentation`, `duplicate`, `wontfix`. + +### 2.2 Live label set: 123 labels total + +**In `labels.yml` but NOT live (2)** — declared, never created: + +- `docs-eval:skip` +- `status:close-gate-override` + +Consequence (fact): the audited close-gate escape hatch documented in `netscript-pr/SKILL.md` +lines 222-225 **cannot be applied today** without first creating the label. + +**Live but NOT in `labels.yml` (33, excluding canaries)** — the file is materially out of date: + +- Extra `area:` labels: `area:agentic`, `area:ai`, `area:contracts`, `area:db`, `area:packages`, + `area:queue`, `area:release`, `area:runtime-config`, `area:sagas`, `area:services`, + `area:streams`, `area:triggers`, `area:workers`. Several are actively used on recent issues: + `area:agentic` (#1330, #1331, #1343), `area:contracts` (#1332, #1263), `area:release`. +- Extra `epic:` labels: `epic:deploy-plugin`, `epic:desktop-frontend`, `epic:enterprise-auth`, + `epic:road-to-stable`, `epic:unified-runtime`. +- Extra `status:` labels (duplicate/legacy columns, a single-status hazard): `status:blocked` + (in live use — #1320, #1280), `status:in-progress`, `status:in-review`, `status:review`. +- Extra `type:` labels: `type:feature` (duplicate of `type:feat`; live-used on #1306), + `type:release`. +- Extra gates/flags: `gate:ci`, `e2e-cli-gate` ("Run the toolchain-heavy e2e-cli workflow + (scaffold-static + scaffold-runtime) on this PR."), `priority:high` (duplicate of `priority:p1`), + `codex`, `dx`, `prime-time`, `sagas`, `service`, `question`, `invalid`. +- **Also live: `area:db` vs `area:database`, and `area:sagas`/`area:streams`/`area:triggers`/ + `area:workers` vs the single `area:plugins`** — pick the one used on recent comparable issues. + +**Duplicate-pair guidance (derived, from recent-issue usage):** prefer `type:feat` (not +`type:feature`), `priority:p1` (not `priority:high`), `area:database` (not `area:db`), +`area:plugins` (used on #1325/#1326/#1329 for streams/triggers work). + +**`canary:*` labels (20 live)** — machine-generated, do NOT hand-apply. Created by +`.llm/tools/release/canary-label.ts:361` with description +`` `Published NetScript prerelease ${version}` ``. Range live: `canary:0.0.4-canary.1..4`, +`canary:0.0.5-canary.1..16`. Applied to shipped issues/PRs at publish (#1328 → `canary:0.0.5-canary.15`; +#1331 → `canary:0.0.5-canary.14`). + +### 2.3 Minimum label contract for a new issue + +From `netscript-pr/SKILL.md` line 320: "**Every open issue and PR carries at least one `type:` and +one `area:` label, and every open issue carries a milestone.** New issues land with `status:triage` +(the issue forms apply it automatically) until triaged." + +Observed on all 11 issues sampled in 1325-1335: every one carries `type:*` (or `type:umbrella`) + +≥1 `area:*` + `priority:p0..p3` + exactly one `status:*` + a milestone. `priority:` is de-facto +mandatory in practice even though the skill only says type/area/milestone. + +Stage-label lifecycle (`SKILL.md` lines 328-344): +`status:research → status:plan → status:plan-eval → status:impl → status:impl-eval → +status:augment-review (optional) → status:ready-merge`, then atomically swap to `status:shipped` on a +completed close. A merged PR still sitting at `status:plan` is called out as a real observed failure +(~50% non-compliance audit). + +--- + +## 3. Issue forms — `.github/ISSUE_TEMPLATE/` + +`config.yml`: `blank_issues_enabled: false` (**raw blank issues are disabled in the UI**; API +creation still works). Contact links: Discussions → Q&A, Ideas, RFCs, and private security advisory. + +| File | `name` | `title` prefix | auto `labels` | Required fields | +|---|---|---|---|---| +| `bug_report.yml` | Bug report | `bug: ` | `bug`, `status:triage` | Summary (textarea), Area (dropdown), Reproduction steps, Expected behavior, Actual behavior, Environment (`render: shell`), 2 required checkboxes | +| `feature_request.yml` | Feature request | `feat: ` | `type:feat`, `status:triage` | Problem / motivation, Proposed solution, Area (dropdown); Alternatives optional; 2 required scope checkboxes | +| `documentation.yml` | Documentation issue | `docs: ` | `documentation`, `status:triage` | Location, Kind of issue (dropdown), Details | +| `rfc_proposal.yml` | RFC proposal (tracking issue) | `rfc: ` | `rfc`, `status:triage` | RFC title (input), Summary, Motivation; "Why this needs an RFC" checkbox group; RFC PR link optional; 1 required checklist box | + +Shared **Area dropdown** option list (identical in bug + feature forms, "maps to the `area:*` +labels"): `cli, fresh, fresh-ui, plugins, auth, aspire, database, kv, sdk, service, config, +telemetry, ai-core / plugin-ai, tooling, docs, unsure`. Note it does **not** include the live-only +areas (`agentic`, `contracts`, `release`, …) — another form/live drift. + +`feature_request.yml` embeds the epic standard in its markdown preamble (verbatim): "**Multi-slice / +epic work:** file a program epic as `Epic: ` (`type:umbrella` + `epic:`) with a sub-issue +checklist, and file each slice as its own `[ S] ` issue linked by +`Part of #`." + +`rfc_proposal.yml` "Why this needs an RFC" options: public API/export surface change; breaking or +release/publish surface; plugin contracts / service seam / architecture doctrine; cross-cutting +across multiple packages/plugins. + +**Field → body mapping:** issue-form field *labels* become `##