Skip to content

feat(kernel-utils): narrow default-guarded exos - #1054

Draft
ci-belphegor wants to merge 1 commit into
grypez/narrowing-7-joinfrom
grypez/narrowing-8-default-guarded
Draft

feat(kernel-utils): narrow default-guarded exos#1054
ci-belphegor wants to merge 1 commit into
grypez/narrowing-7-joinfrom
grypez/narrowing-8-default-guarded

Conversation

@ci-belphegor

Copy link
Copy Markdown

Explanation

Most exos in this repo are built with makeDefaultExo, which produces M.interface(name, {}, { defaultGuards: 'passable' }) — an empty methodGuards map, so there is no per-method guard to conjoin a delta onto. Without this PR, narrow cannot narrow almost any existing exo: 79 non-test files use makeDefaultExo against 9 with explicit method guards.

defaultGuards: 'passable' means every method admits any passable arguments, so for a method absent from methodGuards a delta of length n synthesizes:

M.callWhen(...deltaPatterns).rest(M.any()).returns(M.any());

Sound on the same grounds as the general case: the synthesized guard admits no more calls than the base's own, and the method still forwards unaltered.

This replaces only the defaultGuards: 'passable' arm of the error PR 5 left as a seam. Unmarks one ratchet case.

Notes for reviewers

defaultGuards: 'raw' keeps throwing, deliberately. Raw guards pass arguments unmarshalled, which is not "admits any passable argument," so the soundness argument does not transfer. The it.each case that previously covered the 'passable' error now covers 'raw', so the surviving arm keeps its coverage rather than quietly becoming untested.

A length-0 delta is a proven no-op, not an approximation: the test pins the whole shape — zero required positions, zero optionals, and a rest guard admitting 'anything' and 42. So it is ⊆ with equality, which is the claim the design needs.

The cost, documented at the synthesis site. Such a base names no methods, so a delta naming one the base does not implement is indistinguishable from one it does, and no error can be raised at narrowing time. A misspelled method therefore mints a forwarder for a method that does not exist — which rejects at call time with target has no method "..." rather than silently returning undefined. That is why PR 6 refused an optional call in the forwarding path.

Probing past the case's own assertion shows the synthesized guard genuinely enforces, including .. rejection through the rest position: rejected:… ...rest: [0]: ".." - Must fail negated pattern: "..". A makeDefaultExo base with no guards of its own ends up as tightly scoped as an explicitly-guarded one.

Stack

PR 8 of 12. Base: #1053.

A base built with makeDefaultExo carries defaultGuards: 'passable' and an
empty methodGuards map, so there is no per-method guard to conjoin onto.
Since 'passable' admits any passable arguments, the delta's patterns can be
the whole guard: the result admits no more calls than the base did, and a
delta of length 0 synthesizes M.callWhen().rest(M.any()).returns(M.any()),
which admits exactly what the base admits. That makes narrow usable against
the exos this repo actually builds.

The base names no methods, so a delta naming one it does not implement is
indistinguishable from one it does and can no longer be refused while
narrowing. Calling it rejects with 'target has no method', which is what a
caller holding no such authority must see. The error for a base that names
its methods is unchanged, as is the one for raw defaults.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ci-belphegor
ci-belphegor force-pushed the grypez/narrowing-8-default-guarded branch from 2d4b340 to d2c3fc8 Compare September 11, 2026 11:42
@ci-belphegor
ci-belphegor added this pull request to stack #1060 September 11, 2026 11:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant