Skip to content

Commit bcb65af

Browse files
FUDCoclaude
andcommitted
fix(ocap-kernel): accept durable/virtual/faceted vrefs in isVRef
The previous regex only matched plain `[op][+-]N`, rejecting liveslots-allocated durable-kind refs like `o+d10/1` and faceted forms like `o+d10/1:0`. Any vat using `defineDurableKind` would fail EndpointMessage validation on outgoing sends and reachable.ts on persisted-slot reads. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent eeae1ee commit bcb65af

2 files changed

Lines changed: 46 additions & 12 deletions

File tree

packages/ocap-kernel/src/types.test.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -602,12 +602,21 @@ describe('insistKRef', () => {
602602
});
603603

604604
describe('isVRef', () => {
605-
it.each(['o+0', 'o-1', 'p+42', 'p-0', 'o+123456789'])(
606-
'returns true for valid VRef %s',
607-
(value) => {
608-
expect(isVRef(value)).toBe(true);
609-
},
610-
);
605+
it.each([
606+
'o+0',
607+
'o-1',
608+
'p+42',
609+
'p-0',
610+
'o+123456789',
611+
// Vat-allocated durable / virtual objects (liveslots `defineDurableKind`
612+
// and friends). See parseVatSlot in @agoric/swingset-liveslots.
613+
'o+d10/1',
614+
'o+v3/4',
615+
'o+d10/1:0',
616+
'o+v3/4:7',
617+
])('returns true for valid VRef %s', (value) => {
618+
expect(isVRef(value)).toBe(true);
619+
});
611620

612621
it.each([
613622
{ name: 'missing sign', value: 'o1' },
@@ -616,6 +625,16 @@ describe('isVRef', () => {
616625
{ name: 'non-digit suffix', value: 'o+1abc' },
617626
{ name: 'kernel ref', value: 'ko1' },
618627
{ name: 'remote ref', value: 'ro+1' },
628+
// Durability marker only valid on `o+`. `o-` is kernel-allocated;
629+
// promises and devices never carry a durability/subid suffix.
630+
{ name: 'durable on import', value: 'o-d10/1' },
631+
{ name: 'durable on promise', value: 'p+d10/1' },
632+
// Subid grammar requires a durability marker.
633+
{ name: 'subid without marker', value: 'o+10/1' },
634+
// Facet requires a subid.
635+
{ name: 'facet without subid', value: 'o+d10:0' },
636+
// Device refs are not supported by this kernel.
637+
{ name: 'device ref', value: 'd+0' },
619638
{ name: 'number', value: 123 },
620639
{ name: 'null', value: null },
621640
])('returns false for $name', ({ value }) => {
@@ -624,7 +643,7 @@ describe('isVRef', () => {
624643
});
625644

626645
describe('insistVRef', () => {
627-
it.each(['o+0', 'p-1', 'o+42'])(
646+
it.each(['o+0', 'p-1', 'o+42', 'o+d10/1', 'o+v3/4:7'])(
628647
'does not throw for valid VRef %s',
629648
(value) => {
630649
expect(() => insistVRef(value)).not.toThrow();
@@ -680,7 +699,7 @@ describe('insistRRef', () => {
680699
});
681700

682701
describe('isERef', () => {
683-
it.each(['o+0', 'p-1', 'ro+1', 'rp-2'])(
702+
it.each(['o+0', 'p-1', 'ro+1', 'rp-2', 'o+d10/1', 'o+v3/4:7'])(
684703
'returns true for valid ERef %s',
685704
(value) => {
686705
expect(isERef(value)).toBe(true);
@@ -698,7 +717,7 @@ describe('isERef', () => {
698717
});
699718

700719
describe('insistERef', () => {
701-
it.each(['o+0', 'p-1', 'ro+1', 'rp-2'])(
720+
it.each(['o+0', 'p-1', 'ro+1', 'rp-2', 'o+d10/1', 'o+v3/4:7'])(
702721
'does not throw for valid ERef %s',
703722
(value) => {
704723
expect(() => insistERef(value)).not.toThrow();

packages/ocap-kernel/src/types.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,22 @@ export type KRef = string & { readonly [KRefBrand]: never };
101101

102102
declare const VRefBrand: unique symbol;
103103
/**
104-
* Vat-space reference. Format: `${'o'|'p'}${'+' | '-'}${number}`.
105-
* E.g. `"o+0"`, `"p-7"`.
104+
* Vat-space reference. Mirrors the vref grammar produced by
105+
* `@agoric/swingset-liveslots`'s `parseVatSlot`:
106+
*
107+
* - `${'o'|'p'}${'+' | '-'}${number}` for plain refs
108+
* (kernel imports, ephemeral exports, promises).
109+
* E.g. `"o+0"`, `"o-3"`, `"p-7"`.
110+
* - `o+${'d'|'v'}${kindId}/${instanceId}` for vat-allocated
111+
* virtual (`v`) or durable (`d`) objects.
112+
* E.g. `"o+d10/1"`, `"o+v3/4"`.
113+
* - `o+${'d'|'v'}${kindId}/${instanceId}:${facetId}` for the
114+
* same with a facet selector.
115+
* E.g. `"o+d10/1:0"`.
116+
*
117+
* The durability/subid/facet syntax is only valid for `o+`
118+
* (vat-allocated objects); kernel imports, promises, and remotes
119+
* never carry it.
106120
*/
107121
export type VRef = string & { readonly [VRefBrand]: never };
108122

@@ -139,7 +153,8 @@ export const isKRef = (value: unknown): value is KRef =>
139153
typeof value === 'string' && /^k[op]\d+$/u.test(value);
140154

141155
export const isVRef = (value: unknown): value is VRef =>
142-
typeof value === 'string' && /^[op][+-]\d+$/u.test(value);
156+
typeof value === 'string' &&
157+
/^(?:p[+-]\d+|o-\d+|o\+(?:\d+|[dv]\d+\/\d+(?::\d+)?))$/u.test(value);
143158

144159
export const isRRef = (value: unknown): value is RRef =>
145160
typeof value === 'string' && /^r[op][+-]\d+$/u.test(value);

0 commit comments

Comments
 (0)