fix(sdk): preserve contract errors through safe() and isDefinedError - #1671
fix(sdk): preserve contract errors through safe() and isDefinedError#1671rickylabs wants to merge 8 commits into
Conversation
|
[PHASE: PLAN] Research and the concrete plan are committed in Findings
Next
|
…error Author stopped on three real scope items at c7a6f3d (PR #1671). Dispatched the ruling: approve service-client.ts as the sixth path, deny public/mod.ts and any NetScriptProcedureMeta definition/export, treat the stale contracts README/benchmark prose as a seventh-path rescope. Records in drift.md that my brief mis-stated #1350/#1466 metadata ownership by quoting an umbrella checkbox instead of that body's normative header, #1350's owner comment 5227724542, and the live #1466 child. The author caught it; uncaught it would have duplicated #1466. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pCd2QWkCscgZpVdjcUPST
|
[PHASE: PLAN] Plan amendment receipt for coordinator ruling; implementation remains untouched. Receipt
Exact six-path ceiling
Any seventh product, test, or docs path is a rescope requiring a fresh coordinator ruling. The contracts public barrel, contracts README, and benchmark reference prose are not authorized. Locked ownership and proof
Next
|
Measured only after the author was fully idle 132s. Amendment is exactly six run-artifact paths, zero others, plan-only against base. Six-path ceiling with seventh declared a rescope; public/mod.ts conditional genuinely removed rather than annotated; acceptance revised to six literals plus Record<never, never> meta-slot non-erasure with no dependency on NetScriptProcedureMeta; breaking published-contract disclosure retained at full strength. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pCd2QWkCscgZpVdjcUPST
Separate-session Fable 5 medium evaluation of PR #1671 plan head 2fa2f71. Verdict PASS with five non-blocking advisories. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RuDy1h3UiCkLzo1PLk5Sc
PLAN-EVAL —
|
| Field | Value |
|---|---|
| Evaluator | Claude · claude-fable-5 · medium · Remote Control (formal_plan_evaluation, route matched) — session 50898ac7-6e79-4f31-ae18-694bb36b7c79, separate from plan author 01a006f3… |
| Evaluated head | 2fa2f71dc5b498c16221461439e53b9f5dc1d5d5 (base 0ef48c2ec) |
| Artifact | .llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/plan-eval.md @ f76a3c45b |
Findings (all re-derived; executed evidence in the artifact)
- Phase invariant —
git diff --name-only 0ef48c2ec..HEADis.llm/runs/only. PASS. - Ownership — Epic: Typed SDK client contributions — credentials, transport policy, metadata, and cache-safe extensions #1348 header, [sdk-client S1] fix(sdk): preserve contract errors through safe() and isDefinedError #1350 comment 5227724542, open [sdk-client S2] feat(sdk): define NetScriptProcedureMeta without erasing contract errors #1466, RFC 0001 stage table all agree: [sdk-client S1] fix(sdk): preserve contract errors through safe() and isDefinedError #1350 = literal-preserving error repair + four-generic builder spelling; [sdk-client S2] feat(sdk): define NetScriptProcedureMeta without erasing contract errors #1466 =
NetScriptProcedureMeta. Plan keeps the fourth slot asRecord<never, never>with no vocabulary/export; the metaEqualguard is already green at base (verified), so no acceptance item depends on [sdk-client S2] feat(sdk): define NetScriptProcedureMeta without erasing contract errors #1466. PASS. - Scope — six paths exact; seventh = rescope;
public/mod.tssurvives only as a denial. Probe:createORPCClient(link) as { m: (i) => ClientPromiseResult<Out, ErrorFromErrorMap<…>> }compiles, soports/service-client.tsis sufficient andclient/service-client.ts:65need not change. No baseContract handler uses an undeclared error code. PASS. - Breaking change — matches upstream 1.14.6 (
data: undefined, two literal arms,TError = ThrowableError); repo search finds no in-repoSafeFailureconstruction /data === null. Correct, not over-declared. PASS. - Proof/docs — plan snippet at base:
TS18046 'result.error' is of type 'unknown';isDefinedErrorpath:TS2339 … 'never';'NOT_DECLARED'accepted at base. Every error-story line in both pages is in the disposition tables; retained lines stay true. PASS.
Advisories (non-blocking)
- A1 Record the RED as both TS18046 (
unknown, plan snippet) and TS2339 (never,isDefinedError). - A2
packages/sdkhas nozodmapping (probe → TS2307). Build the fixture route from@netscript/contracts-exported schemas; azodentry inpackages/sdk/deno.jsonwould be a seventh path. - A3 Locked
type SafeFailure<TError>drops today's default — keep= ThrowableErroror declare the extra break. - A4 "Tracked debt" for bench prose has no
arch-debt.mdentry; add one in slice 4. Research §3 overstatespackages/contracts/README.md(it does not describe erasure). - A5 Close gate: [sdk-client S1] fix(sdk): preserve contract errors through safe() and isDefinedError #1350 acceptance boxes still mention metadata; state the split under comment 5227724542, do not tick metadata boxes.
Implementation may begin at the plan head. No product/test/docs files were modified by this evaluation.
PASS at evaluated head 2fa2f71; artifact-only commit f76a3c4 verified. S1 released to the same author for plan slice 1 only (contract-primitives.ts + readme-doctest_test.ts), with slices 2-4 fenced off including service-client.ts. Carries A1 (both TS18046 and TS2339 REDs, not just TS2339), A2 (exported contracts schemas, no SDK zod mapping), A3 (retain SafeFailure default), A4 (no arch-debt.md or new file; supervisor files any dedup issue), A5 (tick no metadata boxes). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pCd2QWkCscgZpVdjcUPST
|
[PHASE: IMPL] — S1 receipt Slice 1 is committed and pushed at Scope observedChanged product/test paths:
The only other changed paths are the five pre-existing run artifacts ( Required RED — captured once before implementationThe fixture uses real package exports from {
"exitCode": 1,
"command": "deno check --unstable-kv <files>",
"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"
}
]
}The RED was not rerun seeking a tidier failure. S1 contractThe base builder is now explicitly four-generic: ContractBuilder<
Schema<unknown, unknown>,
Schema<unknown, unknown>,
BaseContractErrors,
Record<never, never>
>
The fourth slot remains exactly Structured gate verdictsFinal focused check: {
"exitCode": 0,
"command": "deno check --unstable-kv <files>",
"selection": { "filesSelected": 4, "batches": 1, "failedBatches": 0 },
"summary": {
"totalOccurrences": 0,
"uniqueOccurrences": 0,
"uniqueCodes": 0,
"uniquePaths": 0
},
"groups": []
}The four checked files were the two S1 files plus the affected CRUD and workers type consumers. Two implementation iterations were also recorded honestly in Focused tests: {
"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: {
"exitCode": 0,
"selection": { "filesSelected": 2, "batches": 1 },
"summary": {
"totalOccurrences": 0,
"uniqueOccurrences": 0,
"uniqueRules": 0,
"uniquePaths": 0
},
"groups": []
}Focused format write: {
"exitCode": 0,
"command": "deno fmt",
"mode": "write",
"summary": {
"filesSelected": 2,
"batches": 1,
"failedBatches": 0,
"findings": 0,
"ignoredFindings": 0
},
"findings": []
}Final format check: {
"exitCode": 0,
"command": "deno fmt --check",
"mode": "check",
"summary": {
"filesSelected": 2,
"batches": 1,
"failedBatches": 0,
"findings": 0,
"ignoredFindings": 0
},
"findings": []
}Root/final-slice gates were not run, as required. Push and PR posture
STOP: waiting for a fresh Tier-A review before any authorization for S2. |
Scope exact: two authorized files plus five existing artifacts, no seventh path, no new file, slice-2 files untouched. A1 satisfied precisely - TS18046 and TS2339 captured together in one structured run and not rerun. No NetScriptProcedureMeta anywhere in contracts/sdk src; fourth generic preserved as Record<never, never>. Gates: check 105 files 0 occurrences, doctest 2/0, contracts+sdk 77/0. Flags a non-blocking A5 observation: three process boxes with commit evidence are ticked, none claiming metadata acceptance. My brief was stricter than the ruling, so this is my over-hardening, not an author violation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pCd2QWkCscgZpVdjcUPST
|
[PHASE: IMPL] — S2 receipt Slice 2 is committed and pushed at Scope observedChanged S2 product/test paths:
The only other changes are the existing Error-channel contract
The two former suppressionsBoth S2-specific The former safe-path marker became this exact positive assertion: type _SafePreservesExactErrorCode = Assert<
Equal<typeof discriminated.error.code, ExpectedBaseErrorCode>
>;
const preservedCode: ExpectedBaseErrorCode = discriminated.error.code;The same branch positively proves code-specific The former guard-path marker became: type _GuardNarrowsToExactDefinedError = Assert<
Equal<typeof guarded.error.code, ExpectedBaseErrorCode>
>;
const definedError: DefinedError<ExpectedBaseErrorCode> = guarded.error;That guarded branch also proves Structured verdictsFinal focused check, including 15 SDK/Fresh source, runtime-test, and type-fixture consumers: {
"exitCode": 0,
"command": "deno check --unstable-kv <files>",
"selection": { "filesSelected": 15, "batches": 1, "failedBatches": 0 },
"summary": {
"totalOccurrences": 0,
"uniqueOccurrences": 0,
"uniqueCodes": 0,
"uniquePaths": 0
},
"groups": []
}Final focused and consumer tests: {
"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 doctest-only run independently passed 3/3, including the new runtime test that verifies defined-error identity, plain-error rejection, both literal discriminants, and all four tuple positions. The first lint run was honestly red: {
"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"]
}]
}Both schema imports were marked type-only. Final lint: {
"exitCode": 0,
"selection": { "filesSelected": 3, "batches": 1 },
"summary": {
"totalOccurrences": 0,
"uniqueOccurrences": 0,
"uniqueRules": 0,
"uniquePaths": 0
},
"groups": []
}Final format write: {
"exitCode": 0,
"command": "deno fmt",
"mode": "write",
"summary": {
"filesSelected": 3,
"batches": 1,
"failedBatches": 0,
"findings": 0,
"ignoredFindings": 0
},
"findings": []
}Final format check: {
"exitCode": 0,
"command": "deno fmt --check",
"mode": "check",
"summary": {
"filesSelected": 3,
"batches": 1,
"failedBatches": 0,
"findings": 0,
"ignoredFindings": 0
},
"findings": []
}Additional implementation checks were also green: initial three-file check, post-assertion one-file check, and the repeated final 15-file consumer check. The broader 12-test consumer run was repeated after final formatting and remained 12/12. Consumer compatibility evidenceThe 15-file check covered:
Runtime tests covered SDK doctest/error tuples, query factories, desktop client behavior, and Fresh error extraction. Live service integration suites were not run because this slice forbids a runtime lease. Push and PR posture
Root/final-slice gates were not run. STOP: waiting for a fresh Tier-A review before any slice 3 authorization. |
Both @ts-expect-error markers became positive Assert<Equal<...>> assertions with no suppression added; the author additionally closed a vacuity hole by asserting IsAny<BaseMeta> is false alongside the Record<never, never> equality. SafeFailure default retained at errors.ts:71; no zod map or ambient redeclaration; NetScriptProcedureMeta still absent; no extra boxes. Worklog's 16 deletions verified as a table reflow with the S1 RED row surviving verbatim. Gates: check 105 files 0 occurrences, doctest 3/0, sdk+contracts 78/0, lint 0, fmt 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pCd2QWkCscgZpVdjcUPST
|
[PHASE: IMPL] — S3 receipt Slice 3 is committed and pushed at Scope observedChanged exactly the two S3 docs paths plus the existing run worklog:
The four landed product/test files and Disposition-table receipt
Both pages name exactly Compile evidenceThe new SDK error fence compiles directly against real workspace entrypoints: {
"gate": "docs:snippets",
"outcome": "PASS",
"exitCode": 0,
"census": { "scanned": 581, "tsLike": 298, "tier1": 36, "checked": 22, "exempt": 14, "outsideFloor": 262, "malformed": 0 }
}Discover-services is outside the current snippet day-one floor, so its changed examples were compiled page-isolated in two batches. The only supplied prelude replaced the project-local contract import with a real {
"gate": "discover-services-error-examples",
"verdict": "PASS",
"results": [
{ "source": [105, 146], "code": 0, "rootLockUnchanged": true, "temporaryLockRewritten": true },
{ "source": [232], "code": 0, "rootLockUnchanged": true, "temporaryLockRewritten": true }
]
}The existing SDK doctest remained green: {
"schemaVersion": 1,
"exitCode": 0,
"summary": { "passed": 3, "failed": 0, "ignored": 0, "totalResults": 3 },
"failures": []
}Gate verdicts{
"docsSourceFormat": { "outcome": "PASS", "exitCode": 0, "durationMs": 444, "summary": "Docs source format: OK" },
"docsAccuracy": { "outcome": "PASS", "exitCode": 0, "durationMs": 11532, "publishedSourcePages": 199 },
"docsSourceLinks": { "outcome": "PASS", "exitCode": 0, "docs": 103, "brokenLinks": 0, "brokenAnchors": 0, "orphans": 0 },
"siteVerify": { "outcome": "PASS", "exitCode": 0, "covers": ["source-format", "build", "rendered-output", "rendered-links", "caveats"] },
"scope": { "outcome": "PASS", "productAndTestFilesByteIdentical": true, "denoLockUnchanged": true, "newFiles": 0 }
}The docs-accuracy PASS is claimed only for invariants that script implements; it is not used as proof of the page-level error narrative. The known raw-red Push and PR posture
STOP: waiting for fresh Tier-A review before any S4 authorization. Lock-hygiene guardrail (post-commit attestation)The guardrail arrived after S3 had been committed and this receipt posted. The exact requested comparison was nevertheless run against pushed head Working-copy |
Scope exact: two docs pages plus one existing artifact, packages/ untouched. Lock verified LOCK-IDENTICAL to ca7ade4. All six authoritative literals (NOT_FOUND, VALIDATION_ERROR, UNAUTHORIZED, FORBIDDEN, RATE_LIMITED, SERVICE_UNAVAILABLE) appear on both pages; a guessed set had shown a false 5-of-6 gap, so the set was derived from source before filing anything. Literal isSuccess/isDefined branching present on both; no NetScriptProcedureMeta, no metadata claim. Gates: docs-source-format PASS, docs-accuracy PASS, doctest 3/0. Records that the reported deno.lock zod drift was not reproducible, so the guardrail was dispatched as preventive rather than corrective. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pCd2QWkCscgZpVdjcUPST
|
@/home/codex/.claude/jobs/944115a6/tmp/s4r-comment-body.md |
S4 conduct is correct: run-artifact-only (0 non-run-artifact paths), LOCK-IDENTICAL, receipts bound gitHead == actualGitHead with no waiver, unreached gates recorded NOT_RUN rather than omitted, and the author stopped instead of editing S1/S2 source to green its own gate. Blocker verified independently: SDK raw doc-lint is 3 at base 0ef48c2 and 13 at head. Ten new private-type-ref diagnostics on ServiceClientMethod, ServiceClientShape, SafeFailure, SafeResult, isDefinedError and safe - a genuine leaf-owned published-surface regression, correctly not laundered into the known F-DOCT-5 baseline. Needs a coordinator ruling for a bounded source-touching S5. IMPL-EVAL not requested at this head. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pCd2QWkCscgZpVdjcUPST
codex-resume delivers to thread 01a006f3 (rollout grep 2->4, mtime +143s) but the backend refuses: usage limit hit, retry at 2026-08-20 05:31, 1,576,949 tokens used. Leaf unchanged at db8aadd, clean, lock identical. Records the discipline gap this exposed: rollout-grep proves a message was written, not that a model could act on it. Delivery proof and liveness proof are separate checks; never arm a head-change watcher on an unproven-live author. No retries, no supervisor substitution as implementer. Coordinator decision required: credits, wait for reset, or lane escalation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pCd2QWkCscgZpVdjcUPST
One-shot test as directed: fresh gpt-5.6-sol/medium session, new thread 01a00767, dedicated detached worktree at db8aadd, leaf sender preserved. Identical refusal - usageLimitExceeded, willRetry false, hasCredits false, balance 0, limitId premium, turn failed in 2147ms. Quota is account-wide, not thread-local. Parked: temp worktree and scratch branch removed, s4r never pushed, leaf clean at db8aadd == remote == PR, lock identical, draft status:plan. No retries, no substitute authorship. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pCd2QWkCscgZpVdjcUPST
…correction map 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TYBPuyVoK8Bc8926DfnPah
Summary
Plans the #1350 repair so contract-defined oRPC errors retain literal
codeand schema-deriveddatafrombaseContract, throughServiceClient, intosafe()andisDefinedError().This PR still contains research and harness plan artifacts only. The coordinator amendment authorizes the real client loss point as the sixth path, assigns all metadata vocabulary to #1466, and leaves implementation blocked on fresh Tier-A review followed by a separate-session PLAN-EVAL PASS.
Scope
packages/contracts+packages/sdk.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/packages/contracts/src/application/contract-primitives.tspackages/sdk/src/client/errors.tspackages/sdk/src/ports/service-client.tspackages/sdk/tests/readme-doctest_test.tsdocs/site/services-sdk/sdk.mddocs/site/services-sdk/how-to/discover-services.mdpackages/contracts/src/public/mod.ts, all metadata definition/export work,packages/contracts/README.md, and benchmark reference prose.Decided error contract
The contract builder uses an explicit four-generic
ContractBuilder. Its error map is derived fromtypeof commonErrorMap, never openErrorMap, preserving all six declared codes and their schema-derived data. Its fourth generic remains exactlyRecord<never, never>.The acceptance fixture proves the existing
~orpc.metaslot isRecord<never, never>and notany/unknown. It defines no metadata fields, initializes no metadata, exports no metadata type, and makes no claim that #1466's later semantic fixture is proven.No new SDK or contracts export name is introduced; internal narrowing helpers remain off every barrel.
Breaking-change statement
Breaking published contract; not patch-level. The current public
SafeFailurehas one arm withisDefined: booleanand failuredata: null. The accepted/upstream-compatible contract has two literal-discriminated arms and failuredata: undefined. Tightening the base error map also intentionally rejects undeclared codes. Repository consumers were searched; external JSR consumers may need to update assertions or constructedSafeFailurevalues.Acceptance proof
The real-export fixture must first reproduce the base RED:
After implementation, the same fixture must prove:
NOT_FOUND | VALIDATION_ERROR | UNAUTHORIZED | FORBIDDEN | RATE_LIMITED | SERVICE_UNAVAILABLEsurvive through the realServiceClientpromise,safe(), andisDefinedError();error.dataremains schema-specific by code;NOT_DECLAREDis rejected;Errorremains in theisDefined: falsearm;Record<never, never>and notany/unknown, without inventing metadata vocabulary.Slices
c7a6f3d322fa2f71dcValidation
deno doc --filter safe|isDefinedError|SafeResult packages/sdk/src/client/mod.ts— confirmed the current published erasuredeno doc --filter baseContract packages/contracts/mod.ts— confirmedReturnType<typeof oc.errors>deno eval --check --unstable-kv ...— expected base RED reproduced: TS2339,error.codedoes not exist onneverrgconsumer searches — mapped SDK, Fresh, CRUD, query/desktop, CLI/template, docs, and surface-baseline consumersF-DOCT-5remains red; not claimed as a leaf regression or green resultHarness
.llm/runs/fix-sdk-typed-error-channel--0.0.7-wave1/2fa2f71dc; only then may a separate PLAN-EVAL run.Drift / Debt
packages/sdk/src/ports/service-client.tsis the authorized sixth path.Definition of Done
surface:diff, JSRF-DOCT-5, pinned doc-lint baselines, and test(queue): eliminate typed-queue DLQ timing flake #1667 are not laundered as green.