Skip to content

feat(kernel-utils): join - #1053

Closed
ci-belphegor wants to merge 1 commit into
grypez/narrowing-6-narrowfrom
grypez/narrowing-7-join
Closed

ci-belphegor wants to merge 1 commit into
grypez/narrowing-6-narrowfrom
grypez/narrowing-7-join

Conversation

@ci-belphegor

Copy link
Copy Markdown
Contributor

Explanation

join({ name, refs }) returns a narrowing of its operands' common base admitting exactly what any of them admits. Variadic; the result's method set is the union of the operands', and where two operands name the same method each argument position is disjoined.

Every ref must carry a provenance record naming the same base — a ref the library did not mint throws. That is what makes the operation sound: each operand's guard is AND(guard(base), X) by construction, so OR-ing the deltas stays ⊆ the base with no pattern subtyping needed.

The unnarrowed base is a legal operand and absorbs, contributing { method: [] } for every method in its guard. Absorption therefore falls out of the algebra rather than needing a branch.

Unmarks one ratchet case.

Notes for reviewers

The missing-method versus hole rule is one rule, not two, because two read as an inconsistency: a join is a union of authority, so a method absent from an operand contributes the empty set (and survives at the other operand's delta), while a hole contributes everything (and leaves that position unconstrained). A position past the end of a delta is a hole.

disjoinDeltas sits directly below conjoinDeltas so the asymmetry is visible: the OR dual takes the union of keys, whereas conjoinDeltas takes its keys from one side, because narrowing must not restore authority an intermediate narrowing dropped.

One deviation from the design doc, documented in the JSDoc rather than left to be discovered. The doc says the unnarrowed base "is a legal operand and absorbs." It absorbs only in company: the common base is discovered from a minted ref's record, and an unminted ref is recognizable as that base only by identity against it. So a call whose refs are all unminted throws — refs: [] and the degenerate refs: [base] included. Refusing beats trusting an unminted ref to be a base it cannot confirm; supporting the degenerate case would need a guard fetch for a call that means "copy the base."

Probing all four directions rather than just the case's own assertion: ok:read:srv/logs/y, ok:read:srv/data/x, and rejections for ["etc","passwd"] and ["srv","logs","..","etc"] against Must match one of [splitArray([srv,data],…), splitArray([srv,logs],…)].

Stack

PR 7 of 12. Base: #1052.

@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 73.18%
⬇️ -0.15%
10033 / 13709
🔵 Statements 72.95%
⬇️ -0.17%
10163 / 13931
🔵 Functions 73.32%
⬇️ -0.43%
2347 / 3201
🔵 Branches 67.67%
⬇️ -0.02%
4137 / 6113
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/kernel-utils/src/narrow-interface-guard.ts 100% 100% 100% 100%
packages/kernel-utils/src/narrowing.ts 34.09% 6.25% 38.46% 33.33% 64-66, 91-107, 141-153, 193-222
Generated in workflow #5065 for commit b3b283b by the Vitest Coverage Report Action

@ci-belphegor
ci-belphegor force-pushed the grypez/narrowing-7-join branch from 4c00a4f to 4284420 Compare September 30, 2026 13:44
@ci-belphegor
ci-belphegor force-pushed the grypez/narrowing-7-join branch from 4284420 to fe7d3b8 Compare September 30, 2026 16:18
@ci-belphegor
ci-belphegor removed this pull request from stack #1060 September 30, 2026 16:44
@ci-belphegor
ci-belphegor added this pull request to stack #1130 September 30, 2026 16:45
Return a narrowing of the refs' common base admitting whatever any of them
admits: the method set is the union of the operands', and where two name
the same method each argument position is disjoined.

disjoinDeltas sits beside conjoinDeltas as its dual, and the asymmetry is
the whole missing-method rule. A join is a union of authority, so its keys
are the union of both sides: a method absent from an operand contributes
the empty set and survives at the other operand's delta, while a hole
contributes everything and leaves that position unconstrained. Narrowing
is the opposite, which is why conjoinDeltas takes its keys from one side.

The base is a legal operand and absorbs, so the lattice has a representable
top and a fold needs no special case. It is recognizable only by identity
against a minted ref's record, so a call whose refs are all unminted throws
rather than trusting one of them. The result records the same base and the
disjoined delta, so a join can be narrowed or joined again.

narrow and join now share the minting step, since join needs the same
derive-forward-record sequence against a guard it already holds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ci-belphegor

Copy link
Copy Markdown
Contributor Author

Superseded by #1134, which lands the narrowing library as one PR with no stubs. This PR's change is kept as a commit there for review.

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