Skip to content

refactor(ocap-kernel): branded kernel identifiers with runtime validation#917

Merged
rekmarks merged 26 commits intomainfrom
rekm/branded-kernel-identifiers
Apr 7, 2026
Merged

refactor(ocap-kernel): branded kernel identifiers with runtime validation#917
rekmarks merged 26 commits intomainfrom
rekm/branded-kernel-identifiers

Conversation

@rekmarks
Copy link
Copy Markdown
Member

@rekmarks rekmarks commented Apr 3, 2026

Fixes #809

Summary

  • Add branded string types (VatId, RemoteId, KRef, VRef, RRef, SubclusterId, GCAction, etc.) for kernel identifiers with runtime validators (isX/insistX)
  • Split Message into KernelMessage (kernel-space, KRef slots) and EndpointMessage (endpoint-space, ERef slots)
  • Remove public kv from KernelStore API, replace with typed accessor methods
  • Add KernelOneResolution type for kernel-space promise resolutions
  • Add KernelSyscallObject discriminated union type for kernel-space syscalls, removing casts from VatSyscall
  • Add makeGCAction factory with runtime validation
  • Remove redundant interior runtime validation where branded types enforce correctness at compile time
  • Tighten function signatures throughout OCAP URL redemption chain and kslot/makeStandinPromise to use KRef
  • Validate coerceEndpointMessage with EndpointMessageStruct at the liveslots boundary
  • Fix getOwner to handle 'kernel' owner without throwing EndpointId validation failure
  • Document branded type trust model (types.ts header, store README)
  • Add comprehensive tests for all ref validators and assertion functions

TODO

  • kernel-ui type safety is not fully addressed by this PR, and will be implemented in a follow-up
  • While the API of the kernel store is type safe, it is only type safe by an established convention of writeX(value: X): void / readX(): X pairs that use the same db keys internally. If we want to make the store actually type safe, we would have to establish some kind of compile-time mapping between db keys and value types. This may or may not be worth the trouble, and we defer this decision to the future.

Test plan

  • yarn workspace @metamask/ocap-kernel lint:fix passes
  • yarn workspace @metamask/ocap-kernel build passes
  • yarn workspace @metamask/ocap-kernel test:dev:quiet passes
  • yarn workspace @metamask/kernel-node-runtime test:e2e:ci passes

🤖 Generated with Claude Code


Note

Medium Risk
Touches core kernel routing/queueing, remote comms, and persistence access patterns; while largely type-safety refactors, signature changes and reduced runtime checks could surface as behavioral regressions at boundaries (RPC/liveslots/remotes) if any call sites or stored data violate the new invariants.

Overview
Branded kernel identifiers are enforced end-to-end. This introduces branded string types (e.g., KRef, VatId, SubclusterId) with is*/insist* validators, and updates kernel/public RPC APIs (e.g., queueMessage, issueOcapURL, terminateSubcluster) and UI call sites to use these types.

Message and resolution types are split and tightened. Message is separated into KernelMessage vs EndpointMessage, promise resolution flows switch to kernel-space types, and the queue/router/service-manager/remotes paths are updated accordingly (including narrowed kslot/krefOf behavior).

Raw KV access is removed from KernelStore. KernelStore.kv is no longer exposed; consumers/tests now use typed accessors for initialization state, kernel-service krefs, remote identity values, and known relay storage (with added validation on relay parsing).

Reviewed by Cursor Bugbot for commit 812f5bf. Bugbot is set up for automated code reviews on this repo. Configure here.

rekmarks and others added 6 commits April 2, 2026 18:41
Add phantom-property branded types for KRef, VRef, RRef, VatId,
RemoteId, SubclusterId, VatMessageId, and GCAction to prevent
accidental misuse of distinct identifier types at compile time.

Unlike the previous template-literal approach (removed in #366),
branded types are subtypes of string so interpolation, comparison,
and KV store writes work without casts. Assertions (insistKRef, etc.)
validate at runtime boundaries; factory sites use as-casts.

Closes #809

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…or insistX() calls

Remove ~29 redundant runtime validation calls from kernel interior code.
Validation is preserved only at trust boundaries: translators (vat/remote
syscalls), GCAction deserialization, reachable-state parsing, RPC handlers,
and RemoteHandle. Store methods use as-casts for KV reads (trusting the
store). RunQueueItem structs now use branded validators (KRefStruct,
EndpointIdStruct) so types carry brands through deserialization.

Also fixes a bug where insistEndpointId('kernel') in
KernelRouter.#deliverKernelServiceMessage would throw at runtime.
Add typed methods for all external KV access (initialization flag, kernel
service krefs, remote identity state, known relays) and remove the raw kv
property from the KernelStore return value. All KV access is now mediated
through typed store methods, closing the hole where consumers could bypass
branded type safety via raw string reads/writes.
…essage

Replace the untyped Message type (result: string, slots: string[]) with
two distinct types: KernelMessage (result: KRef, slots: KRef[]) and
EndpointMessage (result: ERef, slots: ERef[]). Each has its own struct
and validator. Translation functions bridge between them.

Eliminates ~15 'as KRef' casts on message fields in KernelRouter,
KernelQueue, and translators. The type system now enforces that kernel
code works with KRef-typed messages and endpoint code with ERef-typed
messages, with no possibility of confusion.
…ore and trust boundaries

Add makeGCAction factory, validated store reader helpers, tighten ID
validators to reject non-integer suffixes, add insistKRef at
resolvePromises boundary, validate coerceEndpointMessage slots, and
add comprehensive tests for all ref validators and insist functions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…promise resolutions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rekmarks

This comment was marked as resolved.

cursor[bot]

This comment was marked as resolved.

rekmarks and others added 6 commits April 3, 2026 13:31
…tMessage

The validation was throwing on raw liveslots refs before the translator
had a chance to process them, causing integration test timeouts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rekmarks

This comment was marked as resolved.

cursor[bot]

This comment was marked as resolved.

rekmarks and others added 2 commits April 3, 2026 15:51
…lStore.kv

The KernelStore no longer exposes `kv` publicly. Access the raw KV store
via KernelDatabase.kernelKVStore directly for test queue manipulation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 78.53%
⬆️ +0.05%
8600 / 10951
🔵 Statements 78.34%
⬆️ +0.04%
8735 / 11149
🔵 Functions 76.13%
⬆️ +0.20%
2016 / 2648
🔵 Branches 76.17%
⬆️ +0.07%
3703 / 4861
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/kernel-store/src/types.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/kernel-store/src/sqlite/nodejs.ts 98.95%
🟰 ±0%
93.33%
🟰 ±0%
100%
🟰 ±0%
98.95%
🟰 ±0%
82
packages/kernel-store/src/sqlite/wasm.ts 97.97%
🟰 ±0%
89.47%
🟰 ±0%
100%
🟰 ±0%
97.95%
🟰 ±0%
233-236
packages/kernel-ui/src/types.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/kernel-ui/src/components/SendMessageForm.tsx 100%
🟰 ±0%
72.72%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/kernel-ui/src/components/SubclusterAccordion.tsx 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/kernel-ui/src/components/VatTable.tsx 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/kernel-ui/src/hooks/useVats.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/kernel-ui/src/services/db-parser.ts 96.73%
🟰 ±0%
80.64%
🟰 ±0%
100%
🟰 ±0%
96.7%
🟰 ±0%
103, 128, 163
packages/kernel-utils/src/index.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/kernel-utils/src/types.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/Kernel.ts 87.96%
⬇️ -0.22%
76.47%
⬇️ -1.30%
82.6%
🟰 ±0%
87.96%
⬇️ -0.22%
287-290, 307, 331, 399-409, 497, 565, 631-634, 647, 657-658, 701, 718
packages/ocap-kernel/src/KernelQueue.ts 98.18%
⬇️ -0.05%
90%
⬇️ -0.62%
100%
🟰 ±0%
98.18%
⬇️ -0.05%
89, 346
packages/ocap-kernel/src/KernelRouter.ts 93.93%
⬆️ +0.60%
78.46%
⬆️ +0.85%
100%
🟰 ±0%
93.93%
⬆️ +0.60%
109, 172, 189, 260, 315, 363, 378, 381
packages/ocap-kernel/src/KernelServiceManager.ts 91.66%
⬇️ -0.49%
75%
⬇️ -2.27%
100%
🟰 ±0%
91.66%
⬇️ -0.49%
191-198
packages/ocap-kernel/src/index.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/types.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/garbage-collection/garbage-collection.ts 98.27%
⬆️ +0.03%
93.33%
🟰 ±0%
100%
🟰 ±0%
98.27%
⬆️ +0.03%
78
packages/ocap-kernel/src/liveslots/kernel-marshal.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/remotes/types.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/remotes/kernel/OcapURLManager.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/remotes/kernel/RemoteHandle.ts 89.22%
⬆️ +0.22%
83.92%
⬆️ +1.04%
87.75%
🟰 ±0%
89.49%
⬆️ +0.22%
367, 386-428, 481, 524, 534-536, 577-590, 929, 997-999, 1050
packages/ocap-kernel/src/remotes/kernel/remote-comms.ts 100%
🟰 ±0%
97.5%
⬇️ -0.11%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/rpc/kernel-control/is-revoked.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/rpc/kernel-control/issue-ocap-url.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/rpc/kernel-control/launch-subcluster.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/rpc/kernel-control/queue-message.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/rpc/kernel-control/revoke.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/rpc/kernel-control/terminate-subcluster.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/rpc/vat/deliver.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/rpc/vat-syscall/vat-syscall.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/store/index.ts 100%
🟰 ±0%
90%
⬇️ -10.00%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/store/vat-kv-store.ts 100%
🟰 ±0%
90%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/store/methods/base.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/store/methods/clist.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/store/methods/gc.ts 89.04%
⬇️ -0.70%
72.34%
⬆️ +1.89%
100%
🟰 ±0%
89.04%
⬇️ -0.70%
138, 150, 179-186, 202
packages/ocap-kernel/src/store/methods/id.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/store/methods/object.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/store/methods/pinned.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/store/methods/promise.ts 100%
🟰 ±0%
94.44%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/store/methods/reachable.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/store/methods/remote.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/store/methods/subclusters.ts 98.8%
🟰 ±0%
90%
🟰 ±0%
96.15%
🟰 ±0%
98.76%
🟰 ±0%
258
packages/ocap-kernel/src/store/methods/translators.ts 98.38%
⬇️ -1.62%
96.42%
⬇️ -3.58%
100%
🟰 ±0%
98.38%
⬇️ -1.62%
143
packages/ocap-kernel/src/store/methods/vat.ts 96.33%
⬇️ -0.03%
78.57%
🟰 ±0%
100%
🟰 ±0%
96.29%
⬇️ -0.04%
217, 287, 294-295
packages/ocap-kernel/src/store/utils/kernel-slots.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/store/utils/reachable.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/vats/SubclusterManager.ts 95.07%
🟰 ±0%
88.88%
🟰 ±0%
100%
🟰 ±0%
95%
🟰 ±0%
195-198, 252, 335, 340-342, 358, 362
packages/ocap-kernel/src/vats/VatHandle.ts 90%
🟰 ±0%
85.71%
🟰 ±0%
100%
🟰 ±0%
90%
🟰 ±0%
314, 365-370, 379-385
packages/ocap-kernel/src/vats/VatManager.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/vats/VatSyscall.ts 100%
🟰 ±0%
94.11%
⬇️ -1.34%
100%
🟰 ±0%
100%
🟰 ±0%
packages/omnium-gatherum/src/background.ts 0%
🟰 ±0%
0%
🟰 ±0%
0%
🟰 ±0%
0%
🟰 ±0%
22-278
Generated in workflow #4213 for commit 812f5bf by the Vitest Coverage Report Action

rekmarks and others added 2 commits April 3, 2026 18:24
…, consolidate CapDataStruct

- Widen KernelPromise.decider to EndpointId | 'kernel', validate in getKernelPromise
- Use getOwner() in gc.ts instead of raw KV read, guard isVatId before isVatTerminated
- Add insistKRef validation in redeemLocalOcapURL and remote redemption reply
- Change KernelSyscallObject exit variant and CrankResult.terminate.info to CapData<KRef>
- Tighten parseKernelSlot and insistKernelType to accept KRef instead of string
- Compose isEndpointId from isVatId || isRemoteId
- Fix gc.test.ts to test makeGCAction rejection instead of redundant isGCAction checks
- Export CapDataStruct from kernel-utils, remove duplicate from ocap-kernel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rekmarks
Copy link
Copy Markdown
Member Author

rekmarks commented Apr 4, 2026

@cursor review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit e2361a4. Configure here.

rekmarks and others added 7 commits April 3, 2026 20:24
…erceEndpointMessage

Add trust model documentation to types.ts and a store README explaining
that persistence reads are trusted and data integrity is the persistence
layer's responsibility. Validate coerceEndpointMessage with
EndpointMessageStruct, fix stale JSDoc, consistent insist* docs, and
use makeGCAction in gc tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n completions

Replace the loosely typed deferredCompletion object in
RemoteHandle.handleRemoteMessage with a discriminated union keyed on
`type` ('redeemURL' | 'redeemURLReply') and further discriminated by
`ref` (success) vs `error` (failure), eliminating the `success` boolean
and enabling precise type narrowing at each completion handler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nsafe casts

Replace CapDataStruct (validates string[] slots) with
KernelCapDataStruct (validates KRef[] slots) in queueMessage and
launchSubcluster RPC specs. This makes the runtime validation match the
declared CapData<KRef> type, eliminating two `as unknown as` casts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…literal rationale

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ten test casts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rekmarks rekmarks marked this pull request as ready for review April 6, 2026 19:58
@rekmarks rekmarks requested a review from a team as a code owner April 6, 2026 19:58
@rekmarks rekmarks enabled auto-merge April 7, 2026 19:30
@rekmarks rekmarks added this pull request to the merge queue Apr 7, 2026
Merged via the queue into main with commit d979a06 Apr 7, 2026
32 checks passed
@rekmarks rekmarks deleted the rekm/branded-kernel-identifiers branch April 7, 2026 19:43
github-merge-queue bot pushed a commit that referenced this pull request Apr 7, 2026
…921)

A follow-up to #917 addressing various items deferred during the
introduction of branded kernel identifier types.

## Summary

- Replace `as KRef` casts from unvalidated sources with runtime
validation in `kernel-ui`
- `db-parser.ts`: remove dead `?? ''` fallbacks on regex match groups,
add `insistKRef` validation for slot resolution from JSON-parsed DB
values
- `SendMessageForm.tsx`: validate user-selected target with `isKRef`
before sending, replacing the blind `as KRef` cast
- Add `isKRef`, `insistKRef`, `KRefStruct` to `setupOcapKernelMock` test
utility
- Includes prior commit addressing PR review feedback on branded types
in `ocap-kernel`

## Test plan

- [x] `yarn workspace @metamask/kernel-ui run build` passes
- [x] `yarn workspace @metamask/kernel-ui test:dev:quiet` passes (all 34
test files, 263 tests)

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Adds runtime `KRef` validation in UI and DB parsing paths, which can
now reject/throw on previously accepted malformed values. Risk is
moderate because it changes input handling and parsing behavior but is
narrowly scoped and covered by updated tests.
> 
> **Overview**
> **Hardens branded identifier handling** by validating `KRef` values at
runtime instead of relying on unchecked `as KRef` casts.
> 
> In `SendMessageForm`, the selected target is now stored as `KRef |
null`, validated via `isKRef` on change, and sending is blocked when the
target is invalid; tests are updated accordingly and add coverage for
failed `KRef` validation.
> 
> In `kernel-ui`’s `db-parser`, regex match fallbacks are removed and
`resolveSlot` now calls `insistKRef` when converting JSON-parsed slot
strings, making malformed DB data fail fast. Supporting changes extend
`setupOcapKernelMock` with `isKRef`/`insistKRef`/`KRefStruct`, and
`ocap-kernel` tests/docs are updated (including new `getKnownRelays`
test coverage and tighter ID validation cases).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
55a981a. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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.

Add branded types for kernel identifier strings

2 participants