From c7a6f3d32009cfe9344ab8ed613f1c54c205b07f Mon Sep 17 00:00:00 2001 From: Rickylabs Date: Sat, 15 Aug 2026 21:56:35 +0200 Subject: [PATCH 1/8] docs(harness): lock typed error plan and scope blockers --- .../codex-thread-ids.md | 16 + .../context-pack.md | 67 +++++ .../drift.md | 41 +++ .../plan.md | 278 ++++++++++++++++++ .../research.md | 215 ++++++++++++++ .../supervisor.md | 28 ++ .../worklog.md | 83 ++++++ 7 files changed, 728 insertions(+) create mode 100644 .llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/codex-thread-ids.md create mode 100644 .llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md create mode 100644 .llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md create mode 100644 .llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan.md create mode 100644 .llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/research.md create mode 100644 .llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/supervisor.md create mode 100644 .llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/codex-thread-ids.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/codex-thread-ids.md new file mode 100644 index 000000000..b1ba8722a --- /dev/null +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/codex-thread-ids.md @@ -0,0 +1,16 @@ +# typed-error — Codex implementation thread +- **Thread / session id:** `01a006f3-ae2d-7941-bd17-2ac71dd3d0f0` +- **Rollout:** `/home/codex/.codex/sessions/2026/08/15/rollout-2026-08-15T21-43-48-01a006f3-ae2d-7941-bd17-2ac71dd3d0f0.jsonl` +- **Worktree:** `/home/codex/repos/netscript-007-leaf-typed-error` +- **Branch:** `fix/sdk-typed-error-channel` @ `0ef48c2ec` (NO upstream by design). +- **Push rule:** explicit refspec only — `git push origin HEAD:refs/heads/fix/sdk-typed-error-channel`. +- **Requested route:** provider=openai · model=gpt-5.6-sol · effort=medium +- **Observed route:** provider=openai · model=gpt-5.6-sol · effort=medium +- **Route verdict:** matched +- **Runtime:** approval=never · sandbox=dangerFullAccess +- **Brief (staged):** `/home/codex/typed-error-brief.md` +## Steering (same thread — never a second send-message-v2 at this worktree) +```bash +codex exec resume 01a006f3-ae2d-7941-bd17-2ac71dd3d0f0 -- "" +``` +_Written by `.llm/tools/agentic/codex/launch-codex-slice.ts`._ \ No newline at end of file diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md new file mode 100644 index 000000000..66aaa8c34 --- /dev/null +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md @@ -0,0 +1,67 @@ +# Context Pack: sdk-typed-error-channel (#1350) + +## Run Metadata + +| Field | Value | +| -------------- | --------------------------------------------------------- | +| Run ID | `fix-sdk-typed-error-channel--0.0.7-wave1` | +| Branch | `fix/sdk-typed-error-channel` | +| Current phase | `plan` — awaiting scope/ownership ruling before PLAN-EVAL | +| Archetype | `1 — Small Contract` slice | +| Scope overlays | `docs` | + +## Current state + +Research and a concrete error contract are complete. No product or docs implementation exists. The +base RED is TS2339 because `safe()` erases `TError` and `isDefinedError()` narrows the result to +`never`. Exact end-to-end repair cannot fit the declared five-file surface because +`packages/sdk/src/ports/service-client.ts` is the loss point. Procedure metadata additionally has a +live owner conflict with #1466 and requires the curated contracts barrel if assigned here. + +## Completed + +- Required skill/harness/doctrine/RFC/issue reading. +- `deno doc` public-surface inspection before source inspection. +- Focused source map and executed whole-repo consumer search. +- Exact RED and JSR/publish baseline inspection. +- Research, plan, design, risk/gate set, docs dispositions, and rescope report. + +## Next steps + +1. Topic orchestrator rules on `packages/sdk/src/ports/service-client.ts` scope. +2. Topic orchestrator rules whether #1350 supersedes #1466 for procedure metadata and, if so, + authorizes `packages/contracts/src/public/mod.ts` plus any required SDK metadata type surface. +3. Update the plan with that ruling if needed; separate session performs PLAN-EVAL. +4. Only after PLAN-EVAL PASS may implementation begin. + +## Key decisions + +- Exact six-key `typeof commonErrorMap`; never open `ErrorMap`. +- `SafeResult` gains literal defined/non-defined failure arms and failure `data: undefined`. +- No broad fallback error union; error identity must originate in the real client promise. +- Breaking published change, not patch-level. +- No new SDK error export; metadata exports are conditional on ownership ruling. + +## Files changed + +Only `.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/` plan-phase artifacts. The +launcher-created `codex-thread-ids.md` is preserved. No declared product/doc file was modified. + +## Gates + +- Plan artifacts: ready for topic-orchestrator ruling. +- PLAN-EVAL: not launched by instruction. +- Implementation/static/docs/fitness gates: not run. +- Baseline JSR/publish inspection is recorded in `research.md` and `worklog.md`. + +## Open questions + +- Required sixth file authorization. +- Metadata owner and required barrel/SDK metadata scope. +- Disposition of already-stale out-of-scope contracts/benchmark prose. + +## Drift and debt + +- Drift: significant scope and live-ownership conflicts; see `drift.md`. +- Debt: no new debt accepted. +- Commit trail: draft PR commit list plus phase comment; no `commits.md`. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md new file mode 100644 index 000000000..04f9caf53 --- /dev/null +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md @@ -0,0 +1,41 @@ +# Drift Log: sdk-typed-error-channel (#1350) + +## 2026-08-15 — Real client error loss is outside the declared surface + +- **What:** Exact procedure error identity is erased in `packages/sdk/src/ports/service-client.ts`, + which is not one of the five declared files. +- **Source:** Executed `deno doc`, source map, and TS2339 RED. +- **Expected:** The declared surface was sufficient for an end-to-end `safe()` repair. +- **Actual:** `ServiceClientMethod` returns plain `Promise` before `safe()` sees the value; + editing `errors.ts` alone cannot recover the missing type. +- **Severity:** significant +- **Action:** rescope; request topic-orchestrator authorization and stop product work. +- **Evidence:** `packages/sdk/src/ports/service-client.ts:165-196`; + `packages/sdk/src/client/service-client.ts:41-65`; `research.md` §§1,3. + +## 2026-08-15 — Procedure metadata ownership and barrel scope conflict + +- **What:** The lane brief assigns procedure-metadata preservation to #1350, while live #1466 owns + metadata initialization/export and #1350's existing maintainer comment explicitly excludes it. +- **Source:** RFC 0001, issues #1348/#1350/#1466 fetched on 2026-08-15, and declared file surface. +- **Expected:** Stage 0 had settled one unambiguous owner and the declared files could publish it. +- **Actual:** RFC 0001 says Stage 0 must choose; #1348 contains internally conflicting wording; + #1466 is open; and the required curated root barrel is outside scope. +- **Severity:** architectural +- **Action:** rescope; brief treated as latest planning instruction, but implementation blocked for + topic-orchestrator ruling. +- **Evidence:** `rfcs/0001-sdk-client-contributions.md:347-370,1267-1277`; + `packages/contracts/src/public/mod.ts:1-6`; `research.md` “Re-baseline and authority.” + +## 2026-08-15 — Out-of-scope published prose already describes the erasure + +- **What:** Contracts/benchmark prose outside the two authorized docs pages explicitly says the + public base contract is erased; a successful repair makes that prose false. +- **Source:** Executed whole-repo consumer search. +- **Expected:** The two declared docs pages were the complete affected narrative. +- **Actual:** Additional published/reference consumers exist. +- **Severity:** significant +- **Action:** request scope disposition; do not edit without authorization. +- **Evidence:** `packages/bench/tasks/t1-storefront-api/reference/README.md:45-47`; + `packages/bench/tasks/t1-storefront-api/reference/netscript/router.ts:7-8`; + `packages/contracts/README.md:13-76`. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan.md new file mode 100644 index 000000000..4bf84c837 --- /dev/null +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan.md @@ -0,0 +1,278 @@ +# Plan: preserve contract errors through `safe()` and `isDefinedError` + +## Run Metadata + +| Field | Value | +| -------------- | ----------------------------------------------------------------------------------------------------------- | +| Run ID | `fix-sdk-typed-error-channel--0.0.7-wave1` | +| Branch | `fix/sdk-typed-error-channel` | +| Phase | `plan` — blocked pending scope/ownership ruling, then PLAN-EVAL | +| Target | `packages/contracts`, `packages/sdk`, and two published docs pages | +| Archetype | `1 — Small Contract` for this bounded contract slice (the SDK package remains doctrine Archetype 2 overall) | +| Scope overlays | `docs` | + +## Archetype and doctrine verdict + +This slice changes a small published type/result contract with no new runtime subsystem, so the +owner-selected Archetype 1 profile is the effective slice profile. Doctrine currently classifies +`@netscript/contracts` as Archetype 1 / Keep and `@netscript/sdk` as Archetype 2 / Keep +(`docs/architecture/doctrine/10-codebase-verdict-and-handoff.md:25-35,53`). The plan preserves the +SDK's existing discovery/client boundary and changes only the type identity/result normalization +needed for error flow. + +## Goal + +A contract-defined oRPC error keeps its literal code and schema-derived data type from +`baseContract`, through the contract procedure and `ServiceClient` method's promise, into `safe()`. +`SafeResult` discriminates defined vs non-defined failures, and `isDefinedError` narrows the same +error union without widening to `string`/`unknown` or collapsing to `never`. + +## Locked error contract + +The following is the planned public TypeScript shape. Helper aliases used to describe the shape stay +internal to `errors.ts`; existing published names remain the only SDK error exports. + +```ts +interface DefinedError extends Error { + readonly defined: true; + readonly code: TCode; + readonly status: number; + readonly data: TData; +} + +type DefinedErrorLike = Error & { + readonly defined: boolean; + readonly code: string; + readonly status: number; + readonly data: unknown; +}; + +type NarrowDefined = Extract & DefinedError; + +type SafeFailure = + | ([Exclude, undefined, false, false] & { + error: Exclude; + data: undefined; + isDefined: false; + isSuccess: false; + }) + | ([NarrowDefined, undefined, true, false] & { + error: NarrowDefined; + data: undefined; + isDefined: true; + isSuccess: false; + }); + +type SafeResult = + | SafeSuccess + | SafeFailure; + +function isDefinedError(error: T): error is NarrowDefined; + +function safe( + promise: ClientPromiseResult, +): Promise>; +``` + +Rationale: + +- It matches upstream stable-v1 `safe` behavior: two failure arms distinguished by + literal `isDefined`, with failure `data`/tuple slot 2 as `undefined`, while retaining NetScript's + package-owned `DefinedError` name and runtime `defined: true` guarantee. +- `NarrowDefined` retains each upstream member's literal `code` and schema-derived `data` + through intersection; it does not replace them with `string` and `unknown`. +- A plain `unknown` still does not become a contract error merely because a runtime predicate was + called. The type must originate in the client promise's real error channel. + +The contract builder shape is: + +```ts +type BaseContractErrors = MergedErrorMap, typeof commonErrorMap>; + +const baseContract: ContractBuilder< + Schema, + Schema, + BaseContractErrors, + TApprovedMeta +> = /* explicit builder chain */; +``` + +`TApprovedMeta` is the only unlocked part because the live ownership sources conflict: + +- if #1466 remains owner, it is `Record` and #1350 only ensures the four-generic + spelling does not block #1466; +- if the slice brief supersedes #1466, it is `NetScriptProcedureMeta & Record` and the + builder initializes that metadata as RFC 0001 requires. + +The error-map choice itself is locked: `typeof commonErrorMap`, never open `ErrorMap`. + +## Breaking-change verdict + +**Breaking published contract; not patch-level.** `SafeFailure` currently has one arm with +`isDefined: boolean` and failure `data: null`; the accepted/upstream-compatible contract changes it +to two literal-discriminated arms and `data: undefined`. Code that asserts `result.data === null`, +constructs a `SafeFailure`, or expects one undifferentiated failure arm must change. Tightening +`baseContract` from an open error-map key space to the six declared literals also intentionally +rejects consumers that treated undeclared codes as valid. Repository search found no package source +constructing `SafeFailure`, but external JSR consumers remain affected. This belongs in the 0.0.7 +minor line with explicit migration notes; it must not be described as a patch-compatible change. + +## What becomes public + +- Error-only ruling: **no new SDK or contracts export names**. Existing `baseContract`, + `BaseContract`, `BaseContractRoute`, `BaseContractOutputRoute`, `DefinedError`, `SafeSuccess`, + `SafeFailure`, `SafeResult`, `safe`, `isDefinedError`, and client types acquire corrected + signatures. +- Metadata-in-#1350 ruling: RFC 0001 requires three new root contracts exports: + `NetScriptAuthenticationRequirement`, `NetScriptProcedureMeta`, and the metadata-aware + `baseContract` type. The types originate in `contract-primitives.ts`, but the curated root barrel + must explicitly export them. No internal helper or upstream adapter type becomes public. +- No new SDK barrel, subpath, helper export, or error code is added. + +## Source authority by decision + +| Decision | Normative text | +| ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Six literal common errors, no new code | Current declaration at `packages/contracts/src/application/contract-primitives.ts:21-52`; issue boundary; RFC exact error inference gate at `rfcs/0001-sdk-client-contributions.md:1317-1333`. | +| Contract vs preparation failures stay separate | RFC error model at `rfcs/0001-sdk-client-contributions.md:1032-1088`. | +| Four-generic explicit builder; no `ReturnType` erasure | RFC metadata section at `rfcs/0001-sdk-client-contributions.md:347-370`. | +| Safe result discriminates defined/non-defined | Accepted issue target plus upstream v1.14.6 `deno doc`; the issue's older prose is used only where it agrees with merged RFC. | +| Metadata ownership | **Unresolved conflict:** lane brief says #1350; RFC says Stage 0 chooses; live #1466 and #1350 comment say #1466. Topic orchestrator must rule. | + +## Declared scope and required expansion + +### Authorized five files + +1. `packages/contracts/src/application/contract-primitives.ts` +2. `packages/sdk/src/client/errors.ts` +3. `packages/sdk/tests/readme-doctest_test.ts` +4. `docs/site/services-sdk/sdk.md` +5. `docs/site/services-sdk/how-to/discover-services.md` + +### Must resolve now — required sixth/seventh files + +| File | Why required | Status | +| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | +| `packages/sdk/src/ports/service-client.ts` | It is where `TError` is erased to `Promise`; without changing its procedure/client algebra, `safe()` cannot infer a real contract error. | Scope expansion requested; no implementation until approved. | +| `packages/contracts/src/public/mod.ts` | Required only if this leaf owns RFC metadata: the curated root barrel cannot expose the three mandated types otherwise. | Scope/ownership ruling requested. | + +Potential published prose that becomes factually stale (`packages/contracts/README.md` and benchmark +reference text) is also reported for a ruling; it will not be edited without authorization. + +## Planned docs dispositions + +Every current error-story location has an explicit disposition in `research.md` §4. Implementation +must update all of these as a coherent narrative, not just the line containing `error.code`: + +- SDK page: lines 12-20, 31-38, 58-70, 113-114 retained only with accurate scope; lines 196-198 + fully replaced with a discriminated error example and explanatory wording. +- Discovery how-to: lines 9-14, 96-101, and 114-115 qualified/retained after proof; lines 135-154 + and 205-229 fully rewritten so non-defined errors cannot fall through as success and `data` is + described as schema-derived. + +## Behavioral proof and expected RED + +The implementation-phase test must first be committed/run as RED against the real public exports. It +will build a real route from `baseContract`, derive a real `ServiceClient`, and check: + +```ts +const result = await safe(usersClient.getById({ id: 'usr_1' })); +if (!result.isSuccess && result.isDefined) { + const code: + | 'NOT_FOUND' + | 'VALIDATION_ERROR' + | 'UNAUTHORIZED' + | 'FORBIDDEN' + | 'RATE_LIMITED' + | 'SERVICE_UNAVAILABLE' = result.error.code; +} +``` + +Expected base RED: + +```text +TS2339: Property 'code' does not exist on type 'never'. +``` + +Negative assertions must prove: + +- `NOT_DECLARED` is rejected by `keyof baseContract['~orpc']['errorMap']`; +- a non-oRPC `Error` remains in the `isDefined: false` arm and does not narrow; +- the exact `data` type changes with `code` (for example NOT_FOUND vs VALIDATION_ERROR); +- if metadata is approved here, `meta.access.authentication` survives builder → procedure → client + type inspection, and removing any link makes the fixture fail. + +The test is meaningful because it is red at this base for the issue's named `never` erasure. A new +test written only after the implementation or one using ambient re-declarations is insufficient. + +## Commit slices (blocked until rulings and PLAN-EVAL PASS) + +| # | What the slice proves | Files | Proving gate | +| - | ----------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------- | +| 1 | RED fixture uses real exports and fails with the named TS2339; then the four-generic builder retains exact error keys (and approved metadata owner). | `contract-primitives.ts`, `readme-doctest_test.ts`; plus `src/public/mod.ts` only if approved | Focused structured test/check; negative type assertions | +| 2 | A real service method carries its contract error into `safe()`, whose result and `isDefinedError` preserve literal code/data and reject plain errors. | `errors.ts`, `readme-doctest_test.ts`; plus required `ports/service-client.ts` if approved | Focused structured test/check; exact base RED turns green | +| 3 | Both published pages tell one consistent, compile-accurate error story, including non-defined failure handling. | the two authorized docs pages | `docs-source-format`, `docs-accuracy`, doctest | +| 4 | Published surfaces remain curated and all package gates are recorded without laundering known reds. | run artifacts only | full selected validation set below | + +## Validation plan + +Structured wrappers are the verdict source. Exact arguments may be refined after the scope ruling, +but the gate set is locked: + +| Order | Gate | Planned command/evidence | Expected result | +| ----- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| 1 | RED | Structured focused test/check on the real-surface fixture before implementation | FAIL with TS2339 `code` on `never`; record once | +| 2 | Check | `.llm/tools/run-deno-check.ts` scoped to affected TS, with `--unstable-kv` | PASS after implementation | +| 3 | Test | `.llm/tools/run-deno-test.ts -- --allow-all packages/sdk/tests/readme-doctest_test.ts` plus affected package tests selected after ruling | PASS; if `typed-queue_test.ts` hits #1667 `expected 1, got 2`, report once and do not rerun | +| 4 | Lint | `.llm/tools/run-deno-lint.ts` scoped to affected package TS | PASS | +| 5 | Format | `.llm/tools/run-deno-fmt.ts --ext ts,tsx` scoped to affected package TS | PASS | +| 6 | Quality | `deno task quality:scan` | PASS for the leaf diff | +| 7 | Doctrine | `deno task arch:check` | PASS for the leaf; known base findings separated | +| 8 | Public surface | base-vs-head `surface:diff` sets/signatures | Raw gate remains known-red at base; no unintended new export, only approved signature changes | +| 9 | Docs | repository `docs-source-format` and `docs-accuracy` tasks | PASS for both authorized pages | +| 10 | Doc lint | `deno task doc:lint --root packages/contracts --pretty` and SDK equivalent | Compare with pinned raw baselines; do not relabel baseline reds green | +| 11 | Publish | `deno task publish:dry-run` plus affected per-package raw dry-run where attribution is needed | PASS; contracts sanctioned oRPC slow-type information reported honestly | +| 12 | JSR | repository JSR audits for `packages/contracts` and `packages/sdk` | Report known SDK `F-DOCT-5` red; no new finding | + +No Aspire, Docker, runtime lease, or `e2e:cli` gate is applicable or permitted. + +## Risk register + +| Risk | Mitigation | +| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Broad default error type makes docs compile without preserving identity | RED uses a real client and exact code/data assertions; broad `string`/`unknown` fails. | +| `DefinedError.defined: true` is incompatible with upstream boolean marker | Normalize by intersection only after extracting the original upstream-shaped union; retain original literal code/data. | +| Public failure `null` → `undefined` breaks consumers | Declare breaking change explicitly, search construction/assertions repo-wide, document migration, release in 0.0.7 rather than calling it patch-compatible. | +| Tight builder annotation breaks CRUD/plugin/handler inference | Run named internal/external consumer checks from research §3 and preserve default generics. | +| Metadata work duplicates #1466 | Hard stop pending topic-orchestrator ownership ruling. | +| One corrected sentence leaves false surrounding prose | Apply every disposition in research §4 and run docs accuracy over whole pages. | +| JSR helper false-positive banner is reported as new slow type | Use raw publish output as authority and record helper vs raw distinction. | + +## Anti-patterns and fitness gates + +- Avoid AP-1/AP-9: do not grow `errors.ts` into a generic error framework or invent a second client + algebra. +- Avoid AP-14: do not re-export upstream helpers/types; only use an upstream type internally where + the hidden promise error marker requires identity. +- Avoid AP-22: no new barrel; amend only the existing curated contracts barrel if metadata is + approved. +- Avoid AP-25: all contract/error helpers remain side-effect-free. +- Required Archetype-1 gates: F-1, F-5, F-6, F-7, F-8, F-10, F-11, F-12, F-14, F-15, F-16, F-17, + F-18, F-19, plus package `quality:scan`, `arch:check`, docs overlay gates, publish dry-run, and + JSR audit. + +## Debt and deferred scope + +- No new architecture debt is accepted by this plan. +- Existing `packages/contracts/crud` root-layout debt remains unrelated. +- SDK JSR `F-DOCT-5`, raw doc-lint baselines, base-red `surface:diff`, and #1667 remain pre-existing + and must not be reported as green or as leaf regressions. +- No new error codes, client construction seam (#1349), server raising behavior (#1263), oRPC v2, + type-soundness sweep (#1278), plugin-local base-contract repair, or generated reference rewrite. + +## Plan-Gate state + +`PLAN-EVAL` is selected and is a hard stop. Before the topic orchestrator launches it, the two “must +resolve now” questions must be answered: required `service-client.ts` scope and metadata/#1466 +ownership (including barrel scope). Until then this plan is complete as a rescope report but is not +implementable. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/research.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/research.md new file mode 100644 index 000000000..f40613798 --- /dev/null +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/research.md @@ -0,0 +1,215 @@ +# Research — sdk-typed-error-channel (#1350) + +## Re-baseline and authority + +- Re-derived on 2026-08-15 against `main@0ef48c2ec661a7e6d55ec2faf5def6ae7dd2e6eb`; the issue's + older evidence was against `fac9e339042c`. +- Normative for error behavior: the merged, Accepted RFC 0001 requires exact server-defined error + inference after metadata initialization (`rfcs/0001-sdk-client-contributions.md:1317-1333`) and + keeps contribution/preparation failures outside the contract-defined channel + (`rfcs/0001-sdk-client-contributions.md:1032-1088`). +- Normative for the builder: RFC 0001 requires an explicit publishable annotation preserving both + the concrete common error map and `NetScriptProcedureMeta`, and rejects the current + `ReturnType` spelling (`rfcs/0001-sdk-client-contributions.md:347-370`). +- Normative for this lane: the slice brief says #1350 owns procedure-metadata preservation and + limits changes to five declared files. This conflicts with current GitHub state: #1466 is an open + child titled “define NetScriptProcedureMeta without erasing contract errors,” and #1350's only + existing comment says metadata initialization is not owned by #1350. RFC 0001 itself records that + Stage 0 had to choose #1350 or a dependent child + (`rfcs/0001-sdk-client-contributions.md:1267-1277`). The brief is treated as the latest + instruction for planning, but the contradictory live ownership cannot be verified and needs the + topic orchestrator's ruling before implementation. + +## 1. Current error path and exact loss points + +The executed public-surface inspection reported: + +```text +deno doc --filter safe packages/sdk/src/client/mod.ts + safe(promise: PromiseLike): Promise> + +deno doc --filter isDefinedError packages/sdk/src/client/mod.ts + isDefinedError(error: T): error is Extract + +deno doc --filter SafeResult packages/sdk/src/client/mod.ts + SafeResult +``` + +The path is: + +1. `commonErrorMap` declares six literal keys and schema-specific payloads + (`packages/contracts/src/application/contract-primitives.ts:21-52`). +2. `baseContract` immediately widens those literals to `ReturnType` + (`packages/contracts/src/application/contract-primitives.ts:81`). `BaseContractErrors` widens a + second time to the open upstream `ErrorMap` + (`packages/contracts/src/application/contract-primitives.ts:91-99`), and both published route + aliases consume that widened type + (`packages/contracts/src/application/contract-primitives.ts:125-159`). An executed check accepted + `const undeclared: keyof typeof baseContract['~orpc']['errorMap'] = 'NOT_DECLARED'`, proving the + literal vocabulary is already gone at the contract boundary. +3. The SDK's structural `ContractProcedureMetadata` retains only input/output schemas + (`packages/sdk/src/ports/service-client.ts:48-60`). `ContractProcedureLike` therefore has no + error-map parameter (`packages/sdk/src/ports/service-client.ts:75-91`). +4. `ServiceClientMethod` returns plain `Promise` + (`packages/sdk/src/ports/service-client.ts:165-171`), and `ServiceClientShape` derives only input + and output (`packages/sdk/src/ports/service-client.ts:181-196`). The actual oRPC client is cast + to that narrowed shape (`packages/sdk/src/client/service-client.ts:41-65`). This is the decisive + loss of the promise's hidden `TError` channel before the caller passes it to `safe()`. +5. `SafeResult` defaults `TError` to `unknown`, and its single failure arm exposes only a boolean + `isDefined` (`packages/sdk/src/client/errors.ts:36-49`). `safe()` does not accept or infer a + `TError` at all (`packages/sdk/src/client/errors.ts:79-91`). +6. `isDefinedError` computes `Extract` (`packages/sdk/src/client/errors.ts:69-77`). + For the `unknown` error emitted by `safe()`, that is `never`; additionally, the local + `DefinedError.defined` property is the literal `true` (`packages/sdk/src/client/errors.ts:9-24`), + whereas the upstream oRPC error class exposes a boolean marker, so a raw upstream error union is + not structurally assignable to the current interface without normalization. + +The exact base RED was executed without creating a product fixture: + +```text +cd packages/sdk +deno eval --check --unstable-kv "import { isDefinedError, safe } from './src/client/mod.ts'; ..." +TS2339: Property 'code' does not exist on type 'never'. +``` + +This RED proves the named defect because it fails only when `safe()` has erased the promise's error +type and `isDefinedError()` therefore narrows that erased value to `never`. The implementation proof +must strengthen it to use a real `baseContract` procedure and assert the exact six-code union plus +schema-specific `data`; a broad `DefinedError` would not satisfy the proof. + +## 2. Current contract guarantees and public surface + +- `commonErrorMap` defines `NOT_FOUND`, `VALIDATION_ERROR`, `UNAUTHORIZED`, `FORBIDDEN`, + `RATE_LIMITED`, and `SERVICE_UNAVAILABLE`, with statuses and Zod data schemas + (`packages/contracts/src/application/contract-primitives.ts:21-52`). It is internal. +- `baseContract`, `BaseContract`, `BaseContractRoute`, and `BaseContractOutputRoute` are public via + the curated root barrel (`packages/contracts/src/public/mod.ts:1-6`) and package root + (`packages/contracts/mod.ts:1-22`). `BaseContractErrors` is currently exported from its source + file but is not a package-root export. +- The current builder JSDoc claims the route marker is genuinely typed and that precise schema types + reach handlers (`packages/contracts/src/application/contract-primitives.ts:54-68`). That statement + is accurate for input/output schemas, but false for the error map and metadata. +- Current metadata is `Record` in both route aliases + (`packages/contracts/src/application/contract-primitives.ts:125-159`). An executed check of + `typeof baseContract['~orpc']['meta'].access` fails with TS2339. +- `@netscript/sdk/client` publicly exports `safe`, `isDefinedError`, `DefinedError`, `SafeSuccess`, + `SafeFailure`, and `SafeResult` (`packages/sdk/src/client/mod.ts:15-17`), and the SDK root + re-exports the entire client subpath (`packages/sdk/mod.ts:46-48`). These are already stable + published names; the repair does not need a new SDK error export. + +## 3. Executed whole-repo consumer map + +Executed searches (not inference from one package): + +```text +rg -l --hidden --glob '!.git/**' --glob '!packages/service/assets/**' \ + --glob '!.llm/runs/**' \ + '\b(isDefinedError|SafeResult|SafeSuccess|SafeFailure|DefinedError)\b|\bsafe\(' . + +rg -n '\b(BaseContract|BaseContractErrors|BaseContractRoute|BaseContractOutputRoute|baseContract)\b' \ + packages plugins --glob '!**/assets/**' --glob '!**/*.template' + +rg -n '\bServiceClient(Method|Shape|<)|Procedure(Output|Input)FromNode' \ + packages/sdk packages/fresh packages/service plugins --glob '!**/assets/**' +``` + +### Inside `packages/sdk` + +| Consumer | Evidence | Consequence | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| Client and root barrels | `packages/sdk/src/client/mod.ts:15-35`; `packages/sdk/mod.ts:46-79` | Existing public error and client type names change in place; no new SDK export is required. | +| Direct client construction | `packages/sdk/src/client/service-client.ts:41-65` | Cast currently discards upstream error identity; must participate in an end-to-end repair. | +| `defineServices` | `packages/sdk/src/presets/define-services.ts:47-69` | Maps every service to `ServiceClient` and will inherit the repaired channel. | +| Query factories | `packages/sdk/src/query/query-factory.ts:197-224` | Assert clients are `ServiceClient`; default-compatible type changes must keep assignments green. | +| Query utils | `packages/sdk/src/ports/service-query-utils.ts:161-170` | Exposes `call: ServiceClientMethod`; any appended error generic needs a compatibility default. | +| Desktop client | `packages/sdk/src/desktop/domain/types.ts:147-155` | Aliases `ServiceClient` and therefore asserts on the same public contract. | +| Type/mocked consumers | `packages/sdk/tests/query/query-factory_test.ts:60-257`; `packages/sdk/tests/type-fixtures/sdk-assignability_type.ts:58`; `packages/sdk/tests/type-fixtures/define-services_type.ts:54-57`; `packages/sdk/tests/type-fixtures/desktop-consumer_type.ts:39` | Plain Promise mocks and old positional generics must remain assignable. | +| Public-surface baseline/reference | `.llm/tools/release/baselines/public-surfaces.json:18811-19194`; `docs/site/reference/sdk/index.md:38-60` | `surface:diff` must compare base-vs-head sets/signatures; generated reference is a consumer but outside this leaf's editable surface. | + +### Outside `packages/sdk` + +| Consumer | Evidence | Consequence | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `@netscript/fresh` diagnostics | `packages/fresh/src/diagnostics/error/extract.ts:1-39` | Imports `isDefinedError`, then manually casts code/status. It must still compile and preserve runtime detection; it is the only non-SDK package source importing the helper. | +| `@netscript/contracts/crud` | `packages/contracts/crud/create-crud-contract.ts:29-44,109-117,350-379` | Directly consumes public `baseContract` and `BaseContractRoute`; exact error/meta generics must remain assignable. | +| Workers health soundness test | `plugins/workers/services/src/routers/health-soundness_test.ts:3-53` | Asserts `BaseContractRoute` handler inference; metadata/error tightening must not regress input/output soundness. | +| CLI scaffold/template assertions | `packages/cli/e2e/tests/application/gates/generated-router-template_test.ts:35-41`; `packages/cli/src/kernel/adapters/contracts/contract-source.ts:63-77`; `packages/cli/src/public/features/plugins/new/new-plugin-use-case.ts:390-416` | Some generated contracts import root `baseContract`; the parser recognizes its route chain. No template change is planned. | +| Benchmark/reference/docs consumers | `packages/bench/tasks/t1-storefront-api/rubric.md:16`; `packages/bench/tasks/t1-storefront-api/reference/README.md:45-47`; `packages/bench/tasks/t1-storefront-api/reference/netscript/router.ts:7-8`; `packages/contracts/README.md:13-76` | Several statements explicitly describe the current erasure. They become stale after repair but are outside the declared surface, creating another documentation rescope question for the orchestrator. | + +First-party plugin-core files found by the base-contract search define their own local +`baseContract: ReturnType` rather than importing `@netscript/contracts` +(`packages/plugin-ai-core/src/contracts/v1/ai.contract.ts:147-157`, +`packages/plugin-auth-core/src/contracts/v1/auth.contract.ts:182-195`, +`packages/plugin-sagas-core/src/contracts/v1/sagas.contract.ts:99-110`, +`packages/plugin-triggers-core/src/contracts/v1/triggers.contract.ts:134-145`, and +`packages/plugin-workers-core/src/contracts/v1/workers.contract-definition.ts:70-88`). They do not +consume this leaf's public error channel, but the executed search shows the same erasing pattern is +not globally repaired by #1350. + +## 4. Authorized docs: current claims and dispositions + +### `docs/site/services-sdk/sdk.md` + +| Lines | Current claim | Truth at base | Planned disposition | +| ------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 12-20 | The contract supplies everything a caller needs and caller/server cannot drift. | Too broad for errors today; method input/output are typed, error channel is not. | Retain after the repair, but qualify the nearby error example so “contract-derived” explicitly includes only declared server errors, not arbitrary thrown/transport errors. | +| 31-38 | L1 method signatures are inferred from the contract. | True for input/output, false for the promise error channel. | Retain after end-to-end method return typing is repaired; otherwise it must be narrowed. | +| 58-70 | End-to-end types and contract changes flow to the client. | Already false for error codes/data. | Retain only if the RED proves error flow; no one-sentence patch while this surrounding story remains unqualified. | +| 113-114 | A direct call is “fully inferred from the contract.” | False for errors at base. | Retain with an explicit cross-reference to the safe error story after proof. | +| 196-198 | “Safe error narrowing” example says `isDefinedError(error)` yields typed `code/status`. | False; executed check gives TS2339 on `never`; the destructured `isDefined` is unused. | Replace the complete tab text. Explain and demonstrate the discriminated `SafeResult`: `isSuccess`, then `isDefined`, exact code/data; use `isDefinedError` in a second narrow or explain when it is useful. | + +### `docs/site/services-sdk/how-to/discover-services.md` + +| Lines | Current claim | Truth at base | Planned disposition | +| --------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 9-14 | The discovered client is “fully typed.” | False for errors. | Retain only after the end-to-end error RED turns green; qualify that transport/unknown throws remain non-defined failures. | +| 96-101, 114-115 | Client is fully typed from the contract and fields drift at compile time. | True for input/output, false for error codes/data. | Retain with wording that includes the newly proven error union, avoiding an isolated snippet-only fix. | +| 135-154 | Step 4 says `safe()` plus `isDefinedError` provides typed contract code/data. | False at base. It also falls through an arbitrary non-defined error into the success `else`, making `result.items` narratively unsafe. | Rewrite the whole Step 4 explanation and block: branch on `isSuccess`; branch on literal `isDefined`; surface/throw the non-defined error; state `error.data` is selected by the error code's Zod schema. | +| 205-229 | End-to-end copyable example repeats safe narrowing and then returns `result.items`. | False for the same typing reason and unsafe narrative. | Rewrite the full function consistently with Step 4; do not retain the old fallthrough. | + +## 5. Granted doctest surface + +`packages/sdk/tests/readme-doctest_test.ts` does not execute package examples against package +exports. It: + +1. reads only `packages/sdk/README.md` (`packages/sdk/tests/readme-doctest_test.ts:3,117-120`); +2. extracts every fenced TypeScript block (`packages/sdk/tests/readme-doctest_test.ts:92-109`); +3. strips all imports and `export` keywords (`packages/sdk/tests/readme-doctest_test.ts:111-115`); +4. prepends a fictional ambient API, including locally declared `safe` and `isDefinedError` + signatures that are stronger than the real exports + (`packages/sdk/tests/readme-doctest_test.ts:5-90`, especially `:36-37`); +5. runs `deno check --no-config` for each temporary snippet + (`packages/sdk/tests/readme-doctest_test.ts:124-141`); and +6. separately parses JSON fences (`packages/sdk/tests/readme-doctest_test.ts:144-152`). + +The granted test can be changed to import the real error helpers and add a real compile-only +contract/client fixture, but it cannot make the actual client promise preserve a contract error +unless `packages/sdk/src/ports/service-client.ts` is changed. + +## JSR/publish surface scan + +- `deno doc` confirmed the currently published signatures before source inspection. +- The repository JSR audit reports `@netscript/contracts` with the sanctioned oRPC slow-type INFO; + raw `deno publish --dry-run --allow-dirty --allow-slow-types` exits 0. The explicit four-generic + builder annotation must not deepen that sanctioned warning. +- The audit reports SDK `F-DOCT-5` because `src/` has 13 immediate children, a known pre-existing + red. Its helper also reports a slow-type warning by counting the “Checking for slow types” banner; + raw `deno publish --dry-run --allow-dirty` exits 0 with no actual slow-type diagnostic. Record the + raw result as authority per `netscript-tools`. +- No new entrypoint is needed for the error repair. If metadata is in this leaf, RFC 0001 requires + new root exports, which cannot be achieved within the declared files because the curated barrel is + `packages/contracts/src/public/mod.ts:1-6`. + +## Open questions requiring a ruling + +1. **Scope expansion:** authorize `packages/sdk/src/ports/service-client.ts`. Without it, exact + procedure error inference is impossible and the accepted error contract cannot be met. +2. **Metadata owner:** does this brief supersede live #1466 and #1350's existing maintainer comment? + If yes, authorize `packages/contracts/src/public/mod.ts` (and likely the SDK contract metadata + port/type surface) so the RFC-mandated root metadata exports and propagation can exist. If no, + #1350 should preserve the fourth `ContractBuilder` generic as `Record` and leave + initialization/export/propagation to #1466. +3. **Stale but out-of-scope published prose:** authorize updates to `packages/contracts/README.md` + and benchmark reference prose that explicitly says the root base contract is erased, or accept a + tracked docs follow-up. Leaving those statements unchanged would violate the brief's own + surrounding-narrative rule. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/supervisor.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/supervisor.md new file mode 100644 index 000000000..b464c38ab --- /dev/null +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/supervisor.md @@ -0,0 +1,28 @@ +# Supervisor Identity — fix-sdk-typed-error-channel--0.0.7-wave1 + +Written at run start per `workflow/lane-policy.md` § Supervisor identity. + +| Field | Value | +| -------- | ------------------------------------------------------------ | +| Model | Codex · OpenAI · GPT-5.6 Sol · medium | +| Session | `01a006f3-ae2d-7941-bd17-2ac71dd3d0f0` | +| Host | `YogaBook9i` · WSL2 Linux · `codex` | +| Checkout | `/home/codex/repos/netscript-007-leaf-typed-error` | +| Worktree | `/home/codex/repos/netscript-007-leaf-typed-error` | +| Branch | `fix/sdk-typed-error-channel` | +| Baseline | `main@0ef48c2ec661a7e6d55ec2faf5def6ae7dd2e6eb` · 2026-08-15 | +| Run ID | `fix-sdk-typed-error-channel--0.0.7-wave1` | + +## Routes in force + +| Task lane | Provider / model / effort | Role in this run | +| ----------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `normal_implementation` | OpenAI / GPT-5.6 Sol / medium | Research and plan generator; implementation is prohibited until external PLAN-EVAL passes. | +| `review_codex` | Native opposite-family route selected by the topic orchestrator | PLAN-EVAL; not launched by this session. | + +## Owner constraints + +- Plan-first. This session must not implement or launch an evaluator. +- The declared product/doc change surface is exactly five files. Any required sixth file is a + rescope and stops implementation pending a topic-orchestrator ruling. +- No Aspire, Docker, `e2e:cli`, runtime lease, lock deletion, cache deletion, or reload. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md new file mode 100644 index 000000000..780572476 --- /dev/null +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md @@ -0,0 +1,83 @@ +# Worklog: sdk-typed-error-channel (#1350) + +## Run Metadata + +| Field | Value | +| -------------- | ------------------------------------------ | +| Run ID | `fix-sdk-typed-error-channel--0.0.7-wave1` | +| Branch | `fix/sdk-typed-error-channel` | +| Archetype | `1 — Small Contract` slice | +| Scope overlays | `docs` | + +## Design + +### Public surface + +- Existing: `baseContract`, `BaseContract`, `BaseContractRoute`, `BaseContractOutputRoute` — exact + error/meta generics corrected in place. +- Existing: `DefinedError`, `SafeSuccess`, `SafeFailure`, `SafeResult`, `safe`, `isDefinedError`, + and `ServiceClient*` — exact error channel corrected in place. +- Conditional on ownership ruling: `NetScriptAuthenticationRequirement` and `NetScriptProcedureMeta` + become new contracts root exports because RFC 0001 requires them. +- No internal helper, adapter, upstream module, or additional subpath becomes public. + +### Domain vocabulary + +- `BaseContractErrors` — exact `typeof commonErrorMap` merge, not open `ErrorMap`. +- `DefinedErrorLike` — private structural extraction boundary matching the upstream error identity + without exporting the upstream class. +- `NarrowDefined` — private conditional type retaining the original code/data members and + adding the runtime-proven `defined: true` marker. +- `SafeResult` — success, non-defined failure, and defined failure discriminated by + `isSuccess`/`isDefined`. +- `NetScriptProcedureMeta` — conditional on the ownership ruling; exact RFC 0001 shape. + +### Ports + +- No new port. The existing `ServiceClient` contract in `src/ports/service-client.ts` must retain + the promise error identity; inventing a parallel port in `errors.ts` would not repair end-to-end + flow. + +### Constants + +- No new constants. The six existing common error-map keys remain the complete vocabulary. + +### Commit slices + +See `plan.md` “Commit slices.” All implementation slices are blocked pending scope ruling and +PLAN-EVAL PASS. + +### Deferred scope + +- Client contributions, transport changes, new codes, server raising behavior, oRPC v2, plugin-local + base-contract erasures, and broad soundness cleanup. + +### Contributor path + +Future contract error additions begin in the single `commonErrorMap`; the derived builder/client +types and RED fixture must then expose the new literal and schema-derived data automatically. No +second code union is hand-maintained. + +## Progress log + +| Time | Slice | Step | Notes | +| ---------- | ----- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | +| 2026-08-15 | Plan | Bootstrap/research | Re-baselined exact branch/base; read required skills, doctrine, harness, Accepted RFC, issues, and docs. | +| 2026-08-15 | Plan | Public inspection | `deno doc` confirmed current `safe`, `isDefinedError`, `SafeResult`, and `baseContract` signatures. | +| 2026-08-15 | Plan | RED | Executed TS2339 `error.code` on `never` with `deno eval --check --unstable-kv`. | +| 2026-08-15 | Plan | Consumer scan | Executed whole-repo searches; identified `@netscript/fresh`, CRUD, query/desktop/type fixtures, CLI/template, docs, and baseline consumers. | +| 2026-08-15 | Plan | Rescope | Required out-of-scope `service-client.ts`; conditional contracts barrel; live #1466 ownership conflict. Stopped all product work. | + +## Gate results + +| Gate | Result | Evidence | +| ------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------ | +| Current SDK raw publish dry-run | PASS (baseline inspection only) | `deno publish --dry-run --allow-dirty`, exit 0; no actual slow-type diagnostic | +| Current contracts raw publish dry-run | PASS with sanctioned slow-type warning | `deno publish --dry-run --allow-dirty --allow-slow-types`, exit 0 | +| Current JSR audit | KNOWN RED / INFO | SDK `F-DOCT-5`; contracts sanctioned slow-type INFO; not leaf verdicts | +| Implementation gates | NOT RUN | Prohibited before PLAN-EVAL; no product files changed | + +## Handoff + +Topic orchestrator should rule first on the two scope/ownership questions in `plan.md`, then send +the resolved plan to a separate PLAN-EVAL session. This generator must not self-evaluate. From 2fa2f71dc5b498c16221461439e53b9f5dc1d5d5 Mon Sep 17 00:00:00 2001 From: Rickylabs Date: Sat, 15 Aug 2026 22:07:13 +0200 Subject: [PATCH 2/8] docs(harness): lock typed error scope ruling --- .../context-pack.md | 40 +++---- .../drift.md | 13 +++ .../plan.md | 110 +++++++++--------- .../research.md | 64 +++++----- .../supervisor.md | 6 +- .../worklog.md | 13 ++- 6 files changed, 132 insertions(+), 114 deletions(-) diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md index 66aaa8c34..2b5709fa9 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md @@ -2,21 +2,21 @@ ## Run Metadata -| Field | Value | -| -------------- | --------------------------------------------------------- | -| Run ID | `fix-sdk-typed-error-channel--0.0.7-wave1` | -| Branch | `fix/sdk-typed-error-channel` | -| Current phase | `plan` — awaiting scope/ownership ruling before PLAN-EVAL | -| Archetype | `1 — Small Contract` slice | -| Scope overlays | `docs` | +| Field | Value | +| -------------- | -------------------------------------------------------------- | +| Run ID | `fix-sdk-typed-error-channel--0.0.7-wave1` | +| Branch | `fix/sdk-typed-error-channel` | +| Current phase | `plan` — amended; awaiting fresh Tier-A review, then PLAN-EVAL | +| Archetype | `1 — Small Contract` slice | +| Scope overlays | `docs` | ## Current state Research and a concrete error contract are complete. No product or docs implementation exists. The base RED is TS2339 because `safe()` erases `TError` and `isDefinedError()` narrows the result to -`never`. Exact end-to-end repair cannot fit the declared five-file surface because -`packages/sdk/src/ports/service-client.ts` is the loss point. Procedure metadata additionally has a -live owner conflict with #1466 and requires the curated contracts barrel if assigned here. +`never`. The coordinator authorized `packages/sdk/src/ports/service-client.ts` as the sixth and +final product path. #1466 owns metadata definition/export; this leaf keeps only the existing fourth +generic slot as `Record` and makes no metadata semantic claim. ## Completed @@ -28,11 +28,9 @@ live owner conflict with #1466 and requires the curated contracts barrel if assi ## Next steps -1. Topic orchestrator rules on `packages/sdk/src/ports/service-client.ts` scope. -2. Topic orchestrator rules whether #1350 supersedes #1466 for procedure metadata and, if so, - authorizes `packages/contracts/src/public/mod.ts` plus any required SDK metadata type surface. -3. Update the plan with that ruling if needed; separate session performs PLAN-EVAL. -4. Only after PLAN-EVAL PASS may implementation begin. +1. Fresh Tier-A reviews the amended head. +2. A separate session performs PLAN-EVAL after that review passes. +3. Only after PLAN-EVAL PASS may implementation begin. ## Key decisions @@ -40,7 +38,9 @@ live owner conflict with #1466 and requires the curated contracts barrel if assi - `SafeResult` gains literal defined/non-defined failure arms and failure `data: undefined`. - No broad fallback error union; error identity must originate in the real client promise. - Breaking published change, not patch-level. -- No new SDK error export; metadata exports are conditional on ownership ruling. +- No new export. The empty fourth metadata generic remains explicit; all metadata vocabulary belongs + to #1466. +- The exact six-path ceiling is locked; any seventh product/test/docs path requires a fresh ruling. ## Files changed @@ -56,12 +56,12 @@ launcher-created `codex-thread-ids.md` is preserved. No declared product/doc fil ## Open questions -- Required sixth file authorization. -- Metadata owner and required barrel/SDK metadata scope. -- Disposition of already-stale out-of-scope contracts/benchmark prose. +- None that can force implementation rework. Stale contracts/benchmark prose is tracked follow-up + debt outside the six-path ceiling. ## Drift and debt -- Drift: significant scope and live-ownership conflicts; see `drift.md`. +- Drift: the earlier scope and ownership conflicts are resolved by the coordinator amendment; see + the append-only resolution in `drift.md`. - Debt: no new debt accepted. - Commit trail: draft PR commit list plus phase comment; no `commits.md`. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md index 04f9caf53..9af335447 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md @@ -39,3 +39,16 @@ - **Evidence:** `packages/bench/tasks/t1-storefront-api/reference/README.md:45-47`; `packages/bench/tasks/t1-storefront-api/reference/netscript/router.ts:7-8`; `packages/contracts/README.md:13-76`. + +## 2026-08-15 — Coordinator resolution locks six paths and #1466 metadata ownership + +- **What:** The coordinator corrected the original brief after reconciling #1348's amendment and + #1350 comment 5227724542. +- **Resolution:** `packages/sdk/src/ports/service-client.ts` is authorized as the sixth and final + product path. Any seventh product/test/docs path requires a fresh ruling. +- **Metadata:** #1466 owns definition, initialization, export, and semantic proof. #1350 retains + only the explicit fourth generic slot as `Record` and introduces no vocabulary. +- **Denied:** The contracts public barrel and all metadata export work. +- **Deferred:** Stale contracts/benchmark prose remains tracked follow-up debt outside this leaf. +- **Phase effect:** The plan has no remaining must-resolve decision, but fresh Tier-A review and a + separate PLAN-EVAL `PASS` remain hard stops before implementation. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan.md index 4bf84c837..2c64877ed 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan.md @@ -6,7 +6,7 @@ | -------------- | ----------------------------------------------------------------------------------------------------------- | | Run ID | `fix-sdk-typed-error-channel--0.0.7-wave1` | | Branch | `fix/sdk-typed-error-channel` | -| Phase | `plan` — blocked pending scope/ownership ruling, then PLAN-EVAL | +| Phase | `plan` — coordinator-amended; awaiting fresh Tier-A review, then separate PLAN-EVAL | | Target | `packages/contracts`, `packages/sdk`, and two published docs pages | | Archetype | `1 — Small Contract` for this bounded contract slice (the SDK package remains doctrine Archetype 2 overall) | | Scope overlays | `docs` | @@ -93,18 +93,14 @@ const baseContract: ContractBuilder< Schema, Schema, BaseContractErrors, - TApprovedMeta + Record > = /* explicit builder chain */; ``` -`TApprovedMeta` is the only unlocked part because the live ownership sources conflict: - -- if #1466 remains owner, it is `Record` and #1350 only ensures the four-generic - spelling does not block #1466; -- if the slice brief supersedes #1466, it is `NetScriptProcedureMeta & Record` and the - builder initializes that metadata as RFC 0001 requires. - -The error-map choice itself is locked: `typeof commonErrorMap`, never open `ErrorMap`. +Both generic choices are locked: errors use `typeof commonErrorMap`, never open `ErrorMap`; the +fourth slot remains explicitly `Record`. This leaf proves the metadata slot is present +and not widened to `any`/`unknown`, but assigns no fields or semantics to it. #1466 will later +replace that slot with its owned vocabulary. ## Breaking-change verdict @@ -119,15 +115,12 @@ minor line with explicit migration notes; it must not be described as a patch-co ## What becomes public -- Error-only ruling: **no new SDK or contracts export names**. Existing `baseContract`, - `BaseContract`, `BaseContractRoute`, `BaseContractOutputRoute`, `DefinedError`, `SafeSuccess`, - `SafeFailure`, `SafeResult`, `safe`, `isDefinedError`, and client types acquire corrected - signatures. -- Metadata-in-#1350 ruling: RFC 0001 requires three new root contracts exports: - `NetScriptAuthenticationRequirement`, `NetScriptProcedureMeta`, and the metadata-aware - `baseContract` type. The types originate in `contract-primitives.ts`, but the curated root barrel - must explicitly export them. No internal helper or upstream adapter type becomes public. +- **No new SDK or contracts export names.** Existing `baseContract`, `BaseContract`, + `BaseContractRoute`, `BaseContractOutputRoute`, `DefinedError`, `SafeSuccess`, `SafeFailure`, + `SafeResult`, `safe`, `isDefinedError`, and client types acquire corrected signatures. - No new SDK barrel, subpath, helper export, or error code is added. +- No metadata type, field vocabulary, initialization, root export, or semantic fixture is + introduced; those belong to #1466. ## Source authority by decision @@ -137,27 +130,23 @@ minor line with explicit migration notes; it must not be described as a patch-co | Contract vs preparation failures stay separate | RFC error model at `rfcs/0001-sdk-client-contributions.md:1032-1088`. | | Four-generic explicit builder; no `ReturnType` erasure | RFC metadata section at `rfcs/0001-sdk-client-contributions.md:347-370`. | | Safe result discriminates defined/non-defined | Accepted issue target plus upstream v1.14.6 `deno doc`; the issue's older prose is used only where it agrees with merged RFC. | -| Metadata ownership | **Unresolved conflict:** lane brief says #1350; RFC says Stage 0 chooses; live #1466 and #1350 comment say #1466. Topic orchestrator must rule. | +| Metadata ownership | Coordinator ruling: #1466 owns definition, initialization, and export; #1350 preserves only the empty fourth generic slot. | -## Declared scope and required expansion +## Exact six-path ceiling -### Authorized five files +These are the complete authorized product/test/docs paths: 1. `packages/contracts/src/application/contract-primitives.ts` 2. `packages/sdk/src/client/errors.ts` -3. `packages/sdk/tests/readme-doctest_test.ts` -4. `docs/site/services-sdk/sdk.md` -5. `docs/site/services-sdk/how-to/discover-services.md` - -### Must resolve now — required sixth/seventh files - -| File | Why required | Status | -| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -| `packages/sdk/src/ports/service-client.ts` | It is where `TError` is erased to `Promise`; without changing its procedure/client algebra, `safe()` cannot infer a real contract error. | Scope expansion requested; no implementation until approved. | -| `packages/contracts/src/public/mod.ts` | Required only if this leaf owns RFC metadata: the curated root barrel cannot expose the three mandated types otherwise. | Scope/ownership ruling requested. | +3. `packages/sdk/src/ports/service-client.ts` +4. `packages/sdk/tests/readme-doctest_test.ts` +5. `docs/site/services-sdk/sdk.md` +6. `docs/site/services-sdk/how-to/discover-services.md` -Potential published prose that becomes factually stale (`packages/contracts/README.md` and benchmark -reference text) is also reported for a ruling; it will not be edited without authorization. +This ceiling is exact. A seventh product, test, or docs path is a rescope requiring a fresh +coordinator ruling. In particular, `packages/contracts/src/public/mod.ts`, +`packages/contracts/README.md`, and the benchmark reference files are not authorized. The stale +prose remains tracked follow-up debt rather than being absorbed into this leaf. ## Planned docs dispositions @@ -199,31 +188,46 @@ Negative assertions must prove: - `NOT_DECLARED` is rejected by `keyof baseContract['~orpc']['errorMap']`; - a non-oRPC `Error` remains in the `isDefined: false` arm and does not narrow; - the exact `data` type changes with `code` (for example NOT_FOUND vs VALIDATION_ERROR); -- if metadata is approved here, `meta.access.authentication` survives builder → procedure → client - type inspection, and removing any link makes the fixture fail. +- `typeof baseContract['~orpc']['meta']` is exactly `Record` rather than `any` or + `unknown`, proving the fourth generic slot was retained without assigning metadata semantics. + +The metadata probe names no production vocabulary: it compares the existing `~orpc.meta` type to +`Record` in the compile-only fixture. It does not define fields, initialize metadata, +export a type, or claim the later #1466 metadata flow is proven. + +```ts +type IsAny = 0 extends (1 & T) ? true : false; +type Equal = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? true + : false; +type Assert = T; + +type BaseMeta = typeof baseContract['~orpc']['meta']; +type _MetaIsNotAny = Assert, false>>; +type _MetaSlotPreserved = Assert>>; +``` The test is meaningful because it is red at this base for the issue's named `never` erasure. A new test written only after the implementation or one using ambient re-declarations is insufficient. -## Commit slices (blocked until rulings and PLAN-EVAL PASS) +## Commit slices (blocked until fresh Tier-A review and PLAN-EVAL PASS) -| # | What the slice proves | Files | Proving gate | -| - | ----------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------- | -| 1 | RED fixture uses real exports and fails with the named TS2339; then the four-generic builder retains exact error keys (and approved metadata owner). | `contract-primitives.ts`, `readme-doctest_test.ts`; plus `src/public/mod.ts` only if approved | Focused structured test/check; negative type assertions | -| 2 | A real service method carries its contract error into `safe()`, whose result and `isDefinedError` preserve literal code/data and reject plain errors. | `errors.ts`, `readme-doctest_test.ts`; plus required `ports/service-client.ts` if approved | Focused structured test/check; exact base RED turns green | -| 3 | Both published pages tell one consistent, compile-accurate error story, including non-defined failure handling. | the two authorized docs pages | `docs-source-format`, `docs-accuracy`, doctest | -| 4 | Published surfaces remain curated and all package gates are recorded without laundering known reds. | run artifacts only | full selected validation set below | +| # | What the slice proves | Files | Proving gate | +| - | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------- | +| 1 | RED fixture uses real exports and fails with TS2339; then the four-generic builder retains all six error keys and the empty fourth metadata slot without defining metadata vocabulary. | `contract-primitives.ts`, `readme-doctest_test.ts` | Focused structured test/check; negative type assertions | +| 2 | A real service method carries its six-literal contract error union into `safe()`; `SafeResult`/`isDefinedError` preserve code-specific data, reject plain errors, and the client port remains compatible with existing consumers. | `errors.ts`, `ports/service-client.ts`, `readme-doctest_test.ts` | Focused structured test/check; exact base RED turns green | +| 3 | Both published pages tell one consistent, compile-accurate error story, including non-defined failure handling. | `sdk.md`, `how-to/discover-services.md` | `docs-source-format`, `docs-accuracy`, doctest | +| 4 | Published surfaces remain curated and all package gates are recorded without laundering known reds. | run artifacts only | full selected validation set below | ## Validation plan -Structured wrappers are the verdict source. Exact arguments may be refined after the scope ruling, -but the gate set is locked: +Structured wrappers are the verdict source. The gate set is locked; command filters may be +mechanically finalized after PLAN-EVAL but cannot expand the six-path edit ceiling: | Order | Gate | Planned command/evidence | Expected result | | ----- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | 1 | RED | Structured focused test/check on the real-surface fixture before implementation | FAIL with TS2339 `code` on `never`; record once | | 2 | Check | `.llm/tools/run-deno-check.ts` scoped to affected TS, with `--unstable-kv` | PASS after implementation | -| 3 | Test | `.llm/tools/run-deno-test.ts -- --allow-all packages/sdk/tests/readme-doctest_test.ts` plus affected package tests selected after ruling | PASS; if `typed-queue_test.ts` hits #1667 `expected 1, got 2`, report once and do not rerun | +| 3 | Test | `.llm/tools/run-deno-test.ts -- --allow-all packages/sdk/tests/readme-doctest_test.ts` plus affected package consumer tests | PASS; if `typed-queue_test.ts` hits #1667 `expected 1, got 2`, report once and do not rerun | | 4 | Lint | `.llm/tools/run-deno-lint.ts` scoped to affected package TS | PASS | | 5 | Format | `.llm/tools/run-deno-fmt.ts --ext ts,tsx` scoped to affected package TS | PASS | | 6 | Quality | `deno task quality:scan` | PASS for the leaf diff | @@ -244,7 +248,7 @@ No Aspire, Docker, runtime lease, or `e2e:cli` gate is applicable or permitted. | `DefinedError.defined: true` is incompatible with upstream boolean marker | Normalize by intersection only after extracting the original upstream-shaped union; retain original literal code/data. | | Public failure `null` → `undefined` breaks consumers | Declare breaking change explicitly, search construction/assertions repo-wide, document migration, release in 0.0.7 rather than calling it patch-compatible. | | Tight builder annotation breaks CRUD/plugin/handler inference | Run named internal/external consumer checks from research §3 and preserve default generics. | -| Metadata work duplicates #1466 | Hard stop pending topic-orchestrator ownership ruling. | +| Empty metadata slot is accidentally omitted or widened | Explicit four-generic annotation plus an exact `Record` compile assertion; no metadata fields or exports. | | One corrected sentence leaves false surrounding prose | Apply every disposition in research §4 and run docs accuracy over whole pages. | | JSR helper false-positive banner is reported as new slow type | Use raw publish output as authority and record helper vs raw distinction. | @@ -254,8 +258,7 @@ No Aspire, Docker, runtime lease, or `e2e:cli` gate is applicable or permitted. algebra. - Avoid AP-14: do not re-export upstream helpers/types; only use an upstream type internally where the hidden promise error marker requires identity. -- Avoid AP-22: no new barrel; amend only the existing curated contracts barrel if metadata is - approved. +- Avoid AP-22: no new barrel and no change to the curated contracts barrel. - Avoid AP-25: all contract/error helpers remain side-effect-free. - Required Archetype-1 gates: F-1, F-5, F-6, F-7, F-8, F-10, F-11, F-12, F-14, F-15, F-16, F-17, F-18, F-19, plus package `quality:scan`, `arch:check`, docs overlay gates, publish dry-run, and @@ -269,10 +272,13 @@ No Aspire, Docker, runtime lease, or `e2e:cli` gate is applicable or permitted. and must not be reported as green or as leaf regressions. - No new error codes, client construction seam (#1349), server raising behavior (#1263), oRPC v2, type-soundness sweep (#1278), plugin-local base-contract repair, or generated reference rewrite. +- Metadata definition, initialization, export, and semantic flow remain #1466 scope. Stale + contracts/benchmark prose is tracked follow-up debt; neither category may consume a seventh path + in this leaf without a fresh ruling. ## Plan-Gate state -`PLAN-EVAL` is selected and is a hard stop. Before the topic orchestrator launches it, the two “must -resolve now” questions must be answered: required `service-client.ts` scope and metadata/#1466 -ownership (including barrel scope). Until then this plan is complete as a rescope report but is not -implementable. +The coordinator ruling resolves every “must resolve now” decision. Stale out-of-scope prose and +#1466 metadata semantics are explicitly safe to defer. A fresh Tier-A reviews this amended head; +then `PLAN-EVAL` runs in a separate session and remains a hard stop. No implementation may begin +until that evaluator returns `PASS`. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/research.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/research.md index f40613798..0a7c2de0f 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/research.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/research.md @@ -8,17 +8,17 @@ inference after metadata initialization (`rfcs/0001-sdk-client-contributions.md:1317-1333`) and keeps contribution/preparation failures outside the contract-defined channel (`rfcs/0001-sdk-client-contributions.md:1032-1088`). -- Normative for the builder: RFC 0001 requires an explicit publishable annotation preserving both - the concrete common error map and `NetScriptProcedureMeta`, and rejects the current +- Normative for the builder: RFC 0001 requires an explicit publishable annotation that preserves the + concrete common error map and the fourth metadata generic, and rejects the current `ReturnType` spelling (`rfcs/0001-sdk-client-contributions.md:347-370`). -- Normative for this lane: the slice brief says #1350 owns procedure-metadata preservation and - limits changes to five declared files. This conflicts with current GitHub state: #1466 is an open - child titled “define NetScriptProcedureMeta without erasing contract errors,” and #1350's only - existing comment says metadata initialization is not owned by #1350. RFC 0001 itself records that - Stage 0 had to choose #1350 or a dependent child - (`rfcs/0001-sdk-client-contributions.md:1267-1277`). The brief is treated as the latest - instruction for planning, but the contradictory live ownership cannot be verified and needs the - topic orchestrator's ruling before implementation. +- Normative coordinator amendment on 2026-08-15: #1350 owns only the literal-preserving error + repair. #1466 owns procedure-metadata definition, initialization, and export. This supersedes the + earlier brief wording and resolves the conflict recorded in the original research. This leaf keeps + the fourth generic explicitly as `Record` so #1466 can replace it later, but does + not define, export, or semantically prove any metadata vocabulary. +- Normative scope ceiling: the original five paths plus `packages/sdk/src/ports/service-client.ts` + are the exact six authorized product/test/docs paths. Any seventh path requires a fresh + coordinator ruling. ## 1. Current error path and exact loss points @@ -128,13 +128,13 @@ rg -n '\bServiceClient(Method|Shape|<)|Procedure(Output|Input)FromNode' \ ### Outside `packages/sdk` -| Consumer | Evidence | Consequence | -| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `@netscript/fresh` diagnostics | `packages/fresh/src/diagnostics/error/extract.ts:1-39` | Imports `isDefinedError`, then manually casts code/status. It must still compile and preserve runtime detection; it is the only non-SDK package source importing the helper. | -| `@netscript/contracts/crud` | `packages/contracts/crud/create-crud-contract.ts:29-44,109-117,350-379` | Directly consumes public `baseContract` and `BaseContractRoute`; exact error/meta generics must remain assignable. | -| Workers health soundness test | `plugins/workers/services/src/routers/health-soundness_test.ts:3-53` | Asserts `BaseContractRoute` handler inference; metadata/error tightening must not regress input/output soundness. | -| CLI scaffold/template assertions | `packages/cli/e2e/tests/application/gates/generated-router-template_test.ts:35-41`; `packages/cli/src/kernel/adapters/contracts/contract-source.ts:63-77`; `packages/cli/src/public/features/plugins/new/new-plugin-use-case.ts:390-416` | Some generated contracts import root `baseContract`; the parser recognizes its route chain. No template change is planned. | -| Benchmark/reference/docs consumers | `packages/bench/tasks/t1-storefront-api/rubric.md:16`; `packages/bench/tasks/t1-storefront-api/reference/README.md:45-47`; `packages/bench/tasks/t1-storefront-api/reference/netscript/router.ts:7-8`; `packages/contracts/README.md:13-76` | Several statements explicitly describe the current erasure. They become stale after repair but are outside the declared surface, creating another documentation rescope question for the orchestrator. | +| Consumer | Evidence | Consequence | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `@netscript/fresh` diagnostics | `packages/fresh/src/diagnostics/error/extract.ts:1-39` | Imports `isDefinedError`, then manually casts code/status. It must still compile and preserve runtime detection; it is the only non-SDK package source importing the helper. | +| `@netscript/contracts/crud` | `packages/contracts/crud/create-crud-contract.ts:29-44,109-117,350-379` | Directly consumes public `baseContract` and `BaseContractRoute`; exact error/meta generics must remain assignable. | +| Workers health soundness test | `plugins/workers/services/src/routers/health-soundness_test.ts:3-53` | Asserts `BaseContractRoute` handler inference; metadata/error tightening must not regress input/output soundness. | +| CLI scaffold/template assertions | `packages/cli/e2e/tests/application/gates/generated-router-template_test.ts:35-41`; `packages/cli/src/kernel/adapters/contracts/contract-source.ts:63-77`; `packages/cli/src/public/features/plugins/new/new-plugin-use-case.ts:390-416` | Some generated contracts import root `baseContract`; the parser recognizes its route chain. No template change is planned. | +| Benchmark/reference/docs consumers | `packages/bench/tasks/t1-storefront-api/rubric.md:16`; `packages/bench/tasks/t1-storefront-api/reference/README.md:45-47`; `packages/bench/tasks/t1-storefront-api/reference/netscript/router.ts:7-8`; `packages/contracts/README.md:13-76` | Several statements explicitly describe the current erasure. They become stale after repair but are tracked follow-up debt outside this leaf's exact six-path ceiling. | First-party plugin-core files found by the base-contract search define their own local `baseContract: ReturnType` rather than importing `@netscript/contracts` @@ -196,20 +196,16 @@ unless `packages/sdk/src/ports/service-client.ts` is changed. red. Its helper also reports a slow-type warning by counting the “Checking for slow types” banner; raw `deno publish --dry-run --allow-dirty` exits 0 with no actual slow-type diagnostic. Record the raw result as authority per `netscript-tools`. -- No new entrypoint is needed for the error repair. If metadata is in this leaf, RFC 0001 requires - new root exports, which cannot be achieved within the declared files because the curated barrel is - `packages/contracts/src/public/mod.ts:1-6`. - -## Open questions requiring a ruling - -1. **Scope expansion:** authorize `packages/sdk/src/ports/service-client.ts`. Without it, exact - procedure error inference is impossible and the accepted error contract cannot be met. -2. **Metadata owner:** does this brief supersede live #1466 and #1350's existing maintainer comment? - If yes, authorize `packages/contracts/src/public/mod.ts` (and likely the SDK contract metadata - port/type surface) so the RFC-mandated root metadata exports and propagation can exist. If no, - #1350 should preserve the fourth `ContractBuilder` generic as `Record` and leave - initialization/export/propagation to #1466. -3. **Stale but out-of-scope published prose:** authorize updates to `packages/contracts/README.md` - and benchmark reference prose that explicitly says the root base contract is erased, or accept a - tracked docs follow-up. Leaving those statements unchanged would violate the brief's own - surrounding-narrative rule. +- No new entrypoint is needed or authorized. `packages/contracts/src/public/mod.ts` is explicitly + denied for this leaf; there is no metadata definition/export branch in the approved plan. + +## Coordinator ruling and remaining deferred scope + +1. **Resolved:** `packages/sdk/src/ports/service-client.ts` is the authorized sixth path. It is + required to preserve the real promise error channel. +2. **Resolved:** #1466 owns metadata definition, initialization, and export. #1350 preserves only + the existing fourth `ContractBuilder` generic as `Record` and makes no metadata + semantic claim. +3. **Safe to defer:** `packages/contracts/README.md` and benchmark reference prose remain tracked + follow-up debt. They are not authorized here. Any attempt to edit either location—or any other + seventh product/test/docs path—is a rescope requiring a fresh ruling. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/supervisor.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/supervisor.md index b464c38ab..190c0fc23 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/supervisor.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/supervisor.md @@ -23,6 +23,8 @@ Written at run start per `workflow/lane-policy.md` § Supervisor identity. ## Owner constraints - Plan-first. This session must not implement or launch an evaluator. -- The declared product/doc change surface is exactly five files. Any required sixth file is a - rescope and stops implementation pending a topic-orchestrator ruling. +- The product/test/docs surface is exactly the six paths enumerated in `plan.md`; any seventh path + is a rescope requiring a fresh coordinator ruling. +- #1466 owns all metadata vocabulary, initialization, and exports. This leaf preserves only the + existing empty fourth generic slot and must not touch the contracts public barrel. - No Aspire, Docker, `e2e:cli`, runtime lease, lock deletion, cache deletion, or reload. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md index 780572476..0ba2ad81f 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md @@ -17,8 +17,8 @@ error/meta generics corrected in place. - Existing: `DefinedError`, `SafeSuccess`, `SafeFailure`, `SafeResult`, `safe`, `isDefinedError`, and `ServiceClient*` — exact error channel corrected in place. -- Conditional on ownership ruling: `NetScriptAuthenticationRequirement` and `NetScriptProcedureMeta` - become new contracts root exports because RFC 0001 requires them. +- Existing contract builder metadata slot stays explicitly `Record`; no metadata + vocabulary, initialization, or export is introduced. - No internal helper, adapter, upstream module, or additional subpath becomes public. ### Domain vocabulary @@ -30,7 +30,7 @@ adding the runtime-proven `defined: true` marker. - `SafeResult` — success, non-defined failure, and defined failure discriminated by `isSuccess`/`isDefined`. -- `NetScriptProcedureMeta` — conditional on the ownership ruling; exact RFC 0001 shape. +- No metadata domain vocabulary in this leaf; #1466 owns it. ### Ports @@ -44,7 +44,7 @@ ### Commit slices -See `plan.md` “Commit slices.” All implementation slices are blocked pending scope ruling and +See `plan.md` “Commit slices.” All implementation slices are blocked pending fresh Tier-A review and PLAN-EVAL PASS. ### Deferred scope @@ -67,6 +67,7 @@ second code union is hand-maintained. | 2026-08-15 | Plan | RED | Executed TS2339 `error.code` on `never` with `deno eval --check --unstable-kv`. | | 2026-08-15 | Plan | Consumer scan | Executed whole-repo searches; identified `@netscript/fresh`, CRUD, query/desktop/type fixtures, CLI/template, docs, and baseline consumers. | | 2026-08-15 | Plan | Rescope | Required out-of-scope `service-client.ts`; conditional contracts barrel; live #1466 ownership conflict. Stopped all product work. | +| 2026-08-15 | Plan | Amendment | Coordinator authorized the sixth client-port path, assigned metadata vocabulary to #1466, denied the barrel, and locked six paths. | ## Gate results @@ -79,5 +80,5 @@ second code union is hand-maintained. ## Handoff -Topic orchestrator should rule first on the two scope/ownership questions in `plan.md`, then send -the resolved plan to a separate PLAN-EVAL session. This generator must not self-evaluate. +Fresh Tier-A reviews this amended head first; a separate PLAN-EVAL session follows only after that +review. This generator must not self-evaluate or implement before `PASS`. From f76a3c45bce42cab81c2b481d4abf03be1104bb0 Mon Sep 17 00:00:00 2001 From: Rickylabs Date: Sat, 15 Aug 2026 22:19:25 +0200 Subject: [PATCH 3/8] docs(harness): PLAN-EVAL PASS for #1350 typed error plan Separate-session Fable 5 medium evaluation of PR #1671 plan head 2fa2f71dc. Verdict PASS with five non-blocking advisories. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_015RuDy1h3UiCkLzo1PLk5Sc --- .../plan-eval.md | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 .llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan-eval.md diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan-eval.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan-eval.md new file mode 100644 index 000000000..d4c9d2acf --- /dev/null +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan-eval.md @@ -0,0 +1,196 @@ +# PLAN-EVAL — fix-sdk-typed-error-channel--0.0.7-wave1 + +- Plan evaluator session: Claude bg session `50898ac7-6e79-4f31-ae18-694bb36b7c79` · 2026-08-15 +- Run: `fix-sdk-typed-error-channel--0.0.7-wave1` · PR #1671 (draft) · issue #1350 +- Surface / archetype: `packages/contracts` + `packages/sdk` published surface · Archetype 1 slice +- Scope overlays: `docs` + +## Identity (Step 0) + +| Field | Observed | +| --- | --- | +| Session PID / bridge | pid `636411` (`~/.claude/sessions/636411.json`), `bridgeSessionId` `session_015RuDy1h3UiCkLzo1PLk5Sc`, Remote Control `https://claude.ai/code/session_015RuDy1h3UiCkLzo1PLk5Sc` | +| Job | `50898ac7`; `respawnFlags` = `--permission-mode bypassPermissions --remote-control --name "NetScript 0.0.7 #1671 PLAN-EVAL" --effort medium --model claude-fable-5` | +| Requested route | `formal_plan_evaluation` → native Claude · `claude-fable-5` · medium · Remote Control (`lane-policy.md:45`, `routing-policy.ts:326-332`) | +| Observed route | Claude · `claude-fable-5` · medium · Remote Control — **matched** | +| Generator separation | Plan author is Codex `01a006f3-ae2d-7941-bd17-2ac71dd3d0f0` (`supervisor.md`); this is a separate opposite-family session — invariant holds | +| cwd | `/home/codex/repos/netscript-007-leaf-typed-error` | +| `git rev-parse HEAD` | `2fa2f71dc5b498c16221461439e53b9f5dc1d5d5` == immutable head; `gh pr view 1671 --json headRefOid` == same | + +## Step 1 — phase invariant + +```text +$ git diff --name-only 0ef48c2ec..HEAD +.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/codex-thread-ids.md +.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md +.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md +.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan.md +.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/research.md +.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/supervisor.md +.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md +``` + +`.llm/runs/` only; working tree clean. **PASS.** + +## Step 2 — ownership boundary (re-derived from live records) + +- #1348 body header (fetched via `gh issue view 1348`): "Stage 0 is accepted; #1466 owns procedure + metadata; #1349–#1353 are amended below." The older `NetScriptProcedureMeta.policy` prose is + declared historical/non-normative in that same header. +- #1350 comment `5227724542` (rickylabs, 2026-08-08): #1350 "remains focused on the + `safe()`/literal-preserving error repair and must land the shared four-generic `ContractBuilder` + spelling … It does **not** silently own `NetScriptProcedureMeta` initialization." +- #1466 (OPEN, milestone 0.0.7): "define NetScriptProcedureMeta without erasing contract errors" — + owns public shape, propagation to clients/generated clients/query factories, docs. +- `rfcs/0001-sdk-client-contributions.md:1269-1272` stage table: 1a (#1350) "Preserve the concrete + base error map and client error channel exactly as filed"; 1b metadata "owner selected in Stage 0" + → #1466 per #1348 header. `:347-370` requires the explicit four-generic annotation and forbids + `ReturnType`. + +Plan judgement: `plan.md` "Locked error contract" pins the builder to +`ContractBuilder, typeof commonErrorMap>, Record>` +and states "assigns no fields or semantics to [the fourth slot]"; "What becomes public" says no +metadata type/field/initialization/export. The only metadata proof is the compile-only +`Equal>` guard, which I executed at base — it is **already green at +base** (see Step 5), so it is a regression guard, not an acceptance claim depending on #1466. No +plan acceptance item requires #1466. **PASS.** + +Advisory (non-blocking, close-gate): #1350's own acceptance boxes 2–4 still literally mention "the +RFC 0001 procedure-metadata type … through … client inference, and handler inference". The PR body +carries `Closes #1350`; at IMPL/close the PR must state explicitly which boxes are satisfied under +comment `5227724542` (slot preserved, no vocabulary) and that metadata *semantics* are #1466 — do not +tick metadata boxes. (I did not touch #1348/#1466/#1350.) + +## Step 3 — scope ceiling + +- `plan.md` "Exact six-path ceiling" lists exactly the six authorized paths; "A seventh … is a + rescope requiring a fresh coordinator ruling"; `public/mod.ts`, `contracts/README.md`, bench files + named as not authorized. `research.md:199-200`: "`packages/contracts/src/public/mod.ts` is + explicitly denied for this leaf; there is no metadata definition/export branch." `drift.md` + entry 4 "Denied: The contracts public barrel". No conditional branch survives. **Confirmed.** +- Is `ports/service-client.ts` sufficient end-to-end? Executed probe (transient file under + `packages/sdk/tests/`, deleted; tree clean): + - `createORPCClient(link) as { list: (input) => ClientPromiseResult> }` + compiles → the runtime cast in `packages/sdk/src/client/service-client.ts:65` stays comparable + once `ServiceClientMethod` carries `TError`; that file does **not** need to change. **Confirmed + sufficient** in type space (`errors.ts` extraction + port `TError` derivation). + - `service-query-utils.ts:161` `call: ServiceClientMethod` and + `tests/type-fixtures/sdk-client-contributions-rfc_type.ts:194-198` remain assignable iff the + added error generic is defaulted — the plan's compatibility-default risk row covers it. + - `rg "errors\.[A-Z_]+\("` across `packages plugins` finds only `errors.INTERNAL(` in a plugin-core + comment; no baseContract handler uses an undeclared code, so tightening the error map does not + ripple into a seventh path. +- Hidden-seventh-path check: `packages/sdk/deno.json` maps **no `zod`** (executed: a probe + `import { z } from 'zod'` under `packages/sdk/tests` → `TS2307`). The plan's fixture "builds a + real route from `baseContract`" and needs schemas; it must use `@netscript/contracts`-exported + schemas (`CursorPaginationInputSchema`, `SuccessSchema`, `NotFoundErrorSchema` — resolvable, probe + compiled) rather than adding a `zod` mapping. See advisory A2. + +**PASS.** + +## Step 4 — breaking-change honesty + +Upstream pinned `@orpc/client@1.14.6` (`dist/index.d.mts:139-161`): `SafeResult` = success arm | +`[Exclude, undefined, false, false]` | `[Extract, undefined, +true, false]`; `safe(promise: ClientPromiseResult<…>)`. Current +NetScript (`packages/sdk/src/client/errors.ts:39-49,86`): one failure arm, `data: null`, +`isDefined: boolean`, `SafeResult`, `safe(PromiseLike)`. + +- The plan's declared break (`null`→`undefined` failure `data`, one arm→two literal arms, tighter + `baseContract` key space, `TError` default `unknown`→`ThrowableError`) is **correct and not + over-declared**. Executed repo search (`SafeFailure|data === null` over packages/plugins/docs) + finds only `errors.ts`, `client/mod.ts` and the generated reference table (names only) — matches + research §3. +- Blast radius, repo-wide consumer search (research §3, executed `rg` commands), migration note, + 0.0.7-minor placement are all present. **Confirmed.** +- One understatement (advisory A3): the locked shape writes `type SafeFailure` with **no + default**, whereas today `SafeFailure` is legal to reference bare. That is an + extra, avoidable break not listed; keep a default (`= ThrowableError`) unless dropping it is + intended and then declare it. + +**PASS.** + +## Step 5 — proof and docs + +Executed at base with the plan's exact snippet against real exports (`baseContract`, +`ServiceClient`, `safe`, `isDefinedError`): + +```text +TS18046 [ERROR]: 'result.error' is of type 'unknown'. ← plan snippet: result.isDefined && result.error.code +TS2339 [ERROR]: Property 'code' does not exist on type 'never'. ← isDefinedError(error) path (#1350's probe) +keyof typeof baseContract['~orpc']['errorMap'] = 'NOT_DECLARED' ← accepted at base (no error) +Equal> / IsAny false ← both GREEN at base +Equal> / unknown ← RED at base (lines 8,9) +``` + +- The RED is genuine and for #1350's reason (`TError` erased → `unknown` → `never` after + `Extract`). However the plan's validation row 1 says the *plan snippet* fails with "TS2339 `code` + on `never`"; the snippet as written fails with **TS18046 on `unknown`** — the `never` diagnostic + only appears via `isDefinedError`. Advisory A1: record both diagnostics as the expected RED. +- Six literal codes + code-specific `data` + `NOT_DECLARED` negative + non-oRPC `Error` in the + `isDefined:false` arm do prove the acceptance; the meta-slot `Equal` is a guard already green at + base — correctly framed as "retained", not proven-new. +- Docs: grep of both pages for `isDefinedError|safe\(|error\.(code|data)|typed|inferred|drift` + shows every error-story location is inside the disposition tables (`sdk.md` 12-20, 31-38, 58-70, + 113-114, 196-198; `discover-services.md` 9-14, 96-101, 114-115, 135-154, 205-229). Remaining + "typed client" mentions (`sdk.md:237`, `discover-services.md:20,26-27,37-38,243,249,279`) are + input/output claims that stay true; no overcorrection planned. +- Out-of-scope prose: `packages/bench/.../reference/README.md:45-47` and `router.ts:7-8` say + `baseContract` "is type-erased (`{ '~orpc': any }`)" — already stale today, becomes more so; + `rubric.md:16` stays true. `packages/contracts/README.md:13-76` does **not** describe erasure + (it says the error map "is already applied" — true before and after); research §3 overstates + that file. Deferring is honest, but "tracked follow-up debt" currently has no home: + `.llm/harness/debt/arch-debt.md` has no entry (grep `bench|contracts/README|1350` → none). See A4. + +**PASS.** + +## Checklist results + +| Plan-Gate item | Result | Evidence / location | +| --------------------------------------- | ------ | ------------------- | +| Research present and current | PASS | `research.md` "Re-baseline and authority" against `0ef48c2ec`; spot-checked §1 loss points at `contract-primitives.ts:81,98`, `errors.ts:39-49,75,86`, `ports/service-client.ts:168-171`, `client/service-client.ts:65` — all accurate | +| Decisions locked | PASS | `plan.md` "Locked error contract", "Source authority by decision" | +| Open-decision sweep | PASS | `plan.md` "Plan-Gate state"; evaluator sweep below found none that force rework | +| Commit slices (< 30, gate + files each) | PASS | 4 slices, each with proof/files/gate | +| Risk register | PASS | 7 rows with mitigations | +| Gate set selected | PASS | Archetype-1 F-set matches `archetype-gate-matrix.md:22-40`; docs overlay; 12-row validation table | +| Deferred scope explicit | PASS | `plan.md` "Debt and deferred scope" | +| jsr-audit surface scan (pkg/plugin) | PASS | `research.md` "JSR/publish surface scan" (raw dry-run authority, F-DOCT-5 known red) | + +## Tier-A claims tested + +| Claim | Verdict | +| --- | --- | +| Head carries no product code | Confirmed | +| #1466 owns metadata; #1350 keeps only the empty fourth slot | Confirmed against #1348 header, #1350 comment 5227724542, #1466 body, RFC stage table | +| Six paths exact; `public/mod.ts` denied, not conditional | Confirmed | +| `ports/service-client.ts` is the real loss point and is sufficient | Confirmed by cast-comparability probe | +| Breaking, not patch-level | Confirmed (plus one unlisted default-drop, A3) | +| RED fails at base for #1350's reason | Confirmed; diagnostic text differs from plan (A1) | +| Meta-slot assertion proves slot retained without vocabulary | Confirmed (already green at base — guard) | +| Stale README/bench prose honestly deferred | Confirmed for bench; research overstates contracts README (A4) | + +## Open-decision sweep (evaluator-run) + +None that would force rework. Two implementation choices are left open but are safe to defer: (a) +whether the port derives `TError` via upstream `ErrorFromErrorMap` or a NetScript-owned structural +`DefinedError` union (both work with the structural `DefinedErrorLike` extraction); +(b) whether `safe()`'s parameter names upstream `ClientPromiseResult` or a NetScript-owned +`Promise & { __error?: { type: TError } }` alias (avoids an upstream type in the published d.ts; +AP-14 concerns re-export, so either is permitted). + +## Verdict + +**`PASS`** — implementation may begin. + +### Advisories (non-blocking) + +- **A1 RED diagnostic.** Record the expected RED as *both* `TS18046 'result.error' is of type + 'unknown'` (plan snippet) and `TS2339 … 'never'` (`isDefinedError` path). Same defect, two texts. +- **A2 No `zod` in `packages/sdk`.** Build the fixture route from `@netscript/contracts`-exported + schemas; adding a `zod` mapping to `packages/sdk/deno.json` would be a seventh path. +- **A3 `SafeFailure` default.** Keep `SafeFailure` (or declare the dropped + default as part of the break). +- **A4 Debt home.** In slice 4 add an `arch-debt.md` (or issue) entry for the bench prose so + "tracked" is literal; correct research §3's claim about `packages/contracts/README.md`. +- **A5 Close gate.** Do not tick #1350 metadata boxes; state the split under comment 5227724542. From dc034d680b53c2845e9b82f73c6c709f2c51e2b3 Mon Sep 17 00:00:00 2001 From: Rickylabs Date: Sat, 15 Aug 2026 22:31:02 +0200 Subject: [PATCH 4/8] fix(contracts): preserve exact base error map --- .../context-pack.md | 53 +++--- .../drift.md | 10 + .../plan.md | 53 +++--- .../research.md | 38 ++-- .../worklog.md | 176 +++++++++++++++++- .../src/application/contract-primitives.ts | 48 ++++- packages/sdk/tests/readme-doctest_test.ts | 49 +++++ 7 files changed, 351 insertions(+), 76 deletions(-) diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md index 2b5709fa9..cb7f4dcc1 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md @@ -2,21 +2,20 @@ ## Run Metadata -| Field | Value | -| -------------- | -------------------------------------------------------------- | -| Run ID | `fix-sdk-typed-error-channel--0.0.7-wave1` | -| Branch | `fix/sdk-typed-error-channel` | -| Current phase | `plan` — amended; awaiting fresh Tier-A review, then PLAN-EVAL | -| Archetype | `1 — Small Contract` slice | -| Scope overlays | `docs` | +| Field | Value | +| -------------- | ------------------------------------------------------------ | +| Run ID | `fix-sdk-typed-error-channel--0.0.7-wave1` | +| Branch | `fix/sdk-typed-error-channel` | +| Current phase | `implementation` — S1 complete; awaiting fresh Tier-A review | +| Archetype | `1 — Small Contract` slice | +| Scope overlays | `docs` | ## Current state -Research and a concrete error contract are complete. No product or docs implementation exists. The -base RED is TS2339 because `safe()` erases `TError` and `isDefinedError()` narrows the result to -`never`. The coordinator authorized `packages/sdk/src/ports/service-client.ts` as the sixth and -final product path. #1466 owns metadata definition/export; this leaf keeps only the existing fourth -generic slot as `Record` and makes no metadata semantic claim. +PLAN-EVAL is terminal PASS at evaluator commit `f76a3c45b`. S1 records both real-export REDs +(TS18046 `unknown`, TS2339 `never`), then makes the contracts builder carry the exact six-key error +map and explicit empty fourth slot. S2 error-channel files remain untouched. #1466 owns metadata +definition/export; S1 introduces no metadata vocabulary or acceptance claim. ## Completed @@ -25,12 +24,14 @@ generic slot as `Record` and makes no metadata semantic claim. - Focused source map and executed whole-repo consumer search. - Exact RED and JSR/publish baseline inspection. - Research, plan, design, risk/gate set, docs dispositions, and rescope report. +- Separate-session PLAN-EVAL PASS with advisories A1-A5 incorporated. +- S1 builder annotation and real-export regression fixture using contracts-exported schemas. +- S1 structured check/test/lint/format gates. ## Next steps -1. Fresh Tier-A reviews the amended head. -2. A separate session performs PLAN-EVAL after that review passes. -3. Only after PLAN-EVAL PASS may implementation begin. +1. Fresh Tier-A reviews the landed S1 head and structured evidence. +2. Stop. Slice 2 requires fresh coordinator authorization after that review. ## Key decisions @@ -44,24 +45,28 @@ generic slot as `Record` and makes no metadata semantic claim. ## Files changed -Only `.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/` plan-phase artifacts. The -launcher-created `codex-thread-ids.md` is preserved. No declared product/doc file was modified. +- `packages/contracts/src/application/contract-primitives.ts` +- `packages/sdk/tests/readme-doctest_test.ts` +- existing files under `.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/` + +No S2-S4 file was modified. ## Gates -- Plan artifacts: ready for topic-orchestrator ruling. -- PLAN-EVAL: not launched by instruction. -- Implementation/static/docs/fitness gates: not run. +- PLAN-EVAL: PASS (`plan-eval.md`, `f76a3c45b`). +- S1 RED: expected FAIL with exactly TS18046 and TS2339, captured once. +- S1 focused check/test/lint/format: PASS; full JSON is in `worklog.md`. +- Root quality/publish/docs/JSR gates: not run; reserved for the final slice. - Baseline JSR/publish inspection is recorded in `research.md` and `worklog.md`. ## Open questions -- None that can force implementation rework. Stale contracts/benchmark prose is tracked follow-up - debt outside the six-path ceiling. +- None for S1. Benchmark reference prose is coordinator-owned follow-up debt; the contracts README + correction is recorded in `research.md` without editing that seventh path. ## Drift and debt -- Drift: the earlier scope and ownership conflicts are resolved by the coordinator amendment; see - the append-only resolution in `drift.md`. +- Drift: earlier scope/ownership conflicts and the README research correction are recorded in the + append-only `drift.md`. - Debt: no new debt accepted. - Commit trail: draft PR commit list plus phase comment; no `commits.md`. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md index 9af335447..709de1356 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md @@ -52,3 +52,13 @@ - **Deferred:** Stale contracts/benchmark prose remains tracked follow-up debt outside this leaf. - **Phase effect:** The plan has no remaining must-resolve decision, but fresh Tier-A review and a separate PLAN-EVAL `PASS` remain hard stops before implementation. + +## 2026-08-15 — PLAN-EVAL corrects the out-of-scope prose classification + +- **What:** Original research grouped `packages/contracts/README.md` with benchmark prose that + describes the old erasure. +- **Actual:** The contracts README says the common error map is applied, which remains true. Only + the benchmark reference README/router contain the stale erasure claim. +- **Action:** Corrected existing research/plan/context artifacts. No new file or debt entry was + created; the coordinator owns any later issue for the benchmark prose. +- **Scope:** Both locations remain outside the exact six-path ceiling; no product/docs edit here. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan.md index 2c64877ed..842cdc6ac 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan.md @@ -6,7 +6,7 @@ | -------------- | ----------------------------------------------------------------------------------------------------------- | | Run ID | `fix-sdk-typed-error-channel--0.0.7-wave1` | | Branch | `fix/sdk-typed-error-channel` | -| Phase | `plan` — coordinator-amended; awaiting fresh Tier-A review, then separate PLAN-EVAL | +| Phase | `implementation` — PLAN-EVAL PASS; S1 authorized and isolated | | Target | `packages/contracts`, `packages/sdk`, and two published docs pages | | Archetype | `1 — Small Contract` for this bounded contract slice (the SDK package remains doctrine Archetype 2 overall) | | Scope overlays | `docs` | @@ -49,7 +49,7 @@ type DefinedErrorLike = Error & { type NarrowDefined = Extract & DefinedError; -type SafeFailure = +type SafeFailure = | ([Exclude, undefined, false, false] & { error: Exclude; data: undefined; @@ -145,8 +145,9 @@ These are the complete authorized product/test/docs paths: This ceiling is exact. A seventh product, test, or docs path is a rescope requiring a fresh coordinator ruling. In particular, `packages/contracts/src/public/mod.ts`, -`packages/contracts/README.md`, and the benchmark reference files are not authorized. The stale -prose remains tracked follow-up debt rather than being absorbed into this leaf. +`packages/contracts/README.md`, and the benchmark reference files are not authorized. Only the +benchmark reference prose is confirmed stale and remains coordinator-owned follow-up debt; the +contracts README's statement that the error map is applied remains true. ## Planned docs dispositions @@ -180,6 +181,7 @@ if (!result.isSuccess && result.isDefined) { Expected base RED: ```text +TS18046: 'discriminated.error' is of type 'unknown'. TS2339: Property 'code' does not exist on type 'never'. ``` @@ -213,7 +215,7 @@ test written only after the implementation or one using ambient re-declarations | # | What the slice proves | Files | Proving gate | | - | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------- | -| 1 | RED fixture uses real exports and fails with TS2339; then the four-generic builder retains all six error keys and the empty fourth metadata slot without defining metadata vocabulary. | `contract-primitives.ts`, `readme-doctest_test.ts` | Focused structured test/check; negative type assertions | +| 1 | RED fixture uses real exports and fails with TS18046 plus TS2339; then the four-generic builder retains all six error keys and the empty fourth metadata slot without defining metadata vocabulary. | `contract-primitives.ts`, `readme-doctest_test.ts` | Focused structured test/check; negative type assertions | | 2 | A real service method carries its six-literal contract error union into `safe()`; `SafeResult`/`isDefinedError` preserve code-specific data, reject plain errors, and the client port remains compatible with existing consumers. | `errors.ts`, `ports/service-client.ts`, `readme-doctest_test.ts` | Focused structured test/check; exact base RED turns green | | 3 | Both published pages tell one consistent, compile-accurate error story, including non-defined failure handling. | `sdk.md`, `how-to/discover-services.md` | `docs-source-format`, `docs-accuracy`, doctest | | 4 | Published surfaces remain curated and all package gates are recorded without laundering known reds. | run artifacts only | full selected validation set below | @@ -223,20 +225,20 @@ test written only after the implementation or one using ambient re-declarations Structured wrappers are the verdict source. The gate set is locked; command filters may be mechanically finalized after PLAN-EVAL but cannot expand the six-path edit ceiling: -| Order | Gate | Planned command/evidence | Expected result | -| ----- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | -| 1 | RED | Structured focused test/check on the real-surface fixture before implementation | FAIL with TS2339 `code` on `never`; record once | -| 2 | Check | `.llm/tools/run-deno-check.ts` scoped to affected TS, with `--unstable-kv` | PASS after implementation | -| 3 | Test | `.llm/tools/run-deno-test.ts -- --allow-all packages/sdk/tests/readme-doctest_test.ts` plus affected package consumer tests | PASS; if `typed-queue_test.ts` hits #1667 `expected 1, got 2`, report once and do not rerun | -| 4 | Lint | `.llm/tools/run-deno-lint.ts` scoped to affected package TS | PASS | -| 5 | Format | `.llm/tools/run-deno-fmt.ts --ext ts,tsx` scoped to affected package TS | PASS | -| 6 | Quality | `deno task quality:scan` | PASS for the leaf diff | -| 7 | Doctrine | `deno task arch:check` | PASS for the leaf; known base findings separated | -| 8 | Public surface | base-vs-head `surface:diff` sets/signatures | Raw gate remains known-red at base; no unintended new export, only approved signature changes | -| 9 | Docs | repository `docs-source-format` and `docs-accuracy` tasks | PASS for both authorized pages | -| 10 | Doc lint | `deno task doc:lint --root packages/contracts --pretty` and SDK equivalent | Compare with pinned raw baselines; do not relabel baseline reds green | -| 11 | Publish | `deno task publish:dry-run` plus affected per-package raw dry-run where attribution is needed | PASS; contracts sanctioned oRPC slow-type information reported honestly | -| 12 | JSR | repository JSR audits for `packages/contracts` and `packages/sdk` | Report known SDK `F-DOCT-5` red; no new finding | +| Order | Gate | Planned command/evidence | Expected result | +| ----- | -------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| 1 | RED | Structured focused test/check on the real-surface fixture before implementation | FAIL once with TS18046 `unknown` and TS2339 `code` on `never` | +| 2 | Check | `.llm/tools/run-deno-check.ts` scoped to affected TS, with `--unstable-kv` | PASS after implementation | +| 3 | Test | `.llm/tools/run-deno-test.ts -- --allow-all packages/sdk/tests/readme-doctest_test.ts` plus affected package consumer tests | PASS; if `typed-queue_test.ts` hits #1667 `expected 1, got 2`, report once and do not rerun | +| 4 | Lint | `.llm/tools/run-deno-lint.ts` scoped to affected package TS | PASS | +| 5 | Format | `.llm/tools/run-deno-fmt.ts --ext ts,tsx` scoped to affected package TS | PASS | +| 6 | Quality | `deno task quality:scan` | PASS for the leaf diff | +| 7 | Doctrine | `deno task arch:check` | PASS for the leaf; known base findings separated | +| 8 | Public surface | base-vs-head `surface:diff` sets/signatures | Raw gate remains known-red at base; no unintended new export, only approved signature changes | +| 9 | Docs | repository `docs-source-format` and `docs-accuracy` tasks | PASS for both authorized pages | +| 10 | Doc lint | `deno task doc:lint --root packages/contracts --pretty` and SDK equivalent | Compare with pinned raw baselines; do not relabel baseline reds green | +| 11 | Publish | `deno task publish:dry-run` plus affected per-package raw dry-run where attribution is needed | PASS; contracts sanctioned oRPC slow-type information reported honestly | +| 12 | JSR | repository JSR audits for `packages/contracts` and `packages/sdk` | Report known SDK `F-DOCT-5` red; no new finding | No Aspire, Docker, runtime lease, or `e2e:cli` gate is applicable or permitted. @@ -272,13 +274,12 @@ No Aspire, Docker, runtime lease, or `e2e:cli` gate is applicable or permitted. and must not be reported as green or as leaf regressions. - No new error codes, client construction seam (#1349), server raising behavior (#1263), oRPC v2, type-soundness sweep (#1278), plugin-local base-contract repair, or generated reference rewrite. -- Metadata definition, initialization, export, and semantic flow remain #1466 scope. Stale - contracts/benchmark prose is tracked follow-up debt; neither category may consume a seventh path - in this leaf without a fresh ruling. +- Metadata definition, initialization, export, and semantic flow remain #1466 scope. Stale benchmark + reference prose is coordinator-owned follow-up debt; neither category may consume a seventh path + in this leaf without a fresh ruling. The contracts README is not stale on this point. ## Plan-Gate state -The coordinator ruling resolves every “must resolve now” decision. Stale out-of-scope prose and -#1466 metadata semantics are explicitly safe to defer. A fresh Tier-A reviews this amended head; -then `PLAN-EVAL` runs in a separate session and remains a hard stop. No implementation may begin -until that evaluator returns `PASS`. +The coordinator ruling resolved every “must resolve now” decision. PLAN-EVAL returned terminal +`PASS` in `plan-eval.md` at evaluator commit `f76a3c45b`. S1 alone is authorized; each landed slice +still stops for fresh Tier-A review before the coordinator may authorize the next slice. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/research.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/research.md index 0a7c2de0f..d34b6921c 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/research.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/research.md @@ -72,10 +72,18 @@ deno eval --check --unstable-kv "import { isDefinedError, safe } from './src/cli TS2339: Property 'code' does not exist on type 'never'. ``` -This RED proves the named defect because it fails only when `safe()` has erased the promise's error -type and `isDefinedError()` therefore narrows that erased value to `never`. The implementation proof -must strengthen it to use a real `baseContract` procedure and assert the exact six-code union plus -schema-specific `data`; a broad `DefinedError` would not satisfy the proof. +The S1 real-export fixture sharpened the base RED with one structured check and recorded both +diagnostics required by PLAN-EVAL: + +```text +TS18046: 'discriminated.error' is of type 'unknown'. +TS2339: Property 'code' does not exist on type 'never'. +``` + +Together they prove the named defect: direct discrimination still leaves `safe()`'s erased error as +`unknown`, while `isDefinedError()` narrows that erased value to `never`. The later S2 proof must +turn both green and assert the exact six-code union plus schema-specific `data`; a broad +`DefinedError` would not satisfy the proof. ## 2. Current contract guarantees and public surface @@ -128,13 +136,14 @@ rg -n '\bServiceClient(Method|Shape|<)|Procedure(Output|Input)FromNode' \ ### Outside `packages/sdk` -| Consumer | Evidence | Consequence | -| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `@netscript/fresh` diagnostics | `packages/fresh/src/diagnostics/error/extract.ts:1-39` | Imports `isDefinedError`, then manually casts code/status. It must still compile and preserve runtime detection; it is the only non-SDK package source importing the helper. | -| `@netscript/contracts/crud` | `packages/contracts/crud/create-crud-contract.ts:29-44,109-117,350-379` | Directly consumes public `baseContract` and `BaseContractRoute`; exact error/meta generics must remain assignable. | -| Workers health soundness test | `plugins/workers/services/src/routers/health-soundness_test.ts:3-53` | Asserts `BaseContractRoute` handler inference; metadata/error tightening must not regress input/output soundness. | -| CLI scaffold/template assertions | `packages/cli/e2e/tests/application/gates/generated-router-template_test.ts:35-41`; `packages/cli/src/kernel/adapters/contracts/contract-source.ts:63-77`; `packages/cli/src/public/features/plugins/new/new-plugin-use-case.ts:390-416` | Some generated contracts import root `baseContract`; the parser recognizes its route chain. No template change is planned. | -| Benchmark/reference/docs consumers | `packages/bench/tasks/t1-storefront-api/rubric.md:16`; `packages/bench/tasks/t1-storefront-api/reference/README.md:45-47`; `packages/bench/tasks/t1-storefront-api/reference/netscript/router.ts:7-8`; `packages/contracts/README.md:13-76` | Several statements explicitly describe the current erasure. They become stale after repair but are tracked follow-up debt outside this leaf's exact six-path ceiling. | +| Consumer | Evidence | Consequence | +| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `@netscript/fresh` diagnostics | `packages/fresh/src/diagnostics/error/extract.ts:1-39` | Imports `isDefinedError`, then manually casts code/status. It must still compile and preserve runtime detection; it is the only non-SDK package source importing the helper. | +| `@netscript/contracts/crud` | `packages/contracts/crud/create-crud-contract.ts:29-44,109-117,350-379` | Directly consumes public `baseContract` and `BaseContractRoute`; exact error/meta generics must remain assignable. | +| Workers health soundness test | `plugins/workers/services/src/routers/health-soundness_test.ts:3-53` | Asserts `BaseContractRoute` handler inference; metadata/error tightening must not regress input/output soundness. | +| CLI scaffold/template assertions | `packages/cli/e2e/tests/application/gates/generated-router-template_test.ts:35-41`; `packages/cli/src/kernel/adapters/contracts/contract-source.ts:63-77`; `packages/cli/src/public/features/plugins/new/new-plugin-use-case.ts:390-416` | Some generated contracts import root `baseContract`; the parser recognizes its route chain. No template change is planned. | +| Benchmark/reference consumers | `packages/bench/tasks/t1-storefront-api/rubric.md:16`; `packages/bench/tasks/t1-storefront-api/reference/README.md:45-47`; `packages/bench/tasks/t1-storefront-api/reference/netscript/router.ts:7-8` | The reference README/router explicitly describe the old erasure and are coordinator-owned follow-up debt outside this leaf's exact six-path ceiling; the rubric remains true. | +| Contracts README | `packages/contracts/README.md:13-76` | Says the common error map is applied. That remains true before and after this repair; PLAN-EVAL correctly found that the original research overstated this consumer. | First-party plugin-core files found by the base-contract search define their own local `baseContract: ReturnType` rather than importing `@netscript/contracts` @@ -206,6 +215,7 @@ unless `packages/sdk/src/ports/service-client.ts` is changed. 2. **Resolved:** #1466 owns metadata definition, initialization, and export. #1350 preserves only the existing fourth `ContractBuilder` generic as `Record` and makes no metadata semantic claim. -3. **Safe to defer:** `packages/contracts/README.md` and benchmark reference prose remain tracked - follow-up debt. They are not authorized here. Any attempt to edit either location—or any other - seventh product/test/docs path—is a rescope requiring a fresh ruling. +3. **Safe to defer:** benchmark reference prose remains coordinator-owned follow-up debt and is not + authorized here. `packages/contracts/README.md` is also outside scope but is not stale on this + point. Any attempt to edit either location—or any other seventh product/test/docs path—is a + rescope requiring a fresh ruling. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md index 0ba2ad81f..a1bcaa4c9 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md @@ -54,9 +54,10 @@ PLAN-EVAL PASS. ### Contributor path -Future contract error additions begin in the single `commonErrorMap`; the derived builder/client -types and RED fixture must then expose the new literal and schema-derived data automatically. No -second code union is hand-maintained. +Future contract error additions update the private explicit `CommonErrorMap` shape and its +`commonErrorMap` value together; the explicit annotation is required by `isolatedDeclarations` +because the published builder references `typeof commonErrorMap`. The real-export fixture then +forces the exact key union to stay synchronized. No code union or metadata vocabulary is exported. ## Progress log @@ -68,6 +69,11 @@ second code union is hand-maintained. | 2026-08-15 | Plan | Consumer scan | Executed whole-repo searches; identified `@netscript/fresh`, CRUD, query/desktop/type fixtures, CLI/template, docs, and baseline consumers. | | 2026-08-15 | Plan | Rescope | Required out-of-scope `service-client.ts`; conditional contracts barrel; live #1466 ownership conflict. Stopped all product work. | | 2026-08-15 | Plan | Amendment | Coordinator authorized the sixth client-port path, assigned metadata vocabulary to #1466, denied the barrel, and locked six paths. | +| 2026-08-15 | Eval | PLAN-EVAL | Terminal PASS at `f76a3c45b`; incorporated advisories A1-A5 without editing the evaluator artifact. | +| 2026-08-15 | S1 | RED | Real-export fixture recorded TS18046 (`unknown`) and TS2339 (`never`) together in one structured run; not rerun for tidier output. | +| 2026-08-15 | S1 | Builder | Exact `typeof commonErrorMap` error generic and explicit `Record` fourth slot; no metadata vocabulary or new export. | +| 2026-08-15 | S1 | Fixture | Uses contracts-root `CursorPaginationInputSchema` and `SuccessSchema`; asserts six keys, undeclared rejection, and empty meta slot. | +| 2026-08-15 | S1 | Gates | Focused structured check/test/lint/format pass; CRUD and workers soundness consumers included. Root/final-slice gates not run. | ## Gate results @@ -76,9 +82,167 @@ second code union is hand-maintained. | Current SDK raw publish dry-run | PASS (baseline inspection only) | `deno publish --dry-run --allow-dirty`, exit 0; no actual slow-type diagnostic | | Current contracts raw publish dry-run | PASS with sanctioned slow-type warning | `deno publish --dry-run --allow-dirty --allow-slow-types`, exit 0 | | Current JSR audit | KNOWN RED / INFO | SDK `F-DOCT-5`; contracts sanctioned slow-type INFO; not leaf verdicts | -| Implementation gates | NOT RUN | Prohibited before PLAN-EVAL; no product files changed | +| S1 RED | EXPECTED FAIL | Exactly TS18046 + TS2339 in one structured check | +| S1 focused check | PASS | 4 files, 1 batch, 0 diagnostics; includes CRUD + workers consumer | +| S1 focused test | PASS | 3/3 results; SDK doctest + workers health soundness | +| S1 focused lint | PASS | 2 edited TS files, 0 findings | +| S1 focused format write/check | PASS | 2 edited TS files, 0 findings; final check clean | +| Root/final-slice gates | NOT RUN | Explicitly reserved for slice 4 | + +### S1 structured JSON verdicts + +The RED fixture was checked once before product implementation: + +```json +{ + "exitCode": 1, + "command": "deno check --unstable-kv ", + "selection": { "filesSelected": 1, "batches": 1, "failedBatches": 1 }, + "summary": { + "totalOccurrences": 2, + "uniqueOccurrences": 2, + "uniqueCodes": 2, + "uniquePaths": 1 + }, + "groups": [ + { + "code": "TS18046", + "message": "'discriminated.error' is of type 'unknown'.", + "count": 1, + "location": "packages/sdk/tests/readme-doctest_test.ts:21:5" + }, + { + "code": "TS2339", + "message": "Property 'code' does not exist on type 'never'.", + "count": 1, + "location": "packages/sdk/tests/readme-doctest_test.ts:26:19" + } + ] +} +``` + +Two post-change check iterations failed and were fixed rather than hidden: + +```json +{ + "exitCode": 1, + "command": "deno check --unstable-kv ", + "selection": { "filesSelected": 4, "batches": 1, "failedBatches": 1 }, + "summary": { "totalOccurrences": 2, "uniqueCodes": 2, "uniquePaths": 1 }, + "groups": [ + { "code": "TS9010", "count": 1, "location": "contract-primitives.ts:21:7" }, + { "code": "TS9027", "count": 1, "location": "contract-primitives.ts:21:7" } + ], + "resolution": "Added an exact private CommonErrorMap annotation required by isolatedDeclarations." +} +``` + +```json +{ + "exitCode": 1, + "command": "deno check --unstable-kv ", + "selection": { "filesSelected": 4, "batches": 1, "failedBatches": 1 }, + "summary": { "totalOccurrences": 1, "uniqueCodes": 1, "uniquePaths": 1 }, + "groups": [ + { "code": "TS2322", "count": 1, "location": "contract-primitives.ts:54:7" } + ], + "resolution": "Removed a redundant satisfies expression whose contextual type widened literal values; oc.errors still enforces ErrorMap compatibility." +} +``` + +Final focused check: + +```json +{ + "exitCode": 0, + "command": "deno check --unstable-kv ", + "selection": { "filesSelected": 4, "batches": 1, "failedBatches": 0 }, + "summary": { + "totalOccurrences": 0, + "uniqueOccurrences": 0, + "uniqueCodes": 0, + "uniquePaths": 0 + }, + "groups": [] +} +``` + +Focused tests: + +```json +{ + "exitCode": 0, + "command": [ + "deno", + "test", + "--reporter=tap", + "--allow-all", + "packages/sdk/tests/readme-doctest_test.ts", + "plugins/workers/services/src/routers/health-soundness_test.ts" + ], + "summary": { + "passed": 3, + "failed": 0, + "ignored": 0, + "totalResults": 3, + "uniqueFailures": 0 + }, + "failures": [] +} +``` + +Focused lint: + +```json +{ + "exitCode": 0, + "selection": { "filesSelected": 2, "batches": 1 }, + "summary": { + "totalOccurrences": 0, + "uniqueOccurrences": 0, + "uniqueRules": 0, + "uniquePaths": 0 + }, + "groups": [] +} +``` + +Focused format write and final check: + +```json +{ + "exitCode": 0, + "command": "deno fmt", + "mode": "write", + "summary": { + "filesSelected": 2, + "batches": 1, + "failedBatches": 0, + "findings": 0, + "ignoredFindings": 0 + }, + "findings": [] +} +``` + +```json +{ + "exitCode": 0, + "command": "deno fmt --check", + "mode": "check", + "summary": { + "filesSelected": 2, + "batches": 1, + "failedBatches": 0, + "findings": 0, + "ignoredFindings": 0 + }, + "findings": [] +} +``` ## Handoff -Fresh Tier-A reviews this amended head first; a separate PLAN-EVAL session follows only after that -review. This generator must not self-evaluate or implement before `PASS`. +S1 is complete and stops here for fresh Tier-A review. Slice 2 is not authorized in this session; +`errors.ts`, `ports/service-client.ts`, and both docs pages remain untouched. No evaluator was +launched and no metadata acceptance box was ticked. diff --git a/packages/contracts/src/application/contract-primitives.ts b/packages/contracts/src/application/contract-primitives.ts index 46c107174..8be140a4f 100644 --- a/packages/contracts/src/application/contract-primitives.ts +++ b/packages/contracts/src/application/contract-primitives.ts @@ -1,9 +1,9 @@ import { oc } from '@orpc/contract'; import type { AnySchema, + ContractBuilder, ContractProcedureBuilderWithInputOutput, ContractProcedureBuilderWithOutput, - ErrorMap, MergedErrorMap, Schema, } from '@orpc/contract'; @@ -16,9 +16,40 @@ import { validationErrorSchema, } from '../domain/schemas.ts'; -type OrpcErrorMap = Parameters[0]; +type CommonErrorMap = Readonly<{ + NOT_FOUND: Readonly<{ + status: 404; + message: 'Resource not found'; + data: typeof notFoundErrorSchema; + }>; + VALIDATION_ERROR: Readonly<{ + status: 422; + message: 'Validation failed'; + data: typeof validationErrorSchema; + }>; + UNAUTHORIZED: Readonly<{ + status: 401; + message: 'Authentication required'; + data: typeof unauthorizedErrorSchema; + }>; + FORBIDDEN: Readonly<{ + status: 403; + message: 'Access denied'; + data: typeof forbiddenErrorSchema; + }>; + RATE_LIMITED: Readonly<{ + status: 429; + message: 'Too many requests'; + data: typeof rateLimitErrorSchema; + }>; + SERVICE_UNAVAILABLE: Readonly<{ + status: 503; + message: 'Service temporarily unavailable'; + data: typeof serviceUnavailableErrorSchema; + }>; +}>; -const commonErrorMap = { +const commonErrorMap: CommonErrorMap = { NOT_FOUND: { status: 404, message: 'Resource not found', @@ -49,7 +80,7 @@ const commonErrorMap = { message: 'Service temporarily unavailable', data: serviceUnavailableErrorSchema, }, -} as const satisfies OrpcErrorMap; +}; /** * Common oRPC contract primitive with NetScript's standard error map applied. @@ -78,7 +109,12 @@ const commonErrorMap = { * .output(z.object({ items: z.array(z.unknown()) })); * ``` */ -export const baseContract: ReturnType = oc.errors(commonErrorMap); +export const baseContract: ContractBuilder< + Schema, + Schema, + BaseContractErrors, + Record +> = oc.errors(commonErrorMap); /** * Concrete type of {@link baseContract} — the real oRPC contract builder with @@ -95,7 +131,7 @@ export type BaseContract = typeof baseContract; * vocabulary merged onto an empty map. Mirrors the `BaseErrors` alias used by * the first-party `@netscript/plugin-*-core` contract definitions. */ -export type BaseContractErrors = MergedErrorMap, ErrorMap>; +export type BaseContractErrors = MergedErrorMap, typeof commonErrorMap>; /** * Sound type of a route built via diff --git a/packages/sdk/tests/readme-doctest_test.ts b/packages/sdk/tests/readme-doctest_test.ts index 71ca21acc..d77829234 100644 --- a/packages/sdk/tests/readme-doctest_test.ts +++ b/packages/sdk/tests/readme-doctest_test.ts @@ -1,4 +1,53 @@ +import { baseContract, CursorPaginationInputSchema, SuccessSchema } from '@netscript/contracts'; import { assertEquals } from './test-helpers.ts'; +import { isDefinedError, safe, type ServiceClient } from '../mod.ts'; + +type IsAny = 0 extends (1 & T) ? true : false; +type Equal = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? true + : false; +type Assert = T; + +type BaseErrorCode = keyof typeof baseContract['~orpc']['errorMap']; +type ExpectedBaseErrorCode = + | 'NOT_FOUND' + | 'VALIDATION_ERROR' + | 'UNAUTHORIZED' + | 'FORBIDDEN' + | 'RATE_LIMITED' + | 'SERVICE_UNAVAILABLE'; +type _BaseErrorCodesPreserved = Assert>; +type _UndeclaredBaseErrorRejected = Assert< + Equal, never> +>; + +type BaseMeta = typeof baseContract['~orpc']['meta']; +type _BaseMetaIsNotAny = Assert, false>>; +type _EmptyBaseMetaSlotPreserved = Assert>>; + +const typedErrorContract = { + list: baseContract + .route({ method: 'GET', path: '/typed-error-probe' }) + .input(CursorPaginationInputSchema) + .output(SuccessSchema), +}; + +declare const typedErrorClient: ServiceClient; + +async function assertRealExportErrorChannel(): Promise { + const discriminated = await safe(typedErrorClient.list({ limit: 10 })); + if (!discriminated.isSuccess && discriminated.isDefined) { + // @ts-expect-error S2 must preserve the promise error union through safe(). + discriminated.error.code; + } + + const guarded = await safe(typedErrorClient.list({ limit: 10 })); + if (!guarded.isSuccess && isDefinedError(guarded.error)) { + // @ts-expect-error S2 must make isDefinedError narrow to a real defined error. + guarded.error.code; + } +} + +void assertRealExportErrorChannel; const README_PATH = new URL('../README.md', import.meta.url); From ca7ade409be0cc0c064e75f5bfa1bd109e06d013 Mon Sep 17 00:00:00 2001 From: Rickylabs Date: Sat, 15 Aug 2026 22:48:10 +0200 Subject: [PATCH 5/8] fix(sdk): preserve typed service errors --- .../context-pack.md | 32 ++-- .../worklog.md | 166 ++++++++++++++++-- packages/sdk/src/client/errors.ts | 74 ++++++-- packages/sdk/src/ports/service-client.ts | 19 +- packages/sdk/tests/readme-doctest_test.ts | 98 ++++++++++- 5 files changed, 336 insertions(+), 53 deletions(-) diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md index cb7f4dcc1..c6d3bcffc 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md @@ -6,16 +6,17 @@ | -------------- | ------------------------------------------------------------ | | Run ID | `fix-sdk-typed-error-channel--0.0.7-wave1` | | Branch | `fix/sdk-typed-error-channel` | -| Current phase | `implementation` — S1 complete; awaiting fresh Tier-A review | +| Current phase | `implementation` — S2 complete; awaiting fresh Tier-A review | | Archetype | `1 — Small Contract` slice | | Scope overlays | `docs` | ## Current state -PLAN-EVAL is terminal PASS at evaluator commit `f76a3c45b`. S1 records both real-export REDs -(TS18046 `unknown`, TS2339 `never`), then makes the contracts builder carry the exact six-key error -map and explicit empty fourth slot. S2 error-channel files remain untouched. #1466 owns metadata -definition/export; S1 introduces no metadata vocabulary or acceptance claim. +PLAN-EVAL is terminal PASS at evaluator commit `f76a3c45b`; S1 Tier-A passed at `dc034d680`. S2 +preserves the exact six-key contract error union through the real `ServiceClient` promise marker, +`safe()`, and `isDefinedError()`. The two S1 suppressions are now positive exact-type assertions, +including code-specific data. #1466 still owns metadata definition/export; no metadata vocabulary or +acceptance claim was introduced. ## Completed @@ -27,11 +28,16 @@ definition/export; S1 introduces no metadata vocabulary or acceptance claim. - Separate-session PLAN-EVAL PASS with advisories A1-A5 incorporated. - S1 builder annotation and real-export regression fixture using contracts-exported schemas. - S1 structured check/test/lint/format gates. +- S1 fresh Tier-A PASS at `dc034d680`. +- S2 uses upstream public `ClientPromiseResult`, `ErrorFromErrorMap`, and `ThrowableError` types to + carry the procedure error map without a local schema shim or ambient declaration. +- S2 literal failure arms, real positive assertions, runtime identity test, and focused consumer + compatibility checks/tests. ## Next steps -1. Fresh Tier-A reviews the landed S1 head and structured evidence. -2. Stop. Slice 2 requires fresh coordinator authorization after that review. +1. Commit and push S2, then post its receipt on draft PR #1671. +2. Stop for fresh Tier-A. Slice 3 remains unauthorized in this session. ## Key decisions @@ -46,23 +52,29 @@ definition/export; S1 introduces no metadata vocabulary or acceptance claim. ## Files changed - `packages/contracts/src/application/contract-primitives.ts` +- `packages/sdk/src/client/errors.ts` +- `packages/sdk/src/ports/service-client.ts` - `packages/sdk/tests/readme-doctest_test.ts` - existing files under `.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/` -No S2-S4 file was modified. +No docs/S3-S4 product file was modified. ## Gates - PLAN-EVAL: PASS (`plan-eval.md`, `f76a3c45b`). - S1 RED: expected FAIL with exactly TS18046 and TS2339, captured once. - S1 focused check/test/lint/format: PASS; full JSON is in `worklog.md`. +- S2 focused check: PASS, 15 affected source/test/type-fixture files, 0 diagnostics. +- S2 consumer tests: PASS, 12/12 results across SDK doctest/query/desktop and Fresh extraction. +- S2 lint and format: PASS after correcting two type-only imports; the initial lint red is retained + in `worklog.md`. - Root quality/publish/docs/JSR gates: not run; reserved for the final slice. - Baseline JSR/publish inspection is recorded in `research.md` and `worklog.md`. ## Open questions -- None for S1. Benchmark reference prose is coordinator-owned follow-up debt; the contracts README - correction is recorded in `research.md` without editing that seventh path. +- Live service integration tests requiring a runtime lease were not run because this slice forbids a + lease. Benchmark reference prose remains coordinator-owned follow-up debt. ## Drift and debt diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md index a1bcaa4c9..43c3b0213 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md @@ -61,19 +61,25 @@ forces the exact key union to stay synchronized. No code union or metadata vocab ## Progress log -| Time | Slice | Step | Notes | -| ---------- | ----- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | -| 2026-08-15 | Plan | Bootstrap/research | Re-baselined exact branch/base; read required skills, doctrine, harness, Accepted RFC, issues, and docs. | -| 2026-08-15 | Plan | Public inspection | `deno doc` confirmed current `safe`, `isDefinedError`, `SafeResult`, and `baseContract` signatures. | -| 2026-08-15 | Plan | RED | Executed TS2339 `error.code` on `never` with `deno eval --check --unstable-kv`. | -| 2026-08-15 | Plan | Consumer scan | Executed whole-repo searches; identified `@netscript/fresh`, CRUD, query/desktop/type fixtures, CLI/template, docs, and baseline consumers. | -| 2026-08-15 | Plan | Rescope | Required out-of-scope `service-client.ts`; conditional contracts barrel; live #1466 ownership conflict. Stopped all product work. | -| 2026-08-15 | Plan | Amendment | Coordinator authorized the sixth client-port path, assigned metadata vocabulary to #1466, denied the barrel, and locked six paths. | -| 2026-08-15 | Eval | PLAN-EVAL | Terminal PASS at `f76a3c45b`; incorporated advisories A1-A5 without editing the evaluator artifact. | -| 2026-08-15 | S1 | RED | Real-export fixture recorded TS18046 (`unknown`) and TS2339 (`never`) together in one structured run; not rerun for tidier output. | -| 2026-08-15 | S1 | Builder | Exact `typeof commonErrorMap` error generic and explicit `Record` fourth slot; no metadata vocabulary or new export. | -| 2026-08-15 | S1 | Fixture | Uses contracts-root `CursorPaginationInputSchema` and `SuccessSchema`; asserts six keys, undeclared rejection, and empty meta slot. | -| 2026-08-15 | S1 | Gates | Focused structured check/test/lint/format pass; CRUD and workers soundness consumers included. Root/final-slice gates not run. | +| Time | Slice | Step | Notes | +| ---------- | ----- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | +| 2026-08-15 | Plan | Bootstrap/research | Re-baselined exact branch/base; read required skills, doctrine, harness, Accepted RFC, issues, and docs. | +| 2026-08-15 | Plan | Public inspection | `deno doc` confirmed current `safe`, `isDefinedError`, `SafeResult`, and `baseContract` signatures. | +| 2026-08-15 | Plan | RED | Executed TS2339 `error.code` on `never` with `deno eval --check --unstable-kv`. | +| 2026-08-15 | Plan | Consumer scan | Executed whole-repo searches; identified `@netscript/fresh`, CRUD, query/desktop/type fixtures, CLI/template, docs, and baseline consumers. | +| 2026-08-15 | Plan | Rescope | Required out-of-scope `service-client.ts`; conditional contracts barrel; live #1466 ownership conflict. Stopped all product work. | +| 2026-08-15 | Plan | Amendment | Coordinator authorized the sixth client-port path, assigned metadata vocabulary to #1466, denied the barrel, and locked six paths. | +| 2026-08-15 | Eval | PLAN-EVAL | Terminal PASS at `f76a3c45b`; incorporated advisories A1-A5 without editing the evaluator artifact. | +| 2026-08-15 | S1 | RED | Real-export fixture recorded TS18046 (`unknown`) and TS2339 (`never`) together in one structured run; not rerun for tidier output. | +| 2026-08-15 | S1 | Builder | Exact `typeof commonErrorMap` error generic and explicit `Record` fourth slot; no metadata vocabulary or new export. | +| 2026-08-15 | S1 | Fixture | Uses contracts-root `CursorPaginationInputSchema` and `SuccessSchema`; asserts six keys, undeclared rejection, and empty meta slot. | +| 2026-08-15 | S1 | Gates | Focused structured check/test/lint/format pass; CRUD and workers soundness consumers included. Root/final-slice gates not run. | +| 2026-08-15 | S1 | Tier-A | Fresh Tier-A PASS at `dc034d680`; coordinator authorized S2 only. | +| 2026-08-15 | S2 | Public inspection | `deno doc` confirmed upstream `ClientPromiseResult`, `ErrorFromErrorMap`, `ThrowableError`, and literal `SafeResult` arms before source edit. | +| 2026-08-15 | S2 | Error channel | Defaulted three-parameter `ServiceClientMethod` carries the real error map; `SafeFailure` retains its default. | +| 2026-08-15 | S2 | Assertions | Removed both S2 suppressions and replaced them with exact code-union/defined-error assertions plus code-specific data and plain-error guards. | +| 2026-08-15 | S2 | Consumers | Checked query, desktop, RFC, assignability, service-query, and Fresh consumers; ran SDK query/desktop plus Fresh extraction tests. | +| 2026-08-15 | S2 | Reconcile | Observed S1 Tier-A PASS; no issue/label/checklist mutation, no #1348/#1466 touch, no scope adjustment, and S3 remains blocked. | ## Gate results @@ -87,6 +93,10 @@ forces the exact key union to stay synchronized. No code union or metadata vocab | S1 focused test | PASS | 3/3 results; SDK doctest + workers health soundness | | S1 focused lint | PASS | 2 edited TS files, 0 findings | | S1 focused format write/check | PASS | 2 edited TS files, 0 findings; final check clean | +| S2 focused check | PASS | 15 affected files, 1 batch, 0 diagnostics | +| S2 focused/consumer tests | PASS | 12/12 results across 4 test files | +| S2 focused lint | PASS after one recorded correction | Initial 2 `verbatim-module-syntax`; final 3 files, 0 findings | +| S2 focused format write/check | PASS | 3 edited TS files, 0 findings | | Root/final-slice gates | NOT RUN | Explicitly reserved for slice 4 | ### S1 structured JSON verdicts @@ -241,8 +251,132 @@ Focused format write and final check: } ``` +### S2 structured JSON verdicts + +Final focused check, including SDK query/desktop/RFC/assignability/service-query consumers and the +Fresh error-extraction consumer: + +```json +{ + "exitCode": 0, + "command": "deno check --unstable-kv ", + "selection": { "filesSelected": 15, "batches": 1, "failedBatches": 0 }, + "summary": { + "totalOccurrences": 0, + "uniqueOccurrences": 0, + "uniqueCodes": 0, + "uniquePaths": 0 + }, + "groups": [] +} +``` + +Final focused and consumer tests: + +```json +{ + "exitCode": 0, + "command": [ + "deno", + "test", + "--reporter=tap", + "--allow-all", + "packages/sdk/tests/readme-doctest_test.ts", + "packages/sdk/tests/query/query-factory_test.ts", + "packages/sdk/tests/desktop/desktop-rpc-client_test.ts", + "packages/fresh/src/diagnostics/error/extract_test.ts" + ], + "summary": { + "passed": 12, + "failed": 0, + "ignored": 0, + "totalResults": 12, + "uniqueFailures": 0 + }, + "failures": [] +} +``` + +The first lint run failed and was corrected rather than hidden: + +```json +{ + "exitCode": 1, + "selection": { "filesSelected": 3, "batches": 1 }, + "summary": { + "totalOccurrences": 2, + "uniqueOccurrences": 2, + "uniqueRules": 1, + "uniquePaths": 1 + }, + "groups": [ + { + "rule": "verbatim-module-syntax", + "message": "Import identifier only used in types", + "count": 2, + "path": "packages/sdk/tests/readme-doctest_test.ts", + "locations": ["4:3", "6:3"] + } + ], + "resolution": "Marked both exported schema imports type-only." +} +``` + +Final focused lint: + +```json +{ + "exitCode": 0, + "selection": { "filesSelected": 3, "batches": 1 }, + "summary": { + "totalOccurrences": 0, + "uniqueOccurrences": 0, + "uniqueRules": 0, + "uniquePaths": 0 + }, + "groups": [] +} +``` + +Final focused format write and check: + +```json +{ + "exitCode": 0, + "command": "deno fmt", + "mode": "write", + "summary": { + "filesSelected": 3, + "batches": 1, + "failedBatches": 0, + "findings": 0, + "ignoredFindings": 0 + }, + "findings": [] +} +``` + +```json +{ + "exitCode": 0, + "command": "deno fmt --check", + "mode": "check", + "summary": { + "filesSelected": 3, + "batches": 1, + "failedBatches": 0, + "findings": 0, + "ignoredFindings": 0 + }, + "findings": [] +} +``` + +The S2 doctest-only run also passed 3/3 before the broader 12-test consumer run. The S1 RED block +above was not edited or rerun. `typed-queue_test.ts` was not selected, so #1667 was not encountered. +Root/final-slice gates were not run. + ## Handoff -S1 is complete and stops here for fresh Tier-A review. Slice 2 is not authorized in this session; -`errors.ts`, `ports/service-client.ts`, and both docs pages remain untouched. No evaluator was -launched and no metadata acceptance box was ticked. +S2 is complete and stops here for fresh Tier-A review. Slice 3 and both docs pages remain untouched. +No evaluator was launched, no acceptance box was ticked, and no metadata vocabulary was introduced. diff --git a/packages/sdk/src/client/errors.ts b/packages/sdk/src/client/errors.ts index bf47f0ca8..b6d4efc9e 100644 --- a/packages/sdk/src/client/errors.ts +++ b/packages/sdk/src/client/errors.ts @@ -4,7 +4,11 @@ * @module */ -import { isDefinedError as orpcIsDefinedError } from '@orpc/client'; +import { + type ClientPromiseResult, + isDefinedError as orpcIsDefinedError, + type ThrowableError, +} from '@orpc/client'; /** * Public shape of an oRPC defined error. @@ -33,20 +37,47 @@ export type SafeSuccess = [null, TOutput, false, true] & { isSuccess: true; }; +type DefinedErrorLike = Error & { + readonly defined: boolean; + readonly code: string; + readonly status: number; + readonly data: unknown; +}; + +type NarrowDefined = Extract & DefinedError; + +type NonDefinedSafeFailure = [ + Exclude, + undefined, + false, + false, +] & { + error: Exclude; + data: undefined; + isDefined: false; + isSuccess: false; +}; + +type DefinedSafeFailure = [NarrowDefined, undefined, true, false] & { + error: NarrowDefined; + data: undefined; + isDefined: true; + isSuccess: false; +}; + /** * Failure branch returned by {@link safe}. */ -export type SafeFailure = [TError, null, boolean, false] & { - error: TError; - data: null; - isDefined: boolean; - isSuccess: false; -}; +export type SafeFailure = + | NonDefinedSafeFailure + | DefinedSafeFailure; /** * Tuple/object result returned by {@link safe}. */ -export type SafeResult = SafeSuccess | SafeFailure; +export type SafeResult = + | SafeSuccess + | SafeFailure; function createSafeSuccess(data: TOutput): SafeSuccess { const tuple = [null, data, false, true] as SafeSuccess; @@ -57,11 +88,20 @@ function createSafeSuccess(data: TOutput): SafeSuccess { return tuple; } -function createSafeFailure(error: TError, isDefined: boolean): SafeFailure { - const tuple = [error, null, isDefined, false] as SafeFailure; - tuple.error = error; - tuple.data = null; - tuple.isDefined = isDefined; +function createSafeFailure(error: TError): SafeFailure { + if (isDefinedError(error)) { + const tuple = [error, undefined, true, false] as DefinedSafeFailure; + tuple.error = error; + tuple.data = undefined; + tuple.isDefined = true; + tuple.isSuccess = false; + return tuple; + } + + const tuple = [error, undefined, false, false] as NonDefinedSafeFailure; + tuple.error = error as Exclude; + tuple.data = undefined; + tuple.isDefined = false; tuple.isSuccess = false; return tuple; } @@ -72,7 +112,7 @@ function createSafeFailure(error: TError, isDefined: boolean): SafeFailu * @param error - Unknown thrown value. * @returns `true` when the error is an oRPC defined error. */ -export function isDefinedError(error: T): error is Extract { +export function isDefinedError(error: T): error is NarrowDefined { return orpcIsDefinedError(error); } @@ -83,10 +123,12 @@ export function isDefinedError(error: T): error is Extract { * @param promise - Promise to resolve safely. * @returns Safe tuple/object result. */ -export async function safe(promise: PromiseLike): Promise> { +export async function safe( + promise: ClientPromiseResult, +): Promise> { try { return createSafeSuccess(await promise); } catch (error) { - return createSafeFailure(error, isDefinedError(error)); + return createSafeFailure(error as TError); } } diff --git a/packages/sdk/src/ports/service-client.ts b/packages/sdk/src/ports/service-client.ts index 73e3bd36b..ff7c669a7 100644 --- a/packages/sdk/src/ports/service-client.ts +++ b/packages/sdk/src/ports/service-client.ts @@ -4,6 +4,9 @@ * @module */ +import type { ClientPromiseResult, ThrowableError } from '@orpc/client'; +import type { ErrorFromErrorMap, ErrorMap } from '@orpc/contract'; + /** * Minimal structural representation of a standard-schema-compatible type. */ @@ -162,13 +165,18 @@ export interface ServiceRequestOptions { context?: ServiceClientContext; } +type ProcedureErrorFromNode = TNode extends { + readonly '~orpc': { readonly errorMap: infer TErrorMap extends ErrorMap }; +} ? ErrorFromErrorMap + : ThrowableError; + /** * Typed service-client method derived from a contract procedure. */ -export type ServiceClientMethod = ( +export type ServiceClientMethod = ( input: TInput, options?: ServiceRequestOptions, -) => Promise; +) => ClientPromiseResult; /** * Compile-time marker that preserves the source contract for inference. @@ -182,8 +190,11 @@ export interface ServiceClientContract { * Recursive callable/router shape for a typed service client. */ export type ServiceClientShape = TContract extends - ContractProcedureLike - ? ServiceClientMethod, ProcedureOutputFromNode> + ContractProcedureLike ? ServiceClientMethod< + ProcedureInputFromNode, + ProcedureOutputFromNode, + ProcedureErrorFromNode + > : { [K in keyof TContract]: TContract[K] extends ContractLike ? ServiceClient : never; }; diff --git a/packages/sdk/tests/readme-doctest_test.ts b/packages/sdk/tests/readme-doctest_test.ts index d77829234..857db2403 100644 --- a/packages/sdk/tests/readme-doctest_test.ts +++ b/packages/sdk/tests/readme-doctest_test.ts @@ -1,6 +1,20 @@ -import { baseContract, CursorPaginationInputSchema, SuccessSchema } from '@netscript/contracts'; -import { assertEquals } from './test-helpers.ts'; -import { isDefinedError, safe, type ServiceClient } from '../mod.ts'; +import { + baseContract, + CursorPaginationInputSchema, + type NotFoundErrorSchema, + SuccessSchema, + type ValidationErrorSchema, +} from '@netscript/contracts'; +import { type ClientPromiseResult, ORPCError } from '@orpc/client'; +import { assert, assertEquals } from './test-helpers.ts'; +import { + type ContractSchemaOutput, + type DefinedError, + isDefinedError, + safe, + type SafeFailure, + type ServiceClient, +} from '../mod.ts'; type IsAny = 0 extends (1 & T) ? true : false; type Equal = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? true @@ -36,19 +50,89 @@ declare const typedErrorClient: ServiceClient; async function assertRealExportErrorChannel(): Promise { const discriminated = await safe(typedErrorClient.list({ limit: 10 })); if (!discriminated.isSuccess && discriminated.isDefined) { - // @ts-expect-error S2 must preserve the promise error union through safe(). - discriminated.error.code; + type _SafePreservesExactErrorCode = Assert< + Equal + >; + const preservedCode: ExpectedBaseErrorCode = discriminated.error.code; + void preservedCode; + + if (discriminated.error.code === 'NOT_FOUND') { + type _SafePreservesNotFoundData = Assert< + Equal< + typeof discriminated.error.data, + ContractSchemaOutput + > + >; + } + + if (discriminated.error.code === 'VALIDATION_ERROR') { + type _SafePreservesValidationData = Assert< + Equal< + typeof discriminated.error.data, + ContractSchemaOutput + > + >; + } } const guarded = await safe(typedErrorClient.list({ limit: 10 })); if (!guarded.isSuccess && isDefinedError(guarded.error)) { - // @ts-expect-error S2 must make isDefinedError narrow to a real defined error. - guarded.error.code; + type _GuardNarrowsToExactDefinedError = Assert< + Equal + >; + const definedError: DefinedError = guarded.error; + void definedError; + + if (guarded.error.code === 'NOT_FOUND') { + type _GuardPreservesNotFoundData = Assert< + Equal> + >; + } } } +type _PlainErrorRemainsNonDefined = Assert< + Equal, { isDefined: false }>['error'], Error> +>; +type _PlainErrorRejectedFromDefinedArm = Assert< + Equal, { isDefined: true }>['error'], never> +>; + void assertRealExportErrorChannel; +Deno.test('safe preserves defined and non-defined failure identity', async () => { + const contractError = new ORPCError('NOT_FOUND', { + defined: true, + status: 404, + data: { resource: 'Order', id: 'ord_1' }, + }); + const contractFailure: ClientPromiseResult = Promise.reject( + contractError, + ); + const definedResult = await safe(contractFailure); + + assert(!definedResult.isSuccess, 'expected contract failure'); + assert(definedResult.isDefined, 'expected a defined contract failure'); + assertEquals(definedResult.error, contractError); + assertEquals(definedResult.data, undefined); + assertEquals(definedResult[0], contractError); + assertEquals(definedResult[1], undefined); + assertEquals(definedResult[2], true); + assertEquals(definedResult[3], false); + + const plainError = new Error('network unavailable'); + const plainResult = await safe(Promise.reject(plainError)); + + assert(!plainResult.isSuccess, 'expected plain failure'); + assert(!plainResult.isDefined, 'plain errors must not become defined errors'); + assertEquals(plainResult.error, plainError); + assertEquals(plainResult.data, undefined); + assertEquals(plainResult[0], plainError); + assertEquals(plainResult[1], undefined); + assertEquals(plainResult[2], false); + assertEquals(plainResult[3], false); +}); + const README_PATH = new URL('../README.md', import.meta.url); const DOCTEST_PRELUDE = ` From c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd Mon Sep 17 00:00:00 2001 From: Rickylabs Date: Sat, 15 Aug 2026 23:08:42 +0200 Subject: [PATCH 6/8] docs(sdk): document typed error channel --- .../worklog.md | 164 +++++++++++++++++- .../services-sdk/how-to/discover-services.md | 81 ++++++--- docs/site/services-sdk/sdk.md | 69 ++++++-- 3 files changed, 280 insertions(+), 34 deletions(-) diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md index 43c3b0213..a62ee1fa1 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md @@ -80,6 +80,10 @@ forces the exact key union to stay synchronized. No code union or metadata vocab | 2026-08-15 | S2 | Assertions | Removed both S2 suppressions and replaced them with exact code-union/defined-error assertions plus code-specific data and plain-error guards. | | 2026-08-15 | S2 | Consumers | Checked query, desktop, RFC, assignability, service-query, and Fresh consumers; ran SDK query/desktop plus Fresh extraction tests. | | 2026-08-15 | S2 | Reconcile | Observed S1 Tier-A PASS; no issue/label/checklist mutation, no #1348/#1466 touch, no scope adjustment, and S3 remains blocked. | +| 2026-08-15 | S3 | Narrative | Both published pages now scope contract typing to input/output/declared errors and keep transport/arbitrary throws on the non-defined branch. | +| 2026-08-15 | S3 | Examples | Literal `isSuccess` then `isDefined` branches terminate every failure path; all six codes and code-selected schema data are shown. | +| 2026-08-15 | S3 | Compile proof | SDK fence passed `docs:snippets`; discover Step 3/4 and copyable function passed page-isolated checks with a real `baseContract` fixture. | +| 2026-08-15 | S3 | Gates | Source format, accuracy, source/rendered links, caveats, build, doctest, and focused example compilation passed; final gates remain S4-only. | ## Gate results @@ -97,6 +101,14 @@ forces the exact key union to stay synchronized. No code union or metadata vocab | S2 focused/consumer tests | PASS | 12/12 results across 4 test files | | S2 focused lint | PASS after one recorded correction | Initial 2 `verbatim-module-syntax`; final 3 files, 0 findings | | S2 focused format write/check | PASS | 3 edited TS files, 0 findings | +| S3 docs source format | PASS | Structured receipt; exit 0, `Docs source format: OK` | +| S3 docs accuracy | PASS | Structured receipt; 199 published source pages checked | +| S3 SDK snippet compile | PASS | 36 Tier-1 candidates, 22 checked, 14 existing exemptions | +| S3 discover example compile | PASS | Step 3/4 pair and end-to-end function; real base-contract fixture | +| S3 SDK doctest | PASS | 3/3 results | +| S3 source links | PASS | 103 docs; 0 broken links, anchors, or orphans | +| S3 site verify | PASS | Build, rendered-output, rendered links, and caveats; raw exit 0 | +| S3 scope/lock hygiene | PASS | Four landed product/test files and `deno.lock` unchanged | | Root/final-slice gates | NOT RUN | Explicitly reserved for slice 4 | ### S1 structured JSON verdicts @@ -376,7 +388,155 @@ The S2 doctest-only run also passed 3/3 before the broader 12-test consumer run. above was not edited or rerun. `typed-queue_test.ts` was not selected, so #1667 was not encountered. Root/final-slice gates were not run. +### S3 disposition receipt + +| Planned location | Disposition | Final location and wording | +| ---------------- | ----------- | -------------------------- | +| `sdk.md:12-20` | Retained with required scope | `sdk.md:12-22` now limits non-drift to input, output, and declared-error types; transport/arbitrary throws are explicitly non-defined failures. | +| `sdk.md:31-38` | Retained and corrected | `sdk.md:33-41` says the L1 method's input, output, and declared-error union are inferred from the contract. | +| `sdk.md:58-70` | Retained and corrected | `sdk.md:61-71` says `baseContract` error schemas flow through the client promise and `safe()`, while runtime non-defined failures remain outside the contract. | +| `sdk.md:113-114` | Retained with cross-reference | `sdk.md:118-120` scopes direct-call inference to input/output/declared errors and points rejecting calls to the `safe()` section. | +| `sdk.md:196-198` | Replaced completely | `sdk.md:203-249` names all six codes, branches on literal `isSuccess` then `isDefined`, throws non-defined failures, demonstrates `NOT_FOUND` schema data, and scopes `isDefinedError`. | +| `discover-services.md:9-14` | Retained with required scope | `discover-services.md:11-15` names input/output/declared errors and keeps discovery/transport failures non-defined. | +| `discover-services.md:96-101,114-115` | Retained and corrected | `discover-services.md:99-118` states `baseContract` carries the six errors and the call comment includes declared-error inference; the prerequisite row also names declared errors. | +| `discover-services.md:135-154` | Replaced completely | `discover-services.md:137-176` names all six codes, uses literal discriminants, terminates every failure, demonstrates `NOT_FOUND` schema data, and scopes `isDefinedError`. | +| `discover-services.md:205-229` | Replaced completely | `discover-services.md:227-264` makes the copyable function return only from `isSuccess`, throws the non-defined branch, and demonstrates `VALIDATION_ERROR` schema data before throwing the defined failure. | + +### S3 structured JSON verdicts + +Final source-format receipt: + +```json +{ + "gateId": "docs-source-format", + "invocationId": "fix-sdk-typed-error-channel-s3-source-format-final", + "gitHead": "ca7ade409be0cc0c064e75f5bfa1bd109e06d013", + "outcome": "PASS", + "exitCode": 0, + "durationMs": 444, + "stdout": "Docs source format: OK" +} +``` + +Final docs-accuracy receipt: + +```json +{ + "gateId": "docs-accuracy", + "invocationId": "fix-sdk-typed-error-channel-s3-docs-accuracy-final", + "gitHead": "ca7ade409be0cc0c064e75f5bfa1bd109e06d013", + "outcome": "PASS", + "exitCode": 0, + "durationMs": 11532, + "summary": "199 published source pages; 91/91 root/direct public commands from 149 recursive paths" +} +``` + +This accuracy PASS proves only the invariants implemented by that script; it is not treated as +proof of the page-level error narrative. + +SDK-page snippet compilation: + +```json +{ + "gate": "docs:snippets", + "outcome": "PASS", + "exitCode": 0, + "census": { + "scanned": 581, + "tsLike": 298, + "tier1": 36, + "checked": 22, + "exempt": 14, + "outsideFloor": 262, + "malformed": 0 + } +} +``` + +The SDK error fence is in the Tier-1 floor and compiles directly against the real +`@netscript/contracts` and `@netscript/sdk/client` workspace entrypoints. Discover-services is +outside the current day-one snippet floor, so its changed examples were compiled page-isolated in +two batches. The only supplied prelude replaced the project-local `@my-app/contracts` import with a +real `baseContract` route of the same `list` input/output shape; the documented client construction, +branching, exact code annotation, and code-specific `data` accesses were unchanged: + +```json +{ + "gate": "discover-services-error-examples", + "verdict": "PASS", + "results": [ + { + "source": [105, 146], + "code": 0, + "rootLockUnchanged": true, + "temporaryLockRewritten": true, + "diagnostics": "Check discover-services.md Step 3/4" + }, + { + "source": [232], + "code": 0, + "rootLockUnchanged": true, + "temporaryLockRewritten": true, + "diagnostics": "Check discover-services.md end-to-end function" + } + ] +} +``` + +SDK doctest: + +```json +{ + "schemaVersion": 1, + "command": [ + "deno", + "test", + "--reporter=tap", + "--allow-all", + "packages/sdk/tests/readme-doctest_test.ts" + ], + "exitCode": 0, + "durationMs": 5485, + "summary": { "passed": 3, "failed": 0, "ignored": 0, "totalResults": 3 }, + "failures": [] +} +``` + +Link/build/scope verdicts: + +```json +{ + "docsSourceLinks": { + "command": "deno task docs:links", + "outcome": "PASS", + "exitCode": 0, + "docs": 103, + "brokenLinks": 0, + "brokenAnchors": 0, + "orphans": 0 + }, + "siteVerify": { + "command": "deno task --cwd docs/site verify", + "outcome": "PASS", + "exitCode": 0, + "covers": ["source-format", "build", "rendered-output", "rendered-links", "caveats"] + }, + "scope": { + "outcome": "PASS", + "productAndTestFilesByteIdentical": true, + "denoLockUnchanged": true, + "newFiles": 0 + } +} +``` + +The known raw-red `surface:diff`, JSR `F-DOCT-5`, and pinned raw doc-lint baselines were not run in +this slice and remain known red; no green claim is made. `typed-queue_test.ts` was not selected, so +#1667 was not encountered. Root/final-slice gates remain reserved for S4. + ## Handoff -S2 is complete and stops here for fresh Tier-A review. Slice 3 and both docs pages remain untouched. -No evaluator was launched, no acceptance box was ticked, and no metadata vocabulary was introduced. +S3 is complete and stops here for fresh Tier-A review. No S4 work started, no evaluator was +launched, no acceptance box was ticked, and no metadata vocabulary or acceptance claim was +introduced. diff --git a/docs/site/services-sdk/how-to/discover-services.md b/docs/site/services-sdk/how-to/discover-services.md index 830c611ab..86547a1ab 100644 --- a/docs/site/services-sdk/how-to/discover-services.md +++ b/docs/site/services-sdk/how-to/discover-services.md @@ -10,8 +10,9 @@ oldUrl: /how-to/discover-services/ **Goal:** call another plugin's (or another workspace member's) oRPC service from your app, end to end — declare the dependency so Aspire injects the callee's URL, then obtain a -fully typed client from `@netscript/sdk` that resolves that URL at request time. No registry, -no hardcoded `localhost:`, no codegen. +fully typed client from `@netscript/sdk` that resolves that URL at request time. The shared +contract supplies the method inputs, outputs, and declared errors; discovery or transport failures +remain non-defined failures. No registry, no hardcoded `localhost:`, no codegen. {{ comp.badge({ status: "alpha" }) }} @@ -35,7 +36,7 @@ import the contract, construct the client — that is the whole recipe. { name: "A NetScript workspace", type: "netscript init", desc: "An existing project with at least the caller (an app, plugin, or service) and the callee service already present. Run commands from the workspace root." }, { name: "The callee service", type: "services//", desc: "A target service that answers on its own port over /api/rpc/* — e.g. the example users service on its assigned port. See the Add a service recipe to create one." }, { name: "@netscript/sdk", type: "import alias", desc: "The SDK provides the typed client (createServiceClient / defineServices) and the discovery readers (@netscript/sdk/discovery). It is a workspace dependency of apps and consuming services." }, - { name: "The shared contract", type: "@/contracts", desc: "Both the service and the caller import the SAME oRPC contract object through the project alias, so the client's input/output types are inferred — never duplicated." }, + { name: "The shared contract", type: "@/contracts", desc: "Both the service and the caller import the SAME oRPC contract object through the project alias, so the client's input, output, and declared-error types are inferred — never duplicated." }, { name: "Aspire (for resolved URLs)", type: "aspire start", desc: "Aspire injects the services____http__ env vars that discovery reads. Without it you must set those vars yourself (see pitfalls)." } ] }) }} @@ -98,7 +99,8 @@ caller's environment. In the caller, import the **same contract object** the service implements and hand it to `createServiceClient`. The `serviceName` you pass is the discovery key — it must match the name you referenced in Step 1. The client is fully typed from the contract; there is no -generated client file to import. +generated client file to import. When its routes are composed from `baseContract`, every method +also carries the six standard declared errors through the returned promise. ```ts // web/src/clients/users.ts — a typed client for the discovered users service @@ -111,7 +113,7 @@ export const usersClient = createServiceClient({ serviceName: 'users', }); -// Fully inferred input/output — a renamed contract field is a compile error here. +// Input, output, and declared errors are inferred; a renamed field is a compile error. const { items } = await usersClient.list({ limit: 20 }); ``` @@ -134,25 +136,45 @@ default it targets `/api/rpc/v1/` over HTTP. ## Step 4 — Call it (and handle typed errors) -The client methods mirror the contract's procedure tree. Wrap calls in the SDK's `safe()` -helper to get a tuple instead of a throw, and narrow defined errors with `isDefinedError`: +The client methods mirror the contract's procedure tree. For a `baseContract` route, the defined +error codes are exactly `NOT_FOUND`, `VALIDATION_ERROR`, `UNAUTHORIZED`, `FORBIDDEN`, +`RATE_LIMITED`, and `SERVICE_UNAVAILABLE`. Wrap a call in the SDK's `safe()` helper, test the +literal `isSuccess` discriminant first, and then test `isDefined` on the failure. A plain error, +transport failure, or other non-defined value must be thrown or surfaced from that second branch; +it must not fall through to the successful output path. ```ts // web/src/routes/users.ts — typed call with safe-style error handling -import { isDefinedError, safe } from '@netscript/sdk/client'; +import { safe } from '@netscript/sdk/client'; import { usersClient } from '../clients/users.ts'; -const [error, result] = await safe(usersClient.list({ limit: 20 })); - -if (error && isDefinedError(error)) { - // error.code and error.data are typed from the contract - console.error('users.list failed:', error.code, error.data); -} else { - // result.items is the contract's output type - return result.items; +type UsersErrorCode = + | 'NOT_FOUND' + | 'VALIDATION_ERROR' + | 'UNAUTHORIZED' + | 'FORBIDDEN' + | 'RATE_LIMITED' + | 'SERVICE_UNAVAILABLE'; + +export async function listUsers() { + const result = await safe(usersClient.list({ limit: 20 })); + if (result.isSuccess) return result.data.items; + if (!result.isDefined) throw result.error; + + const code: UsersErrorCode = result.error.code; + if (result.error.code === 'NOT_FOUND') { + // The code selects NOT_FOUND's schema-derived data shape. + console.error(result.error.data.resourceType, result.error.data.resourceId); + } + throw new Error(`users.list failed: ${code}`, { cause: result.error }); } ``` +On the defined branch, `error.data` is selected by `error.code` from the schema declared in the +contract; it is not an open bag and it is not `any`. `isDefinedError(failure.error)` is the +predicate form for an already typed failure union. It preserves the defined members present in +that union rather than promoting arbitrary `unknown` values into contract errors. + ## Many services at once — `defineServices` When the caller talks to several services, `defineServices` builds the clients (plus @@ -209,7 +231,7 @@ run the command sequence from the workspace root: ```ts // web/src/clients/users.ts — the resulting file (contract in, typed client out) -import { createServiceClient, isDefinedError, safe } from '@netscript/sdk/client'; +import { createServiceClient, safe } from '@netscript/sdk/client'; import { UsersContractV1 } from '@my-app/contracts'; // serviceName is the discovery key — resolves services__users__http__0 at call time. @@ -218,13 +240,26 @@ export const usersClient = createServiceClient({ serviceName: 'users', }); +type UsersErrorCode = + | 'NOT_FOUND' + | 'VALIDATION_ERROR' + | 'UNAUTHORIZED' + | 'FORBIDDEN' + | 'RATE_LIMITED' + | 'SERVICE_UNAVAILABLE'; + // A typed call with safe-style error handling — no hardcoded URL anywhere. export async function firstUsers() { - const [error, result] = await safe(usersClient.list({ limit: 20 })); - if (error && isDefinedError(error)) { - throw new Error(`users.list failed: ${error.code}`); + const result = await safe(usersClient.list({ limit: 20 })); + if (result.isSuccess) return result.data.items; + if (!result.isDefined) throw result.error; + + const code: UsersErrorCode = result.error.code; + if (result.error.code === 'VALIDATION_ERROR') { + // VALIDATION_ERROR selects { formErrors, fieldErrors } from its contract schema. + console.error(result.error.data.formErrors, result.error.data.fieldErrors); } - return result.items; + throw new Error(`users.list failed: ${code}`, { cause: result.error }); } ``` @@ -246,7 +281,9 @@ deno task --cwd web dev With `aspire start` up, `usersClient.list({ limit: 20 })` resolves `services__users__http__0`, issues the oRPC call to `/api/rpc/v1/users`, and returns -the contract's typed `items`. No registry, no `localhost:`, no generated client file. +the contract's typed `items` on success. Its declared error union comes from the same contract; +non-defined runtime failures stay separate. No registry, no `localhost:`, no generated client +file. ## Production pitfalls diff --git a/docs/site/services-sdk/sdk.md b/docs/site/services-sdk/sdk.md index abd0ed200..dd928fa85 100644 --- a/docs/site/services-sdk/sdk.md +++ b/docs/site/services-sdk/sdk.md @@ -17,7 +17,9 @@ The `@netscript/sdk` is the **typed client and data layer** for NetScript: it tu wraps each contract action in a **cache-first query factory** (KV-backed SWR), and bridges those into [TanStack Query](https://tanstack.com/query) for islands. The contract object is the single source of truth — the *same* object the [service](/services-sdk/services/) -implements is the one the client imports, so caller and server **cannot drift**. +implements is the one the client imports, so its input, output, and declared-error types cannot +drift between caller and server. Transport failures and arbitrary thrown values are not declared +contract errors; `safe()` keeps those failures on its non-defined branch. {{ comp.badge({ status: "pre-1.0" }) }} {{ comp.diagram({ @@ -30,7 +32,8 @@ implements is the one the client imports, so caller and server **cannot drift**. The SDK is layered. **L1** is the typed client: `createServiceClient()` resolves a service URL from [Aspire service discovery](/explanation/aspire/) and returns a callable -object whose method signatures are inferred from the contract. **L2** wraps each contract +object whose method input, output, and declared-error union are inferred from the contract. +**L2** wraps each contract action in a query factory (`createQueryFactories`) that runs through a shared KV-backed cache provider and exposes `queryOptions`/`mutationOptions`/`clientKey`/`key`/`getCachedEntry` per action. **L3** is the `defineServices()` preset that builds clients, server query factories, @@ -62,8 +65,10 @@ lives: not inside server code, but in a standalone, versioned contract package t the client, **and** the OpenAPI/Scalar surface all import. And the derivation goes further than method types — from one contract action the factory derives the server KV cache key (`key()`), the client TanStack key (`clientKey()`), the `queryOptions`/`mutationOptions` helpers, and the -SWR cache reads. One edit to the contract updates the client, the docs surface, and the query -layer together — there is no separate turn to keep them in sync. +SWR cache reads. For routes built from `baseContract`, the same derivation carries the declared +error schemas through the client promise and `safe()`. One edit to those contract-owned types +updates the client, the docs surface, and the query layer together; transport and arbitrary thrown +failures remain runtime, non-defined failures rather than being promoted into that contract. This is how a production chat application built on NetScript wires its dashboard: the typed client is built straight off the contract type, so a route added to the contract shows @@ -110,7 +115,8 @@ export const ordersQueries = createQueryFactories({ orders: { contract: ordersContract, client: ordersClient }, }).orders; -// Direct typed call: `.list()` is fully inferred from the contract. +// Direct call: input, output, and declared errors are inferred from the contract. +// Calls still reject on failure; use safe() as shown below when you want a result value. const recent = await ordersClient.list({ limit: 10 }); ``` @@ -191,14 +197,57 @@ The L3 alternative builds all three layers from one map: label: "Island (TanStack hydration)", lang: "tsx", code: "// orders/(_islands)/OrdersQueryIsland.tsx\nimport { useQuery, useQueryClient } from '@netscript/fresh/query';\nimport { ordersQueries } from '@app/lib/orders.ts';\n\n// Same contract action → same query key as the server loader, so the island\n// hydrates from server state instead of refetching on mount.\nconst OrdersList = () => {\n const qc = useQueryClient();\n const orders = useQuery(ordersQueries.list.queryOptions({ limit: 20 }));\n // invalidate by prefix after a write:\n const refresh = () => qc.invalidateQueries({ queryKey: ordersQueries.list.clientKey() });\n return null; // render orders.data\n};" - }, - { - label: "Safe error narrowing", - lang: "ts", - code: "// services/orders/src/routers/v1.ts — service-to-service call\nimport { safe, isDefinedError } from '@netscript/sdk/client';\nimport { usersClient } from '@app/lib/users.ts';\n\nconst [error, user, isDefined] = await safe(usersClient.getById({ id }));\nif (error) {\n // narrow to a typed, contract-declared error\n if (isDefinedError(error)) return { code: error.code, status: error.status };\n throw error;\n}" } ] }) }} +## Safe error narrowing + +For a route built from `baseContract`, the defined channel is exactly `NOT_FOUND`, +`VALIDATION_ERROR`, `UNAUTHORIZED`, `FORBIDDEN`, `RATE_LIMITED`, or `SERVICE_UNAVAILABLE`. +`safe()` returns a `SafeResult`: test `isSuccess` first, then test `isDefined` on the failure. +That second test prevents a transport failure or arbitrary thrown value from falling through as a +successful result. On the defined branch, `data` is inferred from the schema registered for the +selected `code`; it is neither an open property bag nor `any`. + +```ts +import { baseContract } from '@netscript/contracts'; +import { createServiceClient, safe } from '@netscript/sdk/client'; +import { z } from 'zod'; + +const usersContract = { + getById: baseContract + .route({ method: 'GET', path: '/users/{id}' }) + .input(z.object({ id: z.string() })) + .output(z.object({ id: z.string(), name: z.string() })), +}; +const usersClient = createServiceClient({ contract: usersContract, serviceName: 'users' }); + +type StandardErrorCode = + | 'NOT_FOUND' + | 'VALIDATION_ERROR' + | 'UNAUTHORIZED' + | 'FORBIDDEN' + | 'RATE_LIMITED' + | 'SERVICE_UNAVAILABLE'; + +export async function loadUser(id: string) { + const result = await safe(usersClient.getById({ id })); + if (result.isSuccess) return result.data; + if (!result.isDefined) throw result.error; + + const code: StandardErrorCode = result.error.code; + if (result.error.code === 'NOT_FOUND') { + // NOT_FOUND selects its schema: { resourceType, resourceId }. + console.error(result.error.data.resourceType, result.error.data.resourceId); + } + throw new Error(`users.getById failed: ${code}`, { cause: result.error }); +} +``` + +`isDefinedError(failure.error)` is the predicate form when you already have a typed failure union. +It preserves the defined members present in that input type; it does not turn an arbitrary +`unknown` value into one of the contract's declared errors. + ## Production notes {{ comp callout { type: "important", title: "/cache and /discovery are server-only" } }} From db8aadd9542c38a305efffbd7017c56d0abf4e01 Mon Sep 17 00:00:00 2001 From: Rickylabs Date: Sat, 15 Aug 2026 23:39:10 +0200 Subject: [PATCH 7/8] chore(harness): record blocked S4 gate receipt --- .../context-pack.md | 39 +- .../drift.md | 16 + .../worklog.md | 373 +++++++++++++++++- 3 files changed, 391 insertions(+), 37 deletions(-) diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md index c6d3bcffc..7980e795b 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md @@ -2,21 +2,24 @@ ## Run Metadata -| Field | Value | -| -------------- | ------------------------------------------------------------ | -| Run ID | `fix-sdk-typed-error-channel--0.0.7-wave1` | -| Branch | `fix/sdk-typed-error-channel` | -| Current phase | `implementation` — S2 complete; awaiting fresh Tier-A review | -| Archetype | `1 — Small Contract` slice | -| Scope overlays | `docs` | +| Field | Value | +| -------------- | ---------------------------------------------------------- | +| Run ID | `fix-sdk-typed-error-channel--0.0.7-wave1` | +| Branch | `fix/sdk-typed-error-channel` | +| Current phase | `implementation` — S4 stopped on new raw doc-lint findings | +| Archetype | `1 — Small Contract` slice | +| Scope overlays | `docs` | ## Current state -PLAN-EVAL is terminal PASS at evaluator commit `f76a3c45b`; S1 Tier-A passed at `dc034d680`. S2 -preserves the exact six-key contract error union through the real `ServiceClient` promise marker, -`safe()`, and `isDefinedError()`. The two S1 suppressions are now positive exact-type assertions, -including code-specific data. #1466 still owns metadata definition/export; no metadata vocabulary or -acceptance claim was introduced. +PLAN-EVAL is terminal PASS at evaluator commit `f76a3c45b`; S1-S3 each passed fresh Tier-A. The +delivered product preserves the exact six-key contract error union through the real `ServiceClient` +promise marker, `safe()`, and `isDefinedError()`, and the two published docs pages carry one +compile-accurate narrative. S4 root check/test, scoped lint/format, quality, architecture, docs, +surface attribution, and publish dry-runs completed. Raw doc lint then proved new leaf-owned +private-type-reference findings (Contracts 9 -> 11; SDK 3 -> 13), so S4 stopped without product +changes. #1466 still owns metadata definition/export; no metadata vocabulary or acceptance claim was +introduced. ## Completed @@ -36,8 +39,9 @@ acceptance claim was introduced. ## Next steps -1. Commit and push S2, then post its receipt on draft PR #1671. -2. Stop for fresh Tier-A. Slice 3 remains unauthorized in this session. +1. Coordinator disposition is required for the new raw doc-lint findings. +2. Any correction must be a fresh authorized product slice; S4 remains run-artifact-only. +3. After correction, rerun the incomplete final matrix at a newly locked immutable content head. ## Key decisions @@ -68,8 +72,11 @@ No docs/S3-S4 product file was modified. - S2 consumer tests: PASS, 12/12 results across SDK doctest/query/desktop and Fresh extraction. - S2 lint and format: PASS after correcting two type-only imports; the initial lint red is retained in `worklog.md`. -- Root quality/publish/docs/JSR gates: not run; reserved for the final slice. -- Baseline JSR/publish inspection is recorded in `research.md` and `worklog.md`. +- S4 root check/test, scoped lint/format, quality, architecture, docs, and publish dry-runs: PASS. +- S4 raw `surface:diff`: known base RED plus exactly 15 authorized signature changes; after + subtracting those changes the 972-finding base/head sets have identical SHA-256 digests. +- S4 raw doc lint: blocking new RED; Contracts 9 -> 11 and SDK 3 -> 13. +- JSR audits and selected specifier/export guards: NOT RUN after the mandated S4 stop. ## Open questions diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md index 709de1356..2eed6a724 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md @@ -62,3 +62,19 @@ - **Action:** Corrected existing research/plan/context artifacts. No new file or debt entry was created; the coordinator owns any later issue for the benchmark prose. - **Scope:** Both locations remain outside the exact six-path ceiling; no product/docs edit here. + +## 2026-08-15 — S4 raw doc-lint findings exceed the pinned baselines + +- **What:** The plan-locked raw Contracts and SDK `deno doc --lint` gates are still red and now + contain leaf-owned private-type-reference findings beyond their base sets. +- **Source:** Executed raw lint at immutable head `c7cba6d9b`, then executed the same commands over + an archive of `main@0ef48c2e`; exact identity deltas are recorded in `worklog.md`. +- **Expected:** Pinned raw reds remain red with no new leaf-owned findings. +- **Actual:** Contracts changes from 9 to 11 (three additions, one removal); SDK changes from 3 to + 13 (ten additions). The additions name `baseContract`, `ServiceClientMethod`, + `ServiceClientShape`, `SafeFailure`, `SafeResult`, `isDefinedError`, and `safe`. +- **Severity:** blocking final-gate regression. +- **Action:** Stop S4. Do not edit S1/S2 product files in the run-artifact-only slice. Remaining JSR + and specifier/export guards are not run or claimed pending coordinator disposition. +- **Scope:** A correction necessarily returns to already-landed product paths and therefore needs a + fresh authorized implementation slice; no seventh path is implied or authorized here. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md index a62ee1fa1..9b4e8b7b2 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md @@ -81,9 +81,9 @@ forces the exact key union to stay synchronized. No code union or metadata vocab | 2026-08-15 | S2 | Consumers | Checked query, desktop, RFC, assignability, service-query, and Fresh consumers; ran SDK query/desktop plus Fresh extraction tests. | | 2026-08-15 | S2 | Reconcile | Observed S1 Tier-A PASS; no issue/label/checklist mutation, no #1348/#1466 touch, no scope adjustment, and S3 remains blocked. | | 2026-08-15 | S3 | Narrative | Both published pages now scope contract typing to input/output/declared errors and keep transport/arbitrary throws on the non-defined branch. | -| 2026-08-15 | S3 | Examples | Literal `isSuccess` then `isDefined` branches terminate every failure path; all six codes and code-selected schema data are shown. | -| 2026-08-15 | S3 | Compile proof | SDK fence passed `docs:snippets`; discover Step 3/4 and copyable function passed page-isolated checks with a real `baseContract` fixture. | -| 2026-08-15 | S3 | Gates | Source format, accuracy, source/rendered links, caveats, build, doctest, and focused example compilation passed; final gates remain S4-only. | +| 2026-08-15 | S3 | Examples | Literal `isSuccess` then `isDefined` branches terminate every failure path; all six codes and code-selected schema data are shown. | +| 2026-08-15 | S3 | Compile proof | SDK fence passed `docs:snippets`; discover Step 3/4 and copyable function passed page-isolated checks with a real `baseContract` fixture. | +| 2026-08-15 | S3 | Gates | Source format, accuracy, source/rendered links, caveats, build, doctest, and focused example compilation passed; final gates remain S4-only. | ## Gate results @@ -104,11 +104,11 @@ forces the exact key union to stay synchronized. No code union or metadata vocab | S3 docs source format | PASS | Structured receipt; exit 0, `Docs source format: OK` | | S3 docs accuracy | PASS | Structured receipt; 199 published source pages checked | | S3 SDK snippet compile | PASS | 36 Tier-1 candidates, 22 checked, 14 existing exemptions | -| S3 discover example compile | PASS | Step 3/4 pair and end-to-end function; real base-contract fixture | -| S3 SDK doctest | PASS | 3/3 results | -| S3 source links | PASS | 103 docs; 0 broken links, anchors, or orphans | -| S3 site verify | PASS | Build, rendered-output, rendered links, and caveats; raw exit 0 | -| S3 scope/lock hygiene | PASS | Four landed product/test files and `deno.lock` unchanged | +| S3 discover example compile | PASS | Step 3/4 pair and end-to-end function; real base-contract fixture | +| S3 SDK doctest | PASS | 3/3 results | +| S3 source links | PASS | 103 docs; 0 broken links, anchors, or orphans | +| S3 site verify | PASS | Build, rendered-output, rendered links, and caveats; raw exit 0 | +| S3 scope/lock hygiene | PASS | Four landed product/test files and `deno.lock` unchanged | | Root/final-slice gates | NOT RUN | Explicitly reserved for slice 4 | ### S1 structured JSON verdicts @@ -390,17 +390,17 @@ Root/final-slice gates were not run. ### S3 disposition receipt -| Planned location | Disposition | Final location and wording | -| ---------------- | ----------- | -------------------------- | -| `sdk.md:12-20` | Retained with required scope | `sdk.md:12-22` now limits non-drift to input, output, and declared-error types; transport/arbitrary throws are explicitly non-defined failures. | -| `sdk.md:31-38` | Retained and corrected | `sdk.md:33-41` says the L1 method's input, output, and declared-error union are inferred from the contract. | -| `sdk.md:58-70` | Retained and corrected | `sdk.md:61-71` says `baseContract` error schemas flow through the client promise and `safe()`, while runtime non-defined failures remain outside the contract. | -| `sdk.md:113-114` | Retained with cross-reference | `sdk.md:118-120` scopes direct-call inference to input/output/declared errors and points rejecting calls to the `safe()` section. | -| `sdk.md:196-198` | Replaced completely | `sdk.md:203-249` names all six codes, branches on literal `isSuccess` then `isDefined`, throws non-defined failures, demonstrates `NOT_FOUND` schema data, and scopes `isDefinedError`. | -| `discover-services.md:9-14` | Retained with required scope | `discover-services.md:11-15` names input/output/declared errors and keeps discovery/transport failures non-defined. | -| `discover-services.md:96-101,114-115` | Retained and corrected | `discover-services.md:99-118` states `baseContract` carries the six errors and the call comment includes declared-error inference; the prerequisite row also names declared errors. | -| `discover-services.md:135-154` | Replaced completely | `discover-services.md:137-176` names all six codes, uses literal discriminants, terminates every failure, demonstrates `NOT_FOUND` schema data, and scopes `isDefinedError`. | -| `discover-services.md:205-229` | Replaced completely | `discover-services.md:227-264` makes the copyable function return only from `isSuccess`, throws the non-defined branch, and demonstrates `VALIDATION_ERROR` schema data before throwing the defined failure. | +| Planned location | Disposition | Final location and wording | +| ------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `sdk.md:12-20` | Retained with required scope | `sdk.md:12-22` now limits non-drift to input, output, and declared-error types; transport/arbitrary throws are explicitly non-defined failures. | +| `sdk.md:31-38` | Retained and corrected | `sdk.md:33-41` says the L1 method's input, output, and declared-error union are inferred from the contract. | +| `sdk.md:58-70` | Retained and corrected | `sdk.md:61-71` says `baseContract` error schemas flow through the client promise and `safe()`, while runtime non-defined failures remain outside the contract. | +| `sdk.md:113-114` | Retained with cross-reference | `sdk.md:118-120` scopes direct-call inference to input/output/declared errors and points rejecting calls to the `safe()` section. | +| `sdk.md:196-198` | Replaced completely | `sdk.md:203-249` names all six codes, branches on literal `isSuccess` then `isDefined`, throws non-defined failures, demonstrates `NOT_FOUND` schema data, and scopes `isDefinedError`. | +| `discover-services.md:9-14` | Retained with required scope | `discover-services.md:11-15` names input/output/declared errors and keeps discovery/transport failures non-defined. | +| `discover-services.md:96-101,114-115` | Retained and corrected | `discover-services.md:99-118` states `baseContract` carries the six errors and the call comment includes declared-error inference; the prerequisite row also names declared errors. | +| `discover-services.md:135-154` | Replaced completely | `discover-services.md:137-176` names all six codes, uses literal discriminants, terminates every failure, demonstrates `NOT_FOUND` schema data, and scopes `isDefinedError`. | +| `discover-services.md:205-229` | Replaced completely | `discover-services.md:227-264` makes the copyable function return only from `isSuccess`, throws the non-defined branch, and demonstrates `VALIDATION_ERROR` schema data before throwing the defined failure. | ### S3 structured JSON verdicts @@ -432,8 +432,8 @@ Final docs-accuracy receipt: } ``` -This accuracy PASS proves only the invariants implemented by that script; it is not treated as -proof of the page-level error narrative. +This accuracy PASS proves only the invariants implemented by that script; it is not treated as proof +of the page-level error narrative. SDK-page snippet compilation: @@ -540,3 +540,334 @@ this slice and remain known red; no green claim is made. `typed-queue_test.ts` w S3 is complete and stops here for fresh Tier-A review. No S4 work started, no evaluator was launched, no acceptance box was ticked, and no metadata vocabulary or acceptance claim was introduced. + +## S4 final-gate receipt — stopped on new raw doc-lint findings + +All executed S4 receipts certify immutable content head `c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd`. +For every receipt below, the head read before the gate and the actual head verified afterward were +identical; no waiver was used. `deno.lock` also remained byte-identical throughout. + +The matrix stopped after raw Contracts and SDK `deno doc --lint` attribution proved new leaf-owned +private-type-reference findings. Correcting those findings would require edits to the already-landed +S1/S2 product files, which S4 forbids. The remaining JSR audits and specifier/export guards were +therefore not executed and are not claimed. + +### Executed structured verdicts + +```json +{ + "gateId": "root-check", + "gitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "actualGitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "waiver": null, + "outcome": "PASS", + "exitCode": 0, + "uncached": true, + "selection": { "filesSelected": 2925, "batches": 25, "failedBatches": 0 }, + "summary": { "totalOccurrences": 0, "uniqueOccurrences": 0, "uniqueCodes": 0, "uniquePaths": 0 } +} +``` + +```json +{ + "gateId": "root-test", + "gitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "actualGitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "waiver": null, + "outcome": "PASS", + "exitCode": 0, + "uncached": true, + "durationMs": 335918, + "summary": { + "passed": 4207, + "failed": 0, + "ignored": 19, + "totalResults": 4226, + "uniqueFailures": 0 + }, + "typedQueue1667Encountered": false +} +``` + +```json +{ + "gateId": "scoped-lint", + "gitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "actualGitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "waiver": null, + "outcome": "PASS", + "exitCode": 0, + "selection": { + "roots": ["packages/contracts", "packages/sdk"], + "filesSelected": 105, + "batches": 1 + }, + "summary": { "totalOccurrences": 0, "uniqueOccurrences": 0, "uniqueRules": 0, "uniquePaths": 0 } +} +``` + +```json +{ + "gateId": "scoped-fmt", + "gitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "actualGitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "waiver": null, + "outcome": "PASS", + "exitCode": 0, + "mode": "check", + "selection": { + "roots": ["packages/contracts", "packages/sdk"], + "filesSelected": 105, + "batches": 1 + }, + "summary": { "failedBatches": 0, "findings": 0, "ignoredFindings": 0 } +} +``` + +```json +{ + "gateId": "quality-scan", + "gitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "actualGitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "waiver": null, + "outcome": "PASS", + "exitCode": 0, + "rootCoverage": { + "workspaceMembers": 37, + "publishableMembersInsideBoundary": 35, + "uncoveredPublishedMembers": [] + }, + "scan": { + "roots": ["packages", "plugins", "docs/site"], + "findings": [], + "allowCount": 7, + "allowanceFailures": [] + }, + "existingAllowances": { "issue": 1276, "count": 7 } +} +``` + +```json +{ + "gateId": "arch-check", + "gitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "actualGitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "waiver": null, + "outcome": "PASS", + "exitCode": 0, + "uncachedUnderlyingCommands": true, + "affectedPackages": { + "contracts": { "fail": 0, "warn": 2, "info": 1 }, + "sdk": { "fail": 0, "warn": 1, "info": 1 } + }, + "notes": [ + "The repository's existing npm-catalog warnings were emitted but are non-failing.", + "zod-alignment PASS: zod@3.25.76 and zod@4.4.3; residual v3 owners unchanged." + ] +} +``` + +```json +{ + "gateId": "docs-source-format", + "gitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "actualGitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "waiver": null, + "outcome": "PASS", + "exitCode": 0, + "summary": "Docs source format: OK" +} +``` + +```json +{ + "gateId": "docs-accuracy", + "gitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "actualGitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "waiver": null, + "outcome": "PASS", + "exitCode": 0, + "summary": { + "publishedSourcePages": 199, + "shippedCorpusFiles": 181, + "publicCommands": "91/91 root/direct from 149 recursive paths", + "freshRootImports": 6 + }, + "existingWarning": "@tanstack/ai-preact@0.10.4 peer expects @tanstack/ai@^0.41.0; resolved 0.39.1" +} +``` + +### Public-surface attribution + +The raw gate remains RED at both base and head. Raw finding sets are not equal because this leaf is +an explicitly breaking published-contract change. The delta is exactly the 15 planned signature +changes on existing symbols/export paths; it adds no symbol or export path. After subtracting that +authorized delta, the base and head finding sets are byte-for-byte equivalent as normalized JSON. + +```json +{ + "gateId": "surface-diff", + "gitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "actualGitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "waiver": null, + "outcome": "RED", + "exitCode": 1, + "classification": "KNOWN BASE RED plus authorized breaking signature delta", + "base": { + "gitTree": "0ef48c2ec661a7e6d55ec2faf5def6ae7dd2e6eb", + "findingCount": 972, + "undeclaredMajorCount": 524, + "findingSetSha256": "55744a8522197fbb450349ca7185631188c97add132c356e173259875ae9406c" + }, + "head": { + "findingCount": 987, + "undeclaredMajorCount": 539, + "findingSetSha256": "7720483a169a126af09a3117875d410ac74171f94005b8386dd32f201e8690a1" + }, + "attribution": { + "authorizedDeltaCount": 15, + "unexpectedAdded": [], + "unexpectedRemoved": [], + "headAfterAuthorizedDeltaCount": 972, + "headAfterAuthorizedDeltaSha256": "55744a8522197fbb450349ca7185631188c97add132c356e173259875ae9406c", + "attributedBaselineSetsEqual": true, + "newExports": [] + }, + "authorizedSymbols": [ + "@netscript/contracts . baseContract", + "@netscript/sdk . SafeFailure", + "@netscript/sdk . SafeResult", + "@netscript/sdk . ServiceClientMethod", + "@netscript/sdk . ServiceClientShape", + "@netscript/sdk . isDefinedError", + "@netscript/sdk . safe", + "@netscript/sdk ./client SafeFailure", + "@netscript/sdk ./client SafeResult", + "@netscript/sdk ./client ServiceClientMethod", + "@netscript/sdk ./client ServiceClientShape", + "@netscript/sdk ./client isDefinedError", + "@netscript/sdk ./client safe", + "@netscript/sdk ./ports ServiceClientMethod", + "@netscript/sdk ./ports ServiceClientShape" + ] +} +``` + +### Publish dry-runs + +```json +{ + "gateId": "publish-dry-run", + "gitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "actualGitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "waiver": null, + "outcome": "PASS", + "exitCode": 0, + "summary": "Workspace dry run complete", + "existingWarnings": [ + "unanalyzable dynamic imports in unrelated packages", + "ignored npm lifecycle scripts" + ] +} +``` + +```json +{ + "gateId": "contracts-raw-publish-dry-run", + "gitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "actualGitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "waiver": null, + "outcome": "PASS", + "exitCode": 0, + "command": "deno publish --dry-run --allow-dirty --allow-slow-types", + "sanctionedWarning": "Publishing a library with slow types is not recommended" +} +``` + +```json +{ + "gateId": "sdk-raw-publish-dry-run", + "gitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "actualGitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "waiver": null, + "outcome": "PASS", + "exitCode": 0, + "command": "deno publish --dry-run --allow-dirty", + "actualSlowTypeDiagnostics": 0 +} +``` + +### Blocking raw doc-lint delta + +Both raw gates remain RED, but their finding sets are not merely the pinned baseline. Contracts has +three added findings and one removed minified upstream identity (`oc`), for a net +2. SDK has ten +added findings and no removals. Every added finding names an S1/S2 public signature or a private +helper/upstream alias introduced to express it. + +```json +{ + "gateId": "contracts-raw-doc-lint", + "gitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "actualGitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "waiver": null, + "outcome": "RED", + "exitCode": 1, + "base": { "gitTree": "0ef48c2ec661a7e6d55ec2faf5def6ae7dd2e6eb", "count": 9 }, + "head": { "count": 11 }, + "added": [ + ["baseContract", "ContractBuilder"], + ["baseContract", "Schema"], + ["baseContract", "BaseContractErrors"] + ], + "removed": [["baseContract", "oc"]], + "classification": "NEW LEAF-OWNED RED; S4 BLOCKER" +} +``` + +```json +{ + "gateId": "sdk-raw-doc-lint", + "gitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "actualGitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "waiver": null, + "outcome": "RED", + "exitCode": 1, + "base": { "gitTree": "0ef48c2ec661a7e6d55ec2faf5def6ae7dd2e6eb", "count": 3 }, + "head": { "count": 13 }, + "added": [ + ["ServiceClientMethod", "ThrowableError"], + ["ServiceClientMethod", "ClientPromiseResult"], + ["ServiceClientShape", "ProcedureErrorFromNode"], + ["SafeFailure", "ThrowableError"], + ["SafeFailure", "NonDefinedSafeFailure"], + ["SafeFailure", "DefinedSafeFailure"], + ["SafeResult", "ThrowableError"], + ["isDefinedError", "NarrowDefined"], + ["safe", "ThrowableError"], + ["safe", "ClientPromiseResult"] + ], + "removed": [], + "classification": "NEW LEAF-OWNED RED; S4 BLOCKER" +} +``` + +### Not executed after the mandated stop + +```json +{ + "gitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "actualGitHead": "c7cba6d9bd6aef1fbeb0e8e9778a5d979c8544bd", + "waiver": null, + "outcome": "NOT_RUN", + "reason": "S4 stopped when new doc-lint findings proved that source changes would be required", + "gates": [ + "contracts-jsr-audit", + "sdk-jsr-audit (known F-DOCT-5 red not reclassified)", + "netscript-jsr-specifiers", + "selected export guards" + ] +} +``` + +No Aspire, Docker, browser, `e2e:cli`, runtime lease, or evaluator was used. No issue, label, +checkbox, readiness, or metadata state was changed. From bd97a7c03a3fe9b9c2534fd53c9fb0518801bb31 Mon Sep 17 00:00:00 2001 From: Rickylabs Date: Sun, 16 Aug 2026 00:17:02 +0200 Subject: [PATCH 8/8] =?UTF-8?q?chore(harness):=20S4-R=20plan-only=20amendm?= =?UTF-8?q?ent=20=E2=80=94=20private-type-ref=20finding->correction=20map?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maps all 13 new leaf-owned deno-doc-lint private-type-ref findings from S4 (contracts 3, SDK 10) to individual type-safe corrections, verified against isolated deno doc --lint probes and the real @orpc/* .d.ts files. 12 of 13 resolve cleanly (SDK 10/10; contracts BaseContractErrors + Schema); one (baseContract -> ContractBuilder) is reported unresolved pending a coordinator ruling rather than planned around. Run-artifact-only: no product/test/docs/lock file touched. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01TYBPuyVoK8Bc8926DfnPah --- .../context-pack.md | 23 +- .../drift.md | 25 ++ .../supervisor.md | 27 ++ .../worklog.md | 268 ++++++++++++++++++ 4 files changed, 339 insertions(+), 4 deletions(-) diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md index 7980e795b..d31ae5a6c 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/context-pack.md @@ -6,7 +6,7 @@ | -------------- | ---------------------------------------------------------- | | Run ID | `fix-sdk-typed-error-channel--0.0.7-wave1` | | Branch | `fix/sdk-typed-error-channel` | -| Current phase | `implementation` — S4 stopped on new raw doc-lint findings | +| Current phase | `implementation` — S4 stopped; S4-R amended with finding→correction mapping, 1 finding unresolved | | Archetype | `1 — Small Contract` slice | | Scope overlays | `docs` | @@ -39,9 +39,21 @@ introduced. ## Next steps -1. Coordinator disposition is required for the new raw doc-lint findings. -2. Any correction must be a fresh authorized product slice; S4 remains run-artifact-only. -3. After correction, rerun the incomplete final matrix at a newly locked immutable content head. +1. S4-R (separate plan-only Claude session, `worklog.md` § S4-R) mapped all 13 new findings to + corrections; 12 of 13 resolve cleanly (all 10 SDK; `BaseContractErrors` and `Schema` in contracts). + `baseContract → ContractBuilder` is unresolved and needs a coordinator ruling: authorize a narrow + `src/public/mod.ts` re-export of `ContractBuilder`/`Schema`, or accept it as permanent leaf-owned + known-red debt. +2. Once the coordinator rules on `ContractBuilder`, the correction is a fresh authorized product slice + over the same three files (`contract-primitives.ts`, `errors.ts`, `service-client.ts`); S4-R itself + remains run-artifact-only, same as S4. +3. Two items are flagged as "reasoned, not `deno check`-proven" in S4-R and need verification at + implementation time: (a) `oc.errors(commonErrorMap)` still type-checks once `commonErrorMap` is + built from the public `ContractSchema`-typed schema aliases instead of the private Zod-typed ones; + (b) `ContractBuilder`'s generic constraint accepts `ContractSchema` in the + pre-`.input()`/`.output()` position. +4. After the repair lands, rerun the incomplete final matrix (JSR audits, specifier/export guards) + at a newly locked immutable content head. ## Key decisions @@ -77,6 +89,9 @@ No docs/S3-S4 product file was modified. subtracting those changes the 972-finding base/head sets have identical SHA-256 digests. - S4 raw doc lint: blocking new RED; Contracts 9 -> 11 and SDK 3 -> 13. - JSR audits and selected specifier/export guards: NOT RUN after the mandated S4 stop. +- S4-R (plan-only): 12/13 findings mapped to type-safe corrections (SDK 10/10, contracts 2/3); + `baseContract -> ContractBuilder` unresolved pending coordinator ruling. No gate was (re-)executed; + this is a mapping, not a rerun. ## Open questions diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md index 2eed6a724..9f77e55be 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/drift.md @@ -78,3 +78,28 @@ and specifier/export guards are not run or claimed pending coordinator disposition. - **Scope:** A correction necessarily returns to already-landed product paths and therefore needs a fresh authorized implementation slice; no seventh path is implied or authorized here. + +## 2026-08-15 — S4-R amends the stop with a finding→correction mapping; one finding unresolved + +- **What:** A separate, plan-only Claude session (native route, quota-exhausted Codex route excluded + per `CLAUDE.md`'s documentation-authoring exception) mapped all 13 new leaf-owned `private-type-ref` + findings (contracts 3, SDK 10) to individual type-safe corrections, verified against isolated + scratch probes of `deno doc --lint`'s actual resolution rules and against the real installed + `@orpc/*` `.d.ts` files. Full mapping is in `worklog.md` § S4-R. +- **Result:** 12 of 13 findings resolve cleanly (all 10 SDK findings; 2 of 3 contracts findings — + `BaseContractErrors` and `Schema`). One contracts finding — `baseContract → ContractBuilder` — does + **not** resolve within the three-file/no-new-export ceiling: it requires either re-exporting + `ContractBuilder`/`Schema` from `src/public/mod.ts` (the forbidden fourth path), duplicating oRPC's + entire builder class locally (rejected as an AP-1/AP-9 violation and a drift/maintenance hazard), or + reverting to an inference-erasing annotation (rejected — it reintroduces the exact "six codes erase + to open `ErrorMap`" regression #1350 exists to fix). +- **Severity:** significant — narrows the S4 blocker from 2 files/13 findings to 1 file/1 finding, but + does not close it; a coordinator ruling is still required before the repair slice can proceed. +- **Action:** Coordinator must choose: (a) authorize a narrow `src/public/mod.ts` re-export of + `ContractBuilder`/`Schema` type names as a scope amendment, or (b) accept `baseContract → + ContractBuilder` as permanent, irreducible, leaf-owned known-red debt alongside the existing pinned + baseline (contracts would then land at 9 base + 1 new = 10 total findings, down from today's 11, + with the 1 remaining new finding explicitly justified rather than silently carried). +- **Scope:** No product/test/docs/lock file was touched to produce this mapping. `#1348`/`#1466` + untouched. The repair itself remains a fresh, separately authorized implementation slice; this + session did not implement it. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/supervisor.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/supervisor.md index 190c0fc23..553ed838f 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/supervisor.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/supervisor.md @@ -28,3 +28,30 @@ Written at run start per `workflow/lane-policy.md` § Supervisor identity. - #1466 owns all metadata vocabulary, initialization, and exports. This leaf preserves only the existing empty fourth generic slot and must not touch the contracts public barrel. - No Aspire, Docker, `e2e:cli`, runtime lease, lock deletion, cache deletion, or reload. + +## S4-R Amendment Supervisor Identity + +Written at S4-R amendment start. S4-R is a separate, plan-only session from the S1-S4 generator +above; it amends the S4 stop with a finding→correction mapping and does not implement. + +| Field | Value | +| --- | --- | +| Model (requested) | native Claude · `claude-sonnet-5` · effort `high` · Remote Control, `documentation_review` lane | +| Model (observed) | `claude-sonnet-5`, effort `high`, `--remote-control` present in `respawnFlags` | +| Route verdict | **MATCHED** on model/effort/remote-control (directly verifiable via `argv`); the `documentation_review` lane is a routing-policy designation, not a CLI flag, so it is not independently verifiable from `respawnFlags` alone — consistent with intent (job name `NetScript 0.0.7 #1671 S4-R doc-amendment`) but not a byte-for-byte flag match. | +| OS PID | `1035332` | +| Session file | `~/.claude/sessions/1035332.json` | +| Session bridge id | `session_01TYBPuyVoK8Bc8926DfnPah` | +| Remote Control URL | `https://claude.ai/code/session_01TYBPuyVoK8Bc8926DfnPah` | +| Job id | `944115a6` | +| respawnFlags | `--permission-mode bypassPermissions --remote-control --name "NetScript 0.0.7 #1671 S4-R doc-amendment" --effort high --model claude-sonnet-5` | +| cwd | `/home/codex/repos/netscript-007-leaf-typed-error-s4r` | +| `git rev-parse HEAD` at start | `db8aadd9542c38a305efffbd7017c56d0abf4e01` | +| Branch | `s4r/doc-amendment` (scratch; pushes by explicit refspec to `fix/sdk-typed-error-channel`) | + +Route reason: the canonical Codex implementation route is quota-exhausted account-wide +(`usageLimitExceeded`, `willRetry:false`, `balance:"0"`, reset 2026-08-20 05:31), reproduced in a +fresh thread/fresh worktree. S4-R is run-artifact-only plan maintenance, inside the +documentation-authoring exception in `CLAUDE.md`, so it is routed to a native Claude session instead +of blocking on the exhausted Codex route. The product repair itself stays parked on the Codex route +and is out of scope for this session. diff --git a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md index 9b4e8b7b2..e3572525d 100644 --- a/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md +++ b/.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/worklog.md @@ -871,3 +871,271 @@ helper/upstream alias introduced to express it. No Aspire, Docker, browser, `e2e:cli`, runtime lease, or evaluator was used. No issue, label, checkbox, readiness, or metadata state was changed. + +## S4-R — plan-only amendment: finding→correction mapping + +Generator: native Claude session (see `supervisor.md` § S4-R Amendment Supervisor Identity). This +section amends the S4 stop with a per-finding disposition. No product/test/docs/lock file was +touched to produce this section; every mechanism below was verified against an isolated scratch +probe outside the repository tree (`deno doc --lint` on synthetic fixtures), not against the actual +S1/S2 source, and against the real `@orpc/client`/`@orpc/contract`/`@orpc/shared` `.d.ts` files +already resolved in this workspace's Deno cache. The repair itself remains a separate, freshly +authorized implementation slice. + +### How `deno doc --lint`'s `private-type-ref` actually resolves (probe-verified) + +The diagnostic is **syntactic and single-hop**, not a deep semantic graph walk: + +1. It flags a directly-named identifier in a public declaration's own text (including default type + parameter values) when that identifier resolves to a symbol not exported/reachable from the + entrypoint being linted. +2. It does **not** recurse into a flagged private type's own body — `BaseContractErrors` (private + relative to `packages/contracts/src/public/mod.ts`, though file-level `export`ed) is flagged when + named by `baseContract`, but its own internal reference to `MergedErrorMap`/`commonErrorMap` is + never separately checked, because `BaseContractErrors` itself isn't a root symbol being linted + from that entrypoint. +3. TypeScript's built-in utility types (`Omit`, `Extract`, `Exclude`, `Record`, `Pick`, `Readonly`) + are never themselves flagged; only their generic *arguments* are, if those name private types. +4. An anonymous inline object/tuple/conditional type literal with no name at all cannot be flagged — + there is nothing to resolve. +5. `typeof aBinding` is flagged exactly like a named type alias when `aBinding` is a private local or + an un-reexported import — **but** `typeof aPublicAliasForTheSameValue` is not flagged, because the + check resolves the specific identifier used, not the underlying value. + +Probe evidence (scratch fixture, not part of this repo): + +```text +public type 'usesTypeofDirectly' references private type 'secretConst' # typeof private const -> flagged +public type 'usesPrivateAliasByName' references private type 'PrivateAlias' # named private alias -> flagged +usesInlineAnon: { x: number } # inline literal -> clean +public type 'fnWithDefault' references private type 'PrivateDefault' # generic default -> flagged +fnWithErrorDefault # global default -> clean +public type 'usesOmit' references private type 'Base' # Omit flags Base, not Omit +usesPrivateBinding -> flagged; usesPublicBinding (typeof PublicName, same value) -> clean +``` + +Also confirmed directly against `packages/contracts/mod.ts`/`packages/sdk` entrypoints at head +`c7cba6d9b`: the 3 contracts and 10 SDK additions are exactly and only the ones the S4 receipt +already named; the other 4 pre-existing findings (7 in `BaseContractRoute`/`BaseContractOutputRoute`, +1 in `crud/create-crud-contract.ts`, plus SDK's 3 unrelated pre-existing findings in +`query-client.ts`/`query-client-factory.ts`/`plugin-streams-core`) are baseline noise, unchanged by +this leaf, and out of scope for this amendment. + +### Root-cause origin of each private type + +| Private type | Origin | Nature | +| --- | --- | --- | +| `ThrowableError` | `@orpc/shared`, re-exported by `@orpc/client` | `type ThrowableError = Registry extends {throwableError: infer T} ? T : Error` — with the empty ambient `Registry` interface (confirmed: no `declare module '@orpc/shared'` augmentation exists anywhere in this repo), this resolves to exactly `Error`. | +| `ClientPromiseResult` | `@orpc/client`/`@orpc/shared` | `= PromiseWithError = Promise & { __error?: { type: E } }` — a real `Promise` plus an optional phantom marker property that never exists at runtime, used only so `safe()` can recover `E` at the type level. | +| `NonDefinedSafeFailure`, `DefinedSafeFailure`, `NarrowDefined`, `DefinedErrorLike` | local to `errors.ts`, declared without `export` | Package-owned helper aliases already private by design; the finding is that the *public* `SafeFailure`/`isDefinedError` declarations name them directly. | +| `ProcedureErrorFromNode` | local to `service-client.ts`, declared without `export` | Reaches into oRPC's own `ErrorMap`/`ErrorFromErrorMap` (also private to us) to derive `TError` from a raw `~orpc.errorMap`. | +| `ContractBuilder`, `Schema` | `@orpc/contract` | `Schema = StandardSchemaV1` (a real standard, but not re-exported by us); `ContractBuilder` is oRPC's own chainable builder **class** (`.route()/.input()/.output()/.errors()/.meta()`, ~15+ members across 4 interfaces). | +| `BaseContractErrors` | local to `contract-primitives.ts`, `export`ed from the file but not re-exported by `src/public/mod.ts` | Private relative to the publish entrypoint, not relative to the file. | + +### Finding → correction mapping (13 findings, grouped by symbol per the requested table) + +| # | Finding (public type → private type) | Disposition | Correction | Why type-safe, not a suppression | +| - | --- | --- | --- | --- | +| 1 | `SafeFailure` → `ThrowableError` | **Inlined (default swap)** | `SafeFailure` (was `= ThrowableError`) | `ThrowableError` resolves to exactly `Error` in this dependency graph today (empty `Registry`, no augmentation anywhere in-repo). Same resolved default, spelled without importing the private name. Documented trade-off below. | +| 2 | `SafeFailure` → `NonDefinedSafeFailure` | **Restructured away (inlined)** | The public `SafeFailure` union's non-defined arm is written inline: `[Exclude, undefined, false, false] & { error: Exclude; data: undefined; isDefined: false; isSuccess: false }`, where `DEL` is `DefinedErrorLike`'s body inlined (see #3). | Byte-identical resolved type; only the source-level reference to the private alias is removed. `NonDefinedSafeFailure` itself stays declared, unchanged, for internal use by `createSafeFailure` (a non-exported function, never linted). | +| 3 | `SafeFailure` → `DefinedSafeFailure` | **Restructured away (inlined)** | Defined arm inlined the same way: `[Extract & DefinedError, undefined, true, false] & {...}`, with `DEL = Error & { readonly defined: boolean; readonly code: string; readonly status: number; readonly data: unknown }` (i.e. `DefinedErrorLike`'s current body) written out at each of the (now 4, across the two arms) points it's needed. `DefinedError` is already a public export — safe to name directly. | Same reasoning as #2. `DefinedErrorLike` itself is not itself flag-eligible until named from a public declaration; inlining its body avoids ever naming it there. Verified inline-literal exemption via probe. | +| 4 | `SafeResult` → `ThrowableError` | **Inlined (default swap)** | `SafeResult` | Same as #1; `SafeResult` only adds `SafeSuccess \| SafeFailure`, no other private ref. | +| 5 | `isDefinedError` → `NarrowDefined` | **Restructured away (inlined)** | `export function isDefinedError(error: T): error is Extract & DefinedError` | `NarrowDefined = Extract & DefinedError`; both `NarrowDefined` and `DefinedErrorLike` are inlined at the one call site that needs them publicly. Predicate result type is unchanged bit-for-bit. | +| 6 | `safe` → `ThrowableError` | **Inlined (default swap)** | `safe(...)` | Same as #1. | +| 7 | `safe` → `ClientPromiseResult` | **Restructured away (inlined)** | Parameter type becomes `promise: Promise & { __error?: { type: TError } }` (drop the `type ClientPromiseResult`/`type ThrowableError` imports from `@orpc/client`; keep the runtime `isDefinedError as orpcIsDefinedError` value import, which is not a type and was never flagged). | Structurally identical to `PromiseWithError` (`@orpc/shared`'s real definition, confirmed from the installed `.d.ts`). Any real oRPC client promise is structurally assignable to this literal; the phantom `__error` marker is preserved so `TError` inference still works — it is not dropped, only un-named. | +| 8 | `ServiceClientMethod` → `ThrowableError` | **Inlined (default swap)** | `ServiceClientMethod` | Same reasoning as #1, in `service-client.ts`. | +| 9 | `ServiceClientMethod` → `ClientPromiseResult` | **Restructured away (inlined)** | Return type becomes `(input: TInput, options?: ServiceRequestOptions) => Promise & { __error?: { type: TError } }` | Same reasoning as #7; duplicated locally in `service-client.ts` rather than shared across files, since sharing would require exporting a new internal type (forbidden). | +| 10 | `ServiceClientShape` → `ProcedureErrorFromNode` | **Restructured away (inlined, redesigned)** | See dedicated subsection below — this is the one finding that required redesigning the private helper's *body*, not just its call site. | See below. | +| 11 | `baseContract` → `BaseContractErrors` | **Restructured away (inlined)** | See dedicated subsection below. | See below. | +| 12 | `baseContract` → `Schema` | **Made public (existing NetScript alias)** | See dedicated subsection below. | See below. | +| 13 | `baseContract` → `ContractBuilder` | **UNRESOLVED — reports, does not plan around** | None proposed. | See below. | + +### #10 — `ServiceClientShape` → `ProcedureErrorFromNode` (service-client.ts) + +Naively inlining `ProcedureErrorFromNode`'s current body into `ServiceClientShape` does **not** +work: its body directly names `ErrorMap` and `ErrorFromErrorMap` (both private, from +`@orpc/contract`), so inlining would just relocate the private reference one level up (new findings +on those two names instead of on `ProcedureErrorFromNode`) — that is the "suppression wearing a +different hat" failure mode the brief warns about, so it was rejected. + +Correction: redesign `ProcedureErrorFromNode`'s body so it never names an oRPC-private type, mirroring +the same "NetScript-owned structural mirror" technique `ContractSchemaInput`/`ContractSchemaOutput`/ +`ContractProcedureMetadata` already use for input/output instead of depending on oRPC's own procedure +type. Inlined directly into `ServiceClientShape`: + +```ts +export type ServiceClientShape = TContract extends + ContractProcedureLike ? ServiceClientMethod< + ProcedureInputFromNode, + ProcedureOutputFromNode, + TContract extends { + readonly '~orpc': { + readonly errorMap: infer TErrorMap extends Record; + }; + } ? { + [K in keyof TErrorMap]: K extends string + ? TErrorMap[K] extends { readonly data?: infer TDataSchema } + ? Error & { + readonly defined: true; + readonly code: K; + readonly status: number; + readonly data: ContractSchemaOutput; + } + : never + : never; + }[keyof TErrorMap] | Error + : Error + > + : { [K in keyof TContract]: TContract[K] extends ContractLike ? ServiceClient : never }; +``` + +Why this is type-safe rather than a shim: it does not import `DefinedError` from `errors.ts` (that +would fail differently — see note below), it does not name `ErrorMap`/`ErrorFromErrorMap`/`ORPCError`, +and it reuses `ContractSchemaOutput`, already public and already used elsewhere in this file for the +identical purpose (schema → inferred value type). The intersection literal `Error & {readonly +defined: true; code: K; status: number; data: ContractSchemaOutput}` is structurally +identical to what `DefinedError>` would produce (confirmed +against `errors.ts`'s own `DefinedError` interface shape) and to what oRPC's real `ORPCError` +class actually looks like at runtime (`extends Error { readonly defined: boolean; code; status; data +}`, confirmed from `@orpc/client`'s installed `.d.ts`) — real thrown contract errors structurally +satisfy it. This is the same "duck-type against oRPC's real runtime shape" technique `errors.ts` +already uses for `DefinedErrorLike`, applied at a different call site. The constraint on `infer +TErrorMap` is narrowed from oRPC's own `ErrorMap` to an equivalent-width `Record` structural bound — width-compatible with any real `~orpc.errorMap` value, so +no real contract narrows out. + +**Note on why `DefinedError` (from `errors.ts`) was not imported instead:** `ports/mod.ts` (an +existing SDK entrypoint that re-exports `ServiceClientShape`) does not currently re-export +`DefinedError` — it is exported only via `client/mod.ts`. Since the private-type-ref check is +per-entrypoint, importing `DefinedError` into `service-client.ts` would make `ServiceClientShape` +pass when linted through `client/mod.ts` but newly fail when linted through `ports/mod.ts` (a type +public via one subpath is still "private" relative to another subpath that doesn't re-export it). +Fixing that would require editing `ports/mod.ts` — a fourth file, out of scope. The inline +intersection literal above avoids the cross-entrypoint dependency entirely. + +### #11 — `baseContract` → `BaseContractErrors` (contract-primitives.ts) + +Inlining `BaseContractErrors`'s current body (`MergedErrorMap, typeof +commonErrorMap>`) naively relocates the problem to `MergedErrorMap` (private) and then to +`commonErrorMap` (a private local const — probe-confirmed that `typeof aPrivateConst` is itself +flagged). Two facts resolve this cleanly: + +1. **`MergedErrorMap, T>` is provably equivalent to plain `T`.** Verified against + the real `@orpc/contract@1.14.6` types in a scratch fixture: `type ErrorsA = MergedErrorMap, CommonErrorMapType>` and `type ErrorsB = CommonErrorMapType` type-checked as mutually + assignable (`ErrorsA extends ErrorsB` and `ErrorsB extends ErrorsA` both `true`) — merging onto an + empty base is a no-op. So the third generic slot can be `typeof commonErrorMap`'s structure + directly, no `MergedErrorMap`/`Omit` needed. +2. **The six schemas already have public aliases.** `domain/schemas.ts` exports both the private + lowercase values used to build `commonErrorMap` (`notFoundErrorSchema`, etc.) *and* public + PascalCase re-exports of the identical value (`export const NotFoundErrorSchema = + notFoundErrorSchema;`, etc.), and the six PascalCase names are already re-exported through + `packages/contracts/src/public/mod.ts`. Probe-confirmed: `typeof PublicAlias` (for the same value) + is not flagged even though `typeof privateBinding` is. + +Correction — switch `contract-primitives.ts`'s import of the six schemas from the private lowercase +names to the public PascalCase names (same file, same `../domain/schemas.ts` module, no edit to +`domain/schemas.ts` itself), use them for both `commonErrorMap`'s value *and* the inlined annotation: + +```ts +Readonly<{ + NOT_FOUND: Readonly<{ status: 404; message: 'Resource not found'; data: typeof NotFoundErrorSchema }>; + VALIDATION_ERROR: Readonly<{ status: 422; message: 'Validation failed'; data: typeof ValidationErrorSchema }>; + UNAUTHORIZED: Readonly<{ status: 401; message: 'Authentication required'; data: typeof UnauthorizedErrorSchema }>; + FORBIDDEN: Readonly<{ status: 403; message: 'Access denied'; data: typeof ForbiddenErrorSchema }>; + RATE_LIMITED: Readonly<{ status: 429; message: 'Too many requests'; data: typeof RateLimitErrorSchema }>; + SERVICE_UNAVAILABLE: Readonly<{ status: 503; message: 'Service temporarily unavailable'; data: typeof ServiceUnavailableErrorSchema }>; +}> +``` + +This preserves the exact six literal codes and code-specific `data` types (the public PascalCase +export is declared `ContractSchema`-typed on the same runtime value, itself a public, +already-used NetScript structural type — see #12). **Verification owed at implementation time:** +`deno check` must confirm `oc.errors(commonErrorMap)`'s actual call-site constraint +(`U extends ErrorMap`) still accepts the value once `commonErrorMap` is built from the +`ContractSchema`-typed public aliases rather than the raw Zod-typed private ones; this was verified +structurally (standard-schema shape) but not against the exact live `oc.errors` overload. + +### #12 — `baseContract` → `Schema` (contract-primitives.ts) + +`Schema` is oRPC's private alias for `StandardSchemaV1`. NetScript already has a +public structural mirror of exactly this shape: `ContractSchema` +(`domain/schema-types.ts`, re-exported via `src/public/mod.ts`), documented in-file as "Standard +Schema metadata consumed by oRPC and other validator-neutral callers" — i.e. it was built to be +duck-type-compatible with standard-schema consumers such as oRPC's own builder. + +Correction: replace both `Schema` generic arguments on `baseContract`'s annotation +with `ContractSchema`. This makes the reference public (option "make the referenced +type public" from the diagnostic's own hint) using a name NetScript already exports — no new export +is introduced. **Verification owed at implementation time:** `deno check` must confirm +`ContractBuilder`'s generic constraint (`TInputSchema/TOutputSchema extends AnySchema`) accepts +`ContractSchema` in the "no `.input()`/`.output()` called yet" position, and that +the class's covariance/method-checking doesn't reject the substitution. The structural shapes match +(`~standard: {version, vendor, validate}`), but class-generic substitutability was reasoned, not +`deno check`-proven, in this plan-only turn. + +### #13 — `baseContract` → `ContractBuilder` (contract-primitives.ts) — UNRESOLVED + +This is the one finding this amendment does **not** resolve, and reports rather than plans around, +per the brief's explicit instruction. + +`ContractBuilder` is oRPC's own chainable builder **class** (`ContractProcedureBuilder` / +`...WithInput` / `...WithOutput` / `...WithInputOutput`, ~15+ chained methods, extending +`ContractProcedure`). An explicit type annotation on `baseContract` is mandatory — `deno doc --lint` +requires one (an unannotated `const` initialized from a generic call is a different, worse +diagnostic class: `missing-explicit-type`), and the annotation must name the actual return type of +`oc.errors(commonErrorMap)`, which is a `ContractBuilder<...>` instance. + +Three paths were considered and all three are blocked by this leaf's own constraints: + +1. **Re-export `ContractBuilder` (and `Schema`, transitively) from `src/public/mod.ts`.** This is the + textbook "make the referenced type public" fix the diagnostic itself suggests — but `src/public/mod.ts` + is the explicitly forbidden fourth file, and doing so is also a new export name, both prohibited + by this amendment's scope. +2. **Locally reconstruct `ContractBuilder`'s full structural surface as an inline/private type.** + Technically possible (TypeScript permits anonymous structural interfaces with methods) but this + means hand-duplicating oRPC's entire builder algebra (`.route()/.input()/.output()/.errors()/.meta()` + across four builder interfaces plus the base `ContractProcedure` members) inside NetScript's own + package, permanently coupled to oRPC's exact internal shape and guaranteed to drift on the next + `@orpc/contract` version bump. This is exactly what doctrine AP-1/AP-9 forbid ("do not grow + `errors.ts`/contract primitives into a generic error framework or invent a second client algebra") + and what the plan's own risk register already flags ("tight builder annotation breaks + CRUD/plugin/handler inference"). Rejected as unsafe, not merely inconvenient. +3. **Revert to an inference-erasing annotation** (e.g. `ReturnType`, the pre-leaf + base state, which only names `oc` and reproduces the *already-pinned* baseline finding instead of + a new one). This was the actual base-commit approach. Rejected: `ReturnType` on an *uninstantiated* + generic function collapses `oc.errors`'s type parameter to its `ErrorMap` upper bound, which erases + the exact six literal codes back to oRPC's full ~13-code vocabulary — the precise regression #1350 + exists to fix. Directly violates the "accepted exact six error codes" invariant this amendment is + bound to preserve. Rejected. + +No fourth option was found that both (a) keeps the explicit, literal-preserving annotation and (b) +never names `ContractBuilder`. This is reported as a genuine architectural limit of the three-file, +no-new-export ceiling, not an oversight — a coordinator ruling is needed on whether to authorize a +narrow, single-purpose re-export of `ContractBuilder`/`Schema` type names only (a scope amendment +touching `src/public/mod.ts`), or to accept `baseContract → ContractBuilder` as permanent, +irreducible, leaf-owned known-red debt alongside the existing pinned baseline. + +### Surface-delta effect + +Every proposed correction above (#1–#12) is **purely notational**: each rewrites how a type is +*spelled* in source (inlined vs. named, or renamed to an existing public alias for the identical +value), without changing what the type *resolves to*. `SafeFailure` resolves to the +same shape as `SafeFailure` did (given the empty `Registry`); the inlined +`ProcedureErrorFromNode` replacement produces the same per-code `DefinedError`-shaped union the +locked plan already specified. None of this changes any of the 15 authorized breaking signature +changes recorded in `worklog.md`'s `surface-diff` receipt — the delta stays exactly 15, neither +exceeded nor silently shrunk. Finding #13 (`ContractBuilder`) is unresolved but also does not change +the surface delta; it leaves `baseContract`'s resolved type exactly as S1 already locked it (only its +open doc-lint finding count is unchanged: contracts moves from 11 to net **9 pinned-baseline-parity + +1 new (`ContractBuilder`, replacing the pinned `oc`)** if this specific mapping is implemented as +proposed — i.e. from +2 new down to +1 new, not to zero, pending the coordinator ruling on #13). SDK +resolves cleanly to **0 new** (all 10 findings dispositioned without residue). + +### What this amendment does not do + +- It does not implement any of the above in `contract-primitives.ts`, `errors.ts`, or + `service-client.ts`. Those remain at the S1–S3 landed content; nothing in the three authorized + product files changed as part of S4-R. +- It does not add, suppress, or ignore any doc-lint diagnostic. +- It does not touch `packages/contracts/src/public/mod.ts`, `#1348`, or `#1466`. +- It does not tick any PR checkbox or change `status:` labels.