Effect 4.0.0-beta.106 — the Schema error constructors were renamed - #6
Merged
Conversation
beta.104 renamed the Schema error constructors to align with their `Data` counterparts: `Schema.TaggedErrorClass` is now `Schema.TaggedError`. The old name is gone, so the bump and the rename have to land together. Typecheck and the 41 client-ts unit tests are green on beta.106.
srid
added a commit
to juspay/kolu
that referenced
this pull request
Aug 8, 2026
…d to The pin recorded `branch: "main"` while its revision was the tip of an unmerged feature branch. `npins update osfacts` fetches the latest commit of the branch NAMED in that field, so a routine pin refresh would silently walk the graft back to main's pre-rename osfacts client and desync it from the workspace's effect@4.0.0-beta.106 — breaking the single-Effect-version invariant this PR exists to hold, with nothing to say why. (The mismatch predates this PR, which pinned an effect-client commit under the same "main" label; this stops perpetuating it.) Also corrects the previous commit's verification note, which named the wrong file: `catalogPins` parses pnpm-workspace.yaml and `check.ts` parses packages/tests/coverage-ledger.yaml — NEITHER site parses pnpm-lock.yaml. The parsers were re-differentialled against those two actual inputs: deep-equal on both, 4ms->2ms and 34ms->5ms. The conclusion is unchanged; the evidence for it now matches the code. After juspay/osfacts#6 merges, this pin should move to the merged commit and the branch field back to the long-lived branch kolu tracks.
srid
added a commit
to juspay/kolu
that referenced
this pull request
Aug 8, 2026
juspay/osfacts#6 merged into `effect-client` (9a92c17). The pin now names that branch and that commit, so the branch field once again describes where the revision actually lives and `npins update osfacts` tracks the long-lived branch kolu follows — the interim feature-branch label is gone. The fetched tree hashes identically to the pre-merge commit, so the graft's content is unchanged; this only moves the pin onto the merged history.
srid
added a commit
that referenced
this pull request
Aug 8, 2026
* build(client-ts): take effect as the client's one runtime dependency The client was zero-dependency; it now pins `effect` at the exact version both consumers already pin (4.0.0-beta.102), which is what lets its verbs hand back Effects that compose with the callers' own. The `.npmrc` line is the price of the dep's own graph: `effect` depends on `msgpackr`, whose OPTIONAL native accelerator carries an install script, and pnpm refuses to guess — an undecided install script fails the install outright. This client never encodes msgpack, so the answer is no, recorded once. A NEW script-bearing dependency still fails loudly rather than inheriting it. * feat(client-ts)!: return Effects from the spawning verbs, and raise tagged errors Three changes that are one change, because they are one API. The SPAWNING verbs — snapshotSubtree, snapshotHost, snapshotPids, processIdentityAsync, socketHolders, host, and the function osfactsSocketHolders returns — now hand back `Effect.Effect<Reading, OsfactsClientError>` instead of a Promise. Nothing spawns until the caller runs it, every declared failure is in the type, and an interrupted fiber KILLS the in-flight child rather than leaving it to run out its five-second deadline against a host nobody is reading. That last part is the capability a Promise could not express, and it is pinned by a test that interrupts a sleeping stub and watches its pid go. The spawn stays node's own `execFile` under `Effect.callback`, NOT a platform command layer, and the reason is written at the call site: childFailure.ts classifies a child's fate by reading node's error shapes (.code / .status / .stderr / .signal), the child-level timeout with killSignal SIGKILL is the kernel-enforced bound a fiber interrupt cannot replace, and drishti runs this client under Bun. The callback reassembles node's error into exactly the shape `promisify(execFile)` used to hand over, so the classifier still sees ONE shape from both twins. The `kind: "spawn" | "version" | "parse"` union becomes three `Schema.TaggedErrorClass`es — OsfactsSpawnError, OsfactsVersionError, OsfactsParseError — with `OsfactsClientError` as their union type and `isOsfactsClientError` as the guard. The discriminant was always the kind; as classes it narrows, and a verb can declare what it fails with. They are still Errors, which is what lets the sync island go on throwing them. The SYNC ISLAND is decided and documented in the module header: snapshotPidsSync, processIdentity, and processIdentityFromEnv stay synchronous over execFileSync. Their consumers' single-instance gate is a deliberately synchronous claim path — async there reorders the gate against the boot side effects it guards — and execFileSync cannot be interrupted, so an Effect wrapper would advertise a capability the call does not have. The parsers and folds stay pure sync throwing functions for the same reason: both consumers import them to read a document they already hold. Also turns on exactOptionalPropertyTypes, which makes HostReading's four genuinely-absent fields machine-checked and forces the two deliberately present-and-undefined shapes (ListenerRow.uid, SocketHolder.command) to say so. BREAKING CHANGE: the spawning verbs return Effects, `OsfactsClientError` is now a union TYPE rather than a class (construct/branch on the three classes, or use `isOsfactsClientError`), and `.kind` is gone. * docs(client-ts): document the Effect surface, and correct a stale capability list "What it does not" claimed the client does not spawn synchronously and does not answer identity questions. It has done both since the sync gate path landed — snapshotPidsSync spawns synchronously on purpose, and processIdentity* reports a pid's start-qualified identity. What it does not do is decide what that fact MEANS, which is the honest line and now the one the list draws. Adds the Effect surface: which verbs return Effects, why three functions are a deliberate sync island, and the three-class error vocabulary. * fix(client-ts): record the msgpackr-extract build decision where pnpm reads it The `.npmrc` form was accepted and then ignored — the install passed locally only because an earlier run had already written the decision into `node_modules`, and a clean checkout (CI, both platforms) failed exactly as it did before the line existed. pnpm 11 reads `allowBuilds` from `pnpm-workspace.yaml` and from nowhere else; `package.json` and `.npmrc` are silently inert. Verified against a simulated consumer graft: a parent pnpm workspace that contains this directory as a member ignores the nested file entirely and links the member as usual, so the setting has no reach beyond an install run from here. * chore(client-ts): effect 4.0.0-beta.103 * chore(client-ts): effect 4.0.0-beta.106 (#6) beta.104 renamed the Schema error constructors to align with their `Data` counterparts: `Schema.TaggedErrorClass` is now `Schema.TaggedError`. The old name is gone, so the bump and the rename have to land together. Typecheck and the 41 client-ts unit tests are green on beta.106.
srid
added a commit
to juspay/kolu
that referenced
this pull request
Aug 8, 2026
…2126) Kolu has been on `effect@4.0.0-beta.103` since it was current. Three betas later, [beta.106](https://github.com/Effect-TS/effect/releases/tag/effect%404.0.0-beta.106) is out, and the interesting thing about the gap is how little of it Kolu had to react to — and which one thing it *couldn't* avoid. ## The forcing change [beta.104](https://github.com/Effect-TS/effect/releases/tag/effect%404.0.0-beta.104) renamed the Schema error constructors to line up with their `Data` counterparts: | before | after | | --- | --- | | `Schema.ErrorClass` | `Schema.Error` | | `Schema.TaggedErrorClass` | `Schema.TaggedError` | | `Schema.ErrorReviver` | `Schema.ErrorInstanceReviver` | The old names are **gone**, not deprecated. Kolu spells `Schema.TaggedErrorClass` 37 times across 15 files — every wire-visible error in `@kolu/surface`, `@kolu/padi` and `kaval` is one — so the bump and the rename are one commit. None of the other renames in these betas (`Schedule.andThen`, `RateLimiter.makeSleep`, CLI `withHidden`) appear anywhere in the tree. **The rename also lands in a repo Kolu doesn't own.** `osfacts-client` is grafted into the workspace from the `osfacts` npins pin, it is `.gitignore`d, and it declares three `Schema.TaggedErrorClass` errors of its own — so it broke the typecheck from a file no grep of this repo would ever surface. That fix is **juspay/osfacts#6** (green on both platforms), and the pin here advances to that commit so the graft and the workspace agree on one Effect version. ## What the new betas gave us, and what we took Most of the new API has no home here — verified by sweep, not assumption: no `HttpClient` anywhere (so `withRateLimiter`'s new bounded 429 retries have nothing to attach to), no `Cron`, no `ExecutionPlan`, no Effect `Config`/`ConfigProvider`, no `HttpApi` (only `HttpRouter`), no multipart, no fast-check, no `File.seek`, and no Effect CLI. **YAML parsing moves into Effect — but only where it belongs.** beta.104 added lightweight INI/YAML/TOML parsers under `effect/unstable/encoding`. The two governance checks now use `Yaml.parse`, which **drops the `yaml` dependency from `packages/tests` outright** (Effect was already a dependency there). Both parsers were run over the real inputs first — `pnpm-workspace.yaml` and the 87 KB `coverage-ledger.yaml` — and agree deep-equal, 4ms→2ms and 34ms→5ms. `@kolu/solid-markdown` deliberately **keeps** the `yaml` package, because the same differential says the new parser is the wrong tool for *that* input. Front matter is user-authored, and against a corpus of all 312 front-matter blocks in this repo plus the cases `render.test.ts` pins, the new parser: - throws on a multi-line plain scalar (`desc: one⏎ two`), which `yaml` folds — a front-matter block that renders as a table today would fall back to a raw code block; - throws on the cyclic anchor `a: &a [*a]` that `render.test.ts` explicitly pins as "must still render a table"; - stops recognising a following ` #` comment once a plain scalar contains an apostrophe — `kolu's closed #1708 manager` keeps the comment as content where `yaml` truncates. Two real Atlas notes parse differently because of it. (It is *better* on one axis — it parses the compact nested mappings in six `SKILL.md` files that `yaml` rejects — but "differently wrong on user content" is not a trade worth taking for a preview surface.) **A `Stdio` adoption was tried and removed.** `kaval-tui attach` briefly asked Effect's new `Stdio` service whether stdin/stdout were terminals. The review gauntlet killed it and it is **not** in this diff — see the lens comment below. Short version: `NodeStdio.layer` is `Effect.sync(() => process.stdin.isTTY === true)`, a pass-through of the global it replaced; `main.ts` runs `cli()` at module load so no test can ever import it to substitute the service; and `attach.ts` already has `AttachTty`, a *tested* seam carrying the raw-mode and resize operations `Stdio` structurally cannot express. `packages/kaval-tui/src/main.ts` is byte-identical to master. ## The beta-assumption gate, honoured rather than satisfied `packages/tests/governance/betaAssumptions.ts` exists so a version bump can't land quietly: every `BETA-ASSUMPTION(<tag>)` marker must carry the current pin's tag, so a bump turns all of them red until someone **re-measures the behaviour**. Re-stamping them is the easy way to defeat the mechanism, so each was actually re-measured against beta.106: - the reactor, socket-redial and frame-limit assumptions by the named tests they cite — `@kolu/surface` is green at **61 files / 628 tests**; - a failed `Effect.forkChild` child still does **not** fail its parent fiber; - `Schema.optionalKey(Schema.Never)` still accepts a missing key and rejects any present value, explicit `undefined` included; - the RPC pinger still `Effect.delay("5 seconds")` and opens its kill latch on the next unanswered tick — so `probeDaemonIdentity`'s derived 8 s still sits inside the band it was derived from. **The gate had a blind spot, and this PR closes it.** Its regex validates the marker *line* only — so three markers here were re-stamped to beta.106 while the prose recording *what was measured* still said beta.103, and that shipped green. Code-police caught it. The prose is now re-verified (all of `frameLimit`'s quoted dist facts still hold verbatim in beta.106), and a second scan holds every `effect@<version>` written in prose under `packages/` to the pinned version. Proven in both directions: green as-is, and failing loudly when the exact original defect is re-introduced. ## One claim worth retracting An earlier reading of beta.105's release note suggested Kolu's ~8 schema-decode sites would lose their human-readable errors, since "schema issues no longer format implicitly." Measured across both versions, that is **not** what happens: the change moved the **constructor** adapters to a generic `"Schema validation failed"` with the issue in `cause`, while `decodeUnknownSync` — which is what every one of those sites uses — still throws a `SchemaError` whose message is the path-annotated tree, byte-identical on beta.103 and beta.106. No migration was needed and none was done. ## Verification - **CI green on both platforms** — `6a6c4c2#1`, 24 recipes per lane, 14m26s. All 48 required checks green. - `@kolu/surface` unit lane — 61 files, 628 passed - governance gate — `effect@4.0.0-beta.106 agreed across 68 pin sites, 11 beta-behavior assumptions stamped (3 evidence citations agreed)` - no open code-scanning alerts on this PR **Review gauntlet**: lens (lowy ∥ hickey) applied, simplify clean + 1 altitude fix, code-police 4 findings all actioned. The **agent-debate track has not run** — the `grok` CLI is unauthenticated on this machine (`grok login`), and the gauntlet forbids substituting another peer. **Merge order**: juspay/osfacts#6 first, then re-point this pin at the merged commit (its `branch` field currently names the feature branch deliberately, so `npins update` can't silently walk it back to the pre-rename client). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Kolu is bumping its Effect pin from
4.0.0-beta.103to4.0.0-beta.106, and it grafts this client from theosfactsnpins pin — so the client has to move with it or Kolu's typecheck goes red on code Kolu cannot edit.The forcing change is in beta.104, which renamed the Schema error constructors to align with their
Datacounterparts:Schema.ErrorClassSchema.ErrorSchema.TaggedErrorClassSchema.TaggedErrorSchema.ErrorReviverSchema.ErrorInstanceReviverThe old names are gone, not deprecated, so the version bump and the rename have to land in the same commit.
clientError.tswas the only file using one — four occurrences ofSchema.TaggedErrorClass, coveringOsfactsSpawnError,OsfactsVersionError, andOsfactsParseError. Nothing else inclient-tstouches a renamed API.Verification
pnpm typecheck— cleanpnpm test:unit— 41 passed (3 files)Both run against the updated lockfile, on beta.106.
🤖 Generated with Claude Code