E6: pin Effect 4.0.0-rc.112 (and E7 working-rule docs) - #246
Conversation
Move the workspace Effect pin from 4.0.0-beta.83 to 4.0.0-rc.112 (latest 4.0.0-rc.* on npm). Rename Schema.TaggedErrorClass to Schema.TaggedError (112 sites / 53 files on live-main) and Schema.ErrorClass to Schema.Error. httpapi-codegen type helpers follow the RC (Any/AnyWithProps → Top/Constraint) plus the runtime/property changes the .d.ts diff could not see: endpoint identifier, SchemaRepresentation.toRepresentations, class markers, builtin toCode generation, and sameEncoding that no longer relies on Transformation identity. Regenerated HTTP clients. Pin the encoded-route failure log on Expected string at ["workspaceID"] after beta.105 dropped Issue#toString.
Replace the dangling author-machine effect-smol clone with node_modules/effect/AGENTS.md and ai-docs/src. Add a short repo-only skill for layer/defaultLayer, runPromiseWithLayer, and fromZod. Mark E6 and E7 done in ROADMAP against the measured rename counts.
HttpApiEndpoint.Top is too tight for unused never params; keep a runtime endpoint shape and map Group.Any to Constraint. Schema.toType and Schema.make now return Constraint. Client.Group dropped the identifier type argument. Provide OtlpExporter.layerFlusher, and map SseError onto LLMError so sseFraming stays closed.
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.OpenSSF ScorecardScorecard details
Scanned Files
|
CI format:check failed because TaggedError is shorter than TaggedErrorClass and prettier wants those two class declarations on fewer lines.
|
| Detail | Value |
|---|---|
| Failed step | Unknown step |
| Autofix status | ⏭️ Autofix skipped |
| Run | View full logs |
Failure Summary
(no summary available)
Please inspect the full Actions logs for complete output. Do not share this comment publicly — it may contain redacted paths.
RC 4.0.0 moved isStartsWith / isPattern / isMinLength off annotations.meta._tag onto annotations.representation.id + payload. zodObject dropped those filters, so Session.Info.parse accepted invalid-id. Read the RC representation and keep the meta fallback.
nikomatt69
left a comment
There was a problem hiding this comment.
Ultrareview (bugs / security / performance / patterns)
Four-role review of this draft pin. Validate / typecheck / Linux test are green. Windows smoke is red; that matches live-main history (TUI smoke / heap), not a new pin-only typecheck break.
[HIGH] RC SseError is mapped in sseFraming and left open on the generated Effect client
The RC decoder fails with tagged SseError (e.g. EventTooLarge) in addition to Retry. This PR closes that channel in packages/llm/src/protocols/shared.ts. The production in-process client is emitEffectImported, whose mapClientError still is:
HttpClientError.isHttpClientError(error) || Schema.isSchemaError(error) || Sse.Retry.is(error)
? new ClientError({ cause: error })
: errorRetry becomes ClientError. SseError is returned as-is. Call sites that catchTag("ClientError") / instanceof ClientError do not see an oversized (or otherwise failed) SSE event. client.ts was not regenerated in this PR (only client-error.ts renamed), so the shipped mapper matches the stale template.
The per-endpoint emitEffect mapper at index.ts:1833 wraps unknown errors, so fixture clients do not leak. Nikcli’s imported client does.
Sse.SseError has no static .is (only Retry does). Use instanceof.
Fix: treat SseError like Retry in the emitEffectImported template, regenerate with bun run generate:httpapi-clients, and add a codegen assertion that the imported mapper mentions SseError.
⚠ regression — SSE error channel / ClientError contract
⚠ propagation — httpapi-codegen feeds every generated Effect client
Other dimensions
No security, performance, or additional pattern issues that survived validation. Pins are consistent at 4.0.0-rc.112. effect-zod representation walker plus the startsWith/endsWith test close the Session.Info id-format hole this pin first exposed.
Independent of #247 (E8). ROADMAP.md will conflict if both merge without a rebase; numbering is already consistent (this PR owns E6/E7).
Validation Summary
Total findings: 1 | Valid: 1 | Invalid: 0
By severity: CRITICAL: 0, HIGH: 1, MEDIUM: 0, LOW: 0
Warnings: propagation: 1, regression: 1
nikomatt69
left a comment
There was a problem hiding this comment.
Line comment on sseFraming: RC SseError is closed here and still leaks through the generated Effect client's mapClientError. See the review summary.
| Stream.decodeText(), | ||
| Stream.pipeThroughChannel(Sse.decode()), | ||
| Stream.catchTag("Retry", () => Stream.empty), | ||
| Stream.catchTag("SseError", (error) => Stream.fail(eventError("sse", error.message))), |
There was a problem hiding this comment.
[HIGH] This closes RC SseError on the LLM framing path only.
The generated Effect client mapper (packages/httpapi-codegen/src/index.ts emitEffectImported → packages/nikcli/src/server/httpapi/client/generated/client.ts mapClientError) still wraps Sse.Retry and returns every other error as-is. After this pin, Sse.decode / HttpApiClient streams also fail with SseError (e.g. EventTooLarge). Callers that catchTag("ClientError") miss it.
Treat error instanceof Sse.SseError like Retry in that template, run bun run generate:httpapi-clients, and pin it with a codegen test. Sse.SseError has no static .is.
Issue for this PR
ROADMAP E6 (Now after #245). E7 is included in the same PR as allowed once the pin is in the tree.
Type of change
What does this PR do?
Measured pin bump, not a rewrite. Research:
specs/research-effect-4-rc.md(diff ofeffect@4.0.0-beta.83vs4.0.0-rc.112). npm's latest4.0.0-rc.*on 2026-08-26 is stillrc.112.Grep against
480b8724(packages/**/*.ts), not the research estimate:Schema.TaggedErrorClassSchema.TaggedErrorSchema.ErrorClassSchema.ErrorHttpApiEndpoint.Any/AnyWithProps,HttpApiGroup.AnyTop/ConstraintResearch said 109/52 for
TaggedErrorClass; the extra three hits are comments and generated-string literals. Every other removed.d.tssymbol has 0 uses here.Layer/Cause/Stream/ScopedCache/ManagedRuntimewere not touched.The
.d.tsgrep missed runtime/property changes inhttpapi-codegenthat the pin actually requires:endpoint.identifier,SchemaRepresentation.toRepresentations, class markers~constructor/~sentinels, RC built-ins shippingtoCode+ aneffect/representation instead of agenerationannotation,fromJsonStringallocating a new Transformation (sosameEncodingcompares encodedtoincluding Literal values),HttpApiClient.Client.Groupdropping its identifier type argument, andOtlpTracer.makerequiringOtlpExporter.Flusher.Sse.decodenow also fails withSseError;sseFramingmaps that toLLMError.RC
Schema.is*checks moved offannotations.meta._tagontoannotations.representation.id(effect/schema/isStartsWith) pluspayload.zodObjectdropped those filters, soSession.Info.parseacceptedid: "invalid-id". The walker now reads the RC representation (meta fallback kept). That is why the first Windows smoke failed onSession > Info schema > rejects invalid id format; after8588fb20that suite is green on both cmd and pwsh.beta.105 dropped implicit
Issue#toString.test/server/httpapi-encode-failure.test.tsdrivesGET /session/:idafterworkspaceID: nulland asserts the effect log still containsencoded route failed,Expected string, and["workspaceID"]. The RC no longer saysgot null.E7: working rule points at
node_modules/effect/AGENTS.mdandai-docs/src..nikcli/skill/effect-v4/SKILL.mdis 34 lines and records onlylayer/defaultLayer,runPromiseWithLayer, andfromZod. Search for the old author-machine clone path underspecsandpackagesis empty.Not in this PR: R2, H9, H10, HTTP compression, SchemaBinary,
Effect.withExecutionPlan.How did you verify your code works?
ci-pipelinevalidate passed on8588fb20(4m57s). Typecheck job passed. Linux test job passed.bun run typecheck35/35.bun test test/session test/util— 1000 pass / 0 fail.Session.Info.parse("invalid-id")throws again.effect-zodrefinements including startsWith/endsWith pass.8588fb20: typecheck and the four targeted suites all passed (double-ESC, session, config+worktree, util), plus--help/--version/ binary build. The remaining red step isBuilt nikcli.exe boots the TUI(tui-smokepainted 0 printable characters). That step is not one of the four suites.windows-compatonlive-mainhas been red since 2026-08-20 (last success); the latestlive-mainrun failed at Windows typecheck and never reached TUI smoke.bun test test/server/httpapi-encode-failure.test.ts test/server/httpapi-session.test.ts— 16 passhttpapi-bridge,httpapi-bridge-401,httpapi-config,httpapi-client-compat,instance-scope,user-error— 32 passpackages/httpapi-codegen— 86 pass / 0 fail,tsc --noEmitcleanbun run check:routes --strict— 338 contracts, 315 handlers, 23 rawbun run generate:httpapi-clients— no drift after the RC codegen adaptationsTaggedErrorClass/Schema.ErrorClass/HttpApiEndpoint.Any/HttpApiGroup.Anyinpackages/**/*.tsis 0effect/@effect/platform-*pins are4.0.0-rc.112;bun.lockone copy; npm latest4.0.0-rc.*is stillrc.112Did not run
bun run test:ci(the full nikcli shard).packages/llmhas one pre-existing-lookingtoMatchObjectassertion on OpenRouter cache hints (openrouter.test.ts:87); first assertion in that test still passes (4 cache controls). Not part of this pin's rename surface.update-node-modules-hashesiscontinue-on-erroron PRs. It fails because the nixnode_modulesfileset does not include thegithubworkspace (Workspace not found "github"). Pre-existing packaging gap, not this pin;nix-evalpassed.Screenshots / recordings
N/A — pin bump, no UI.
Checklist