Skip to content

Commit 0b34805

Browse files
nficanocursoragent
andcommitted
fix: classify subscribe.* as core and reject x- extension types (#102 #106)
ExtensionNamespace::isCore() now treats subscribe./unsubscribe. prefixes as core so subscribe.accepted/event/closed are no longer routed to extension dispatch. isValidExtension() rejects any type whose first label starts with x-, matching the documented reservation for transport-internal experimental fields. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 81b3ebf commit 0b34805

2 files changed

Lines changed: 13 additions & 1 deletion

File tree

src/Extensions/ExtensionNamespace.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ final class ExtensionNamespace
2929
private const array CORE_PREFIXES = [
3030
'session.', 'tool.', 'job.', 'stream.',
3131
'human.', 'permission.', 'lease.',
32-
'subscribe', 'unsubscribe', 'artifact.',
32+
'subscribe', 'subscribe.', 'unsubscribe', 'unsubscribe.', 'artifact.',
3333
'event.emit', 'log', 'metric', 'trace.span',
3434
'ping', 'pong', 'ack', 'nack',
3535
'cancel', 'interrupt', 'resume', 'backpressure',
@@ -61,6 +61,11 @@ public static function isValidExtension(string $type): bool
6161
if (self::isCore($type)) {
6262
return false;
6363
}
64+
// The bare `x-` prefix is reserved for transport-internal
65+
// experimental fields and MUST NOT be a long-lived envelope type.
66+
if (str_starts_with($type, 'x-')) {
67+
return false;
68+
}
6469
// arcpx.<vendor>.<name>.v<n> e.g. arcpx.example.v1
6570
// reverse-DNS prefix e.g. com.acme.workflow.v2
6671
return preg_match('/^[a-z][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)+$/', $type) === 1;

tests/Unit/ExtensionsTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ public static function coreNames(): iterable
2323
yield 'ack' => ['ack'];
2424
yield 'metric' => ['metric'];
2525
yield 'log' => ['log'];
26+
yield 'subscribe' => ['subscribe'];
27+
yield 'subscribe.accepted' => ['subscribe.accepted'];
28+
yield 'subscribe.event' => ['subscribe.event'];
29+
yield 'subscribe.closed' => ['subscribe.closed'];
30+
yield 'unsubscribe' => ['unsubscribe'];
2631
}
2732

2833
#[DataProvider('coreNames')]
@@ -43,6 +48,8 @@ public static function extensionNames(): iterable
4348
yield 'uppercase first segment rejected' => ['ArcpX.example.v1', false];
4449
yield 'core prefix rejected' => ['session.foo', false];
4550
yield 'bare x- rejected (not a type name)' => ['x-foo', false];
51+
yield 'dotted x- rejected (not a type name)' => ['x-foo.v1', false];
52+
yield 'dotted x-experimental rejected' => ['x-experimental.foo.v1', false];
4653
}
4754

4855
#[DataProvider('extensionNames')]

0 commit comments

Comments
 (0)