feat(code-runtime): own portable-identifier exclusions at the seam

Move the reserved-word, reserved-global, reserved-error-member, and
dunder exclusion sets from the worker backend up to the code-runtime
seam package, and narrow the portable identifier subset to drop the
JS-only `$`. Every backend now imports one contract so a binding
namespace list valid on one backend is valid on all.

Delivers only the seam extension and the worker's adoption; the Python
backend, py-types renderer, and Code Mode language dispatch are later
PRs in the stack that depend on these exports.
This commit is contained in:
Chinesezjc
2026-08-07 11:20:05 +08:00
parent 70937db8a0
commit 5d4cea9dc1
8 changed files with 256 additions and 19 deletions
@@ -17,6 +17,68 @@ export type {
CodeRunResult,
} from './types.ts'
/**
* Binding globals EVERY backend refuses because SOME backend owns the slot in
* the program's namespace: `console` (the worker's log capture), and
* `__dsh_main__`/`__builtins__`/`__name__` (the Python bootstrap's wrapper
* and seeded module globals), and `__debug__`. One shared set — rather than each backend
* refusing only its own slots — keeps the portability promise real: a
* namespace list valid on one backend is valid on all, so a caller cannot
* pick a name that works on the worker and collides on Python (or vice
* versa). Dunder-form names are additionally covered by the identifier rule
* on `CodeBindingNamespace.global` only when they fail it; `__name__` et al.
* ARE valid identifiers, hence this explicit set. `__debug__` is listed for a
* different reason than a collision: CPython compiles a bare `__debug__`
* reference to the constant `True` and rejects any assignment to the name at
* COMPILE time, so an injected global under that name is unreachable from the
* program — accepted by validation, unusable on the Python backend, which is
* exactly the split the shared set exists to prevent.
*/
export const RESERVED_BINDING_GLOBALS: ReadonlySet<string> = new Set([
'console',
'__dsh_main__', '__builtins__', '__name__', '__debug__',
])
/**
* `CodeBindingErrorClass.memberNameProperty` names EVERY backend refuses, as
* one shared contract so a request valid on one backend is valid on all. The
* JS `Error` exclusions (`name`, `message`, `stack`) and Python's
* exception-protocol members (`args`, `with_traceback`, `add_note`) are
* listed by name; dunder-form names (`__*__`) are refused wholesale — several
* are constrained CPython descriptors whose `setattr` raises while
* constructing the rejection, and the exact set is an interpreter version
* detail. Any other non-empty own property name is accepted everywhere.
*/
export const RESERVED_ERROR_MEMBERS: ReadonlySet<string> = new Set([
'name', 'message', 'stack',
'args', 'with_traceback', 'add_note',
])
/** Dunder form (`__*__`): object-protocol slots in Python, refused as {@link RESERVED_ERROR_MEMBERS | error members} on every backend. */
export const DUNDER_MEMBER = /^__.*__$/
/**
* Reserved words of EVERY shipped backend language (ECMAScript Python),
* refused as {@link CodeBindingNamespace.global} / error-class names by all
* backends. The portable-identifier contract promises a namespace list valid
* on one backend is valid on every backend; a per-language check would let
* `lambda` pass the TypeScript backend and fail the Python one. Extending the
* seam with a new language means widening this union (a breaking review of
* existing binding names, by design).
*/
export const PORTABLE_RESERVED_WORDS: ReadonlySet<string> = new Set([
// ECMAScript reserved words and reserved-in-strict-mode names.
'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do',
'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import', 'in',
'instanceof', 'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof',
'var', 'void', 'while', 'with', 'yield', 'let', 'static', 'implements', 'interface', 'package',
'private', 'protected', 'public', 'arguments', 'eval',
// Python 3.x keywords and soft keywords not already above ('type' and '_'
// are soft keywords: legal names in practice, reserved here for safety).
'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'def', 'del', 'elif', 'except', 'from',
'global', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'match', 'type', '_',
])
declare module 'cordis' {
interface Context {
codeRuntime: CodeRuntime