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-07-31 18:10:33 +08:00
parent 70937db8a0
commit 5d4cea9dc1
8 changed files with 256 additions and 19 deletions

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-31-code-runtime-portable-identifier-seam.md
2026-07-31-code-runtime-portable-identifier-seam.md: a24a2c03c937a7d569528504b5bd5fba812009a2
2026-07-31-code-runtime-portable-identifier-seam.zh.md: 1bef607e31a5820ba849f09eb7ae4cc781c45a84

View File

@@ -0,0 +1,42 @@
# Agent Note: the code-runtime seam owns portable-identifier exclusions
Status: implemented
English | [中文](2026-07-31-code-runtime-portable-identifier-seam.zh.md)
## Problem
The code-runtime seam promises that a binding-namespace list valid on one backend is valid on every backend, so a Code Mode consumer can hand the same bindings to any registered runtime without knowing its language. The first backend, `dsh-code-runtime-worker`, privately owned the identifier rules that enforce part of that promise: an `IDENTIFIER` regex that allowed the JS-only `$`, a `RESERVED_WORDS` set holding only ECMAScript keywords, and a `RESERVED_ERROR_PROPERTIES` set of three JS `Error` slots. Those rules described the worker's own language, not the seam's portability contract.
A second backend written against a different language (CPython, arriving in a later PR of this stack) would either re-declare its own rules — letting `lambda` pass the worker and fail Python, or `$tools` pass the worker and fail every non-JS backend — or import the worker's, inverting the dependency so the seam's implementation reached into a sibling implementation. Neither keeps the portability promise real: it would hold only for the backend a caller happened to test against.
## Decision
The seam package (`@deepseek-ai/dsh-code-runtime`) exports the portable-identifier exclusion contract as four named constants, and every backend imports them rather than re-declaring:
- `PORTABLE_RESERVED_WORDS` — the union of ECMAScript and Python reserved words. A namespace global or error-class name matching any is refused on all backends, so `lambda` is refused even though it is a legal JS parameter name. Adding a language widens this union, which is a deliberate breaking review of existing binding names.
- `RESERVED_BINDING_GLOBALS` — globals some backend owns in the program's namespace: `console` (the worker's log capture) and `__dsh_main__`/`__builtins__`/`__name__`/`__debug__` (the Python bootstrap's wrapper and seeded module globals). Refused everywhere so a namespace list cannot pick a name that works on one backend and collides on another.
- `RESERVED_ERROR_MEMBERS` — error-member names every backend refuses: the JS `Error` slots (`name`, `message`, `stack`) and Python's exception-protocol members (`args`, `with_traceback`, `add_note`).
- `DUNDER_MEMBER` — the dunder-form regex (`__*__`), refused as an error member wholesale because several are constrained CPython descriptors whose exact set is an interpreter-version detail.
The seam also narrows the portable identifier subset to `[A-Za-z_][A-Za-z0-9_]*` (documented on `CodeBindingNamespace.global` and `CodeBindingErrorClass`), dropping the JS-only `$`. The worker consumes the shared constants: `RESERVED_WORDS = PORTABLE_RESERVED_WORDS`, `RESERVED_ERROR_PROPERTIES = RESERVED_ERROR_MEMBERS`, its `IDENTIFIER` regex loses `$`, and its error-member check adds `DUNDER_MEMBER`.
The constants live at the seam even though only one backend ships in this PR: the whole point is that the contract is language-agnostic and owned above any single language. A backend that violated it would be the bug, and the shared set is where a reviewer looks to see what "portable" means.
## Scope
This PR delivers only the seam extension and the worker's adoption of it. No Python backend, `py-types` renderer, or Code Mode language dispatch ships here — they are later PRs in the stack that depend on these exports. The seam README's worker-only wording is left unchanged for the same reason: linking to a `dsh-code-runtime-python` README that does not yet exist would break the dead-link gate.
## Alternatives considered
**Each backend declares its own exclusions.** Rejected: it makes the portability promise per-backend. A binding list the caller tested on the worker could be refused by Python, which is exactly the split the seam exists to prevent.
**The Python backend imports the worker's constants.** Rejected: it inverts the dependency — the seam's implementations would reach into a sibling implementation for a contract neither owns. The contract belongs above both, at the seam.
**Keep `$` in the portable identifier subset.** Rejected: `$` is JS-only spelling. Allowing it would let `$tools` pass the worker and fail every non-JS backend, breaking portability for a purely cosmetic gain.
## Consequences
Bought: one place — the seam package — defines what a portable binding name is, and every backend enforces the same contract by import. A namespace list valid on one backend is valid on all, verifiably, not by coincidence of which backend the caller tested.
Cost: existing worker callers using a `$`-containing global now fail identifier validation. Under the pre-release stance this is a corrected foundation, not a compatibility break to shim. The worker's seam-misuse tests gain cases for `$tools`, Python exception members (`args`), dunders (`__dict__`), and a Python-owned global (`__dsh_main__`), proving the shared set is enforced from the worker side.

View File

@@ -0,0 +1,42 @@
# Agent Notecode-runtime seam 拥有可移植标识符排除集
Status: implemented
[English](2026-07-31-code-runtime-portable-identifier-seam.md) | 中文
## Problem
code-runtime seam 承诺:在一个后端上有效的绑定命名空间列表,在每个后端上都有效,因此 Code Mode 消费方可以把同一组绑定交给任何已注册的运行时,而不必知道它的语言。首个后端 `dsh-code-runtime-worker` 私自拥有了执行这项承诺一部分的标识符规则:一个允许 JS 专有 `$``IDENTIFIER` 正则、一个只含 ECMAScript 关键字的 `RESERVED_WORDS` 集合,以及一个含三个 JS `Error` 槽位的 `RESERVED_ERROR_PROPERTIES` 集合。这些规则描述的是 worker 自身的语言,而非 seam 的可移植性契约。
一个针对不同语言CPython将在本 stack 后续 PR 中到来)编写的第二后端,要么重新声明自己的规则——让 `lambda` 通过 worker 却在 Python 上失败,或让 `$tools` 通过 worker 却在每个非 JS 后端上失败——要么导入 worker 的规则,从而反转依赖,使 seam 的一个实现伸手进入另一个兄弟实现。二者都无法让可移植承诺成真:它只对调用方恰好测试过的那个后端成立。
## Decision
seam 包(`@deepseek-ai/dsh-code-runtime`)以四个具名常量导出可移植标识符排除契约,每个后端导入它们而非重新声明:
- `PORTABLE_RESERVED_WORDS`——ECMAScript 与 Python 保留字的联集。任何命名空间 global 或 error-class 名称匹配其中之一,都在所有后端上被拒绝,因此 `lambda` 即便是合法的 JS 参数名也被拒绝。新增一门语言即扩宽此联集,这是对现有绑定名称的一次有意的破坏性复审。
- `RESERVED_BINDING_GLOBALS`——某个后端在程序命名空间中拥有的 global`console`worker 的日志捕获)与 `__dsh_main__`/`__builtins__`/`__name__`/`__debug__`Python bootstrap 的包装器与预置模块 global。在所有后端上被拒绝使命名空间列表无法选到一个在某后端能用、在另一后端冲突的名称。
- `RESERVED_ERROR_MEMBERS`——每个后端都拒绝的 error-member 名称JS `Error` 槽位(`name``message``stack`)与 Python 异常协议成员(`args``with_traceback``add_note`)。
- `DUNDER_MEMBER`——dunder 形式正则(`__*__`),作为 error member 被整体拒绝,因为其中若干是受约束的 CPython 描述符,其确切集合是解释器版本细节。
seam 同时把可移植标识符子集收窄为 `[A-Za-z_][A-Za-z0-9_]*`(记录在 `CodeBindingNamespace.global``CodeBindingErrorClass` 上),去掉 JS 专有的 `$`。worker 消费这些共享常量:`RESERVED_WORDS = PORTABLE_RESERVED_WORDS``RESERVED_ERROR_PROPERTIES = RESERVED_ERROR_MEMBERS`,其 `IDENTIFIER` 正则去掉 `$`,其 error-member 检查加上 `DUNDER_MEMBER`
尽管本 PR 只交付一个后端,这些常量仍置于 seam要点正是该契约与语言无关且拥有权在任何单一语言之上。违反它的后端才是 bug而共享集合正是复审者查看"可移植"含义的地方。
## Scope
本 PR 只交付 seam 扩展与 worker 对它的采用。这里不交付任何 Python 后端、`py-types` 渲染器或 Code Mode 的语言分发——它们是本 stack 中依赖这些导出的后续 PR。seam README 中仅描述 worker 的措辞保持不变,理由相同:链接到一个尚不存在的 `dsh-code-runtime-python` README 会破坏死链 gate。
## Alternatives considered
**每个后端声明自己的排除集。** 拒绝:这让可移植承诺变成逐后端成立。调用方在 worker 上测过的绑定列表可能被 Python 拒绝,而这正是 seam 存在要防止的分裂。
**Python 后端导入 worker 的常量。** 拒绝这反转依赖——seam 的实现会为一个二者都不拥有的契约伸手进入兄弟实现。契约属于二者之上,即 seam。
**在可移植标识符子集中保留 `$`。** 拒绝:`$` 是 JS 专有拼写。允许它会让 `$tools` 通过 worker 却在每个非 JS 后端上失败,为纯粹表面的好处破坏可移植性。
## Consequences
获得一个地方——seam 包——定义什么是可移植绑定名称,每个后端通过导入执行同一契约。在一个后端上有效的命名空间列表在所有后端上都有效,这是可验证的,而非取决于调用方测试了哪个后端的巧合。
代价:现有使用含 `$` global 的 worker 调用方现在会在标识符校验时失败。在预发布立场下这是一次被纠正的地基,而非需要 shim 的兼容性破坏。worker 的 seam-misuse 测试新增了 `$tools`、Python 异常成员(`args`、dunder`__dict__`)与一个 Python 拥有的 global`__dsh_main__`)等用例,从 worker 侧证明共享集合被执行。

View File

@@ -13,7 +13,7 @@ import { fileURLToPath } from 'node:url'
import { Context } from 'cordis'
import z from 'schemastery'
import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
import { CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
import { CodeRuntime, DUNDER_MEMBER, PORTABLE_RESERVED_WORDS, RESERVED_BINDING_GLOBALS, RESERVED_ERROR_MEMBERS } from '@deepseek-ai/dsh-code-runtime'
import type { CodeBindingNamespace, CodeJsonValue, CodeRunFailure, CodeRunRequest, CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
import { snapshotJsonValue } from '@deepseek-ai/dsh-session'
import type { ReplyMessage, WorkerBootData, WorkerToHost } from './protocol.ts'
@@ -65,20 +65,27 @@ const ELU_POLL_INTERVAL_MS = 25
/** Smallest cap that can represent the counted payloads: an empty logs array plus an empty JSON failure message. */
const MIN_OUTPUT_BYTES = 4
/** ECMAScript reserved words that cannot be async-function parameter names — rejected as binding globals. */
const RESERVED_WORDS = new Set([
'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',
])
/**
* The seam's cross-language reserved-word union: the portable-identifier
* contract promises a namespace list valid here is valid on every backend, so
* a Python keyword like `lambda` is refused even though it is a legal JS
* parameter name.
*/
const RESERVED_WORDS = PORTABLE_RESERVED_WORDS
/** Valid async-function parameter name (the binding global becomes one). */
const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/
/**
* The seam's language-portable identifier subset (see
* `CodeBindingNamespace.global`): no `$`, which is JS-only spelling — the same
* namespace list must be usable against every backend regardless of language.
*/
const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/
/** Error properties whose binding-member replacement would destroy the promised Error contract. */
const RESERVED_ERROR_PROPERTIES = new Set(['name', 'message', 'stack'])
/**
* The seam's shared error-member exclusions (plus the dunder rule below):
* enforced identically here and in the Python backend so an errorClass valid
* on one backend is valid on all.
*/
const RESERVED_ERROR_PROPERTIES = RESERVED_ERROR_MEMBERS
/**
* The shell a program is wrapped in for the type-strip, matching the
@@ -331,7 +338,11 @@ export class WorkerCodeRuntime extends CodeRuntime {
if (!IDENTIFIER.test(namespace.global) || RESERVED_WORDS.has(namespace.global)) {
throw new Error(`dsh-code-runtime-worker: binding global ${JSON.stringify(namespace.global)} is not a usable identifier`)
}
if (namespace.global === 'console' || bindings.has(namespace.global)) {
// RESERVED_BINDING_GLOBALS is the seam's shared backend-owned set:
// `console` is THIS backend's log-capture slot; the dunder entries are
// the Python bootstrap's — refused here too so the namespace list stays
// portable across backends.
if (RESERVED_BINDING_GLOBALS.has(namespace.global) || bindings.has(namespace.global)) {
throw new Error(`dsh-code-runtime-worker: duplicate binding global ${JSON.stringify(namespace.global)}`)
}
bindings.set(namespace.global, namespace)
@@ -344,10 +355,11 @@ export class WorkerCodeRuntime extends CodeRuntime {
if (!IDENTIFIER.test(descriptor.name) || RESERVED_WORDS.has(descriptor.name)) {
throw new Error(`dsh-code-runtime-worker: binding error class ${JSON.stringify(descriptor.name)} is not a usable identifier`)
}
if (descriptor.name === 'console' || bindings.has(descriptor.name) || errorClassNames.has(descriptor.name)) {
if (RESERVED_BINDING_GLOBALS.has(descriptor.name) || bindings.has(descriptor.name) || errorClassNames.has(descriptor.name)) {
throw new Error(`dsh-code-runtime-worker: duplicate injected global ${JSON.stringify(descriptor.name)}`)
}
if (descriptor.memberNameProperty.length === 0 || RESERVED_ERROR_PROPERTIES.has(descriptor.memberNameProperty)) {
const member = descriptor.memberNameProperty
if (member.length === 0 || RESERVED_ERROR_PROPERTIES.has(member) || DUNDER_MEMBER.test(member)) {
throw new Error(`dsh-code-runtime-worker: binding error member property ${JSON.stringify(descriptor.memberNameProperty)} is not usable`)
}
errorClassNames.add(descriptor.name)

View File

@@ -787,6 +787,9 @@ describe('WorkerCodeRuntime — seam misuse and lifecycle', () => {
const cases: [string, RegExp][] = [
['not valid!', /not a usable identifier/],
['await', /not a usable identifier/],
// `$tools` is legal JS but outside the seam's language-portable subset:
// the same namespace list must work against every backend's language.
['$tools', /not a usable identifier/],
['console', /duplicate binding global/],
]
for (const [global, message] of cases) {
@@ -822,6 +825,14 @@ describe('WorkerCodeRuntime — seam misuse and lifecycle', () => {
])).rejects.toThrow(/duplicate injected global/)
await expect(run([namespace('tools', 'CallError', '')])).rejects.toThrow(/member property.*not usable/)
await expect(run([namespace('tools', 'CallError', 'message')])).rejects.toThrow(/member property.*not usable/)
// The shared exclusion set covers Python's exception-protocol members and
// dunders too, so the same errorClass is valid (or not) on every backend.
await expect(run([namespace('tools', 'CallError', 'args')])).rejects.toThrow(/member property.*not usable/)
await expect(run([namespace('tools', 'CallError', '__dict__')])).rejects.toThrow(/member property.*not usable/)
// The Python bootstrap's owned globals are refused here too (shared
// RESERVED_BINDING_GLOBALS), keeping namespace lists backend-portable.
await expect(runtime.run({ program: 'return 1', bindings: [{ global: '__dsh_main__', functions: {} }] }))
.rejects.toThrow(/duplicate binding global/)
})
it('rejects config values that are not positive numbers', async () => {

View File

@@ -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

View File

@@ -28,9 +28,14 @@ export type CodeJsonValue = null | boolean | number | string | CodeJsonValue[] |
* of a particular consumer such as Code Mode.
*/
export interface CodeBindingErrorClass {
/** Constructor global and resulting `Error.name` (must be a usable JS identifier). */
/** Constructor global and resulting `Error.name`; same portable identifier rule as {@link CodeBindingNamespace.global}. */
name: string
/** Non-empty own property for the member name; cannot replace `name`, `message`, or `stack`. */
/**
* Non-empty own property for the member name. The portable exclusion set is
* `RESERVED_ERROR_MEMBERS` plus dunder-form names (`__*__`), enforced
* identically by every backend; any other name — identifiers or not — is
* accepted everywhere.
*/
memberNameProperty: string
}
@@ -42,7 +47,13 @@ export interface CodeBindingErrorClass {
* collisions.
*/
export interface CodeBindingNamespace {
/** The global identifier the program sees (must be a valid JS identifier). */
/**
* The global identifier the program sees. Must match the LANGUAGE-PORTABLE
* identifier subset `[A-Za-z_][A-Za-z0-9_]*` and no language's reserved
* words, so the same namespace list works against every backend regardless
* of `language` — a JS-only spelling like `$tools` is rejected by design,
* not just by the Python backend.
*/
global: string
/** The callable members, keyed by the exact name the program calls. */
functions: Record<string, CodeBindingFunction>

View File

@@ -0,0 +1,51 @@
import { describe, expect, it } from 'vitest'
import {
DUNDER_MEMBER,
PORTABLE_RESERVED_WORDS,
RESERVED_BINDING_GLOBALS,
RESERVED_ERROR_MEMBERS,
} from '@deepseek-ai/dsh-code-runtime'
/**
* The seam owns the portable-identifier exclusion sets so every backend
* enforces one contract: a namespace list valid on one backend is valid on
* all. These assertions pin the shared membership backends import rather than
* re-declare.
*/
describe('seam-owned portable identifier exclusions', () => {
it('RESERVED_BINDING_GLOBALS covers each backend-owned slot', () => {
expect(RESERVED_BINDING_GLOBALS.has('console')).toBe(true)
expect(RESERVED_BINDING_GLOBALS.has('__dsh_main__')).toBe(true)
expect(RESERVED_BINDING_GLOBALS.has('__builtins__')).toBe(true)
expect(RESERVED_BINDING_GLOBALS.has('__name__')).toBe(true)
expect(RESERVED_BINDING_GLOBALS.has('__debug__')).toBe(true)
expect(RESERVED_BINDING_GLOBALS.has('tools')).toBe(false)
})
it('RESERVED_ERROR_MEMBERS covers the JS Error and Python exception-protocol members', () => {
for (const name of ['name', 'message', 'stack', 'args', 'with_traceback', 'add_note']) {
expect(RESERVED_ERROR_MEMBERS.has(name)).toBe(true)
}
expect(RESERVED_ERROR_MEMBERS.has('code')).toBe(false)
})
it('DUNDER_MEMBER matches dunder-form names only', () => {
expect(DUNDER_MEMBER.test('__dict__')).toBe(true)
expect(DUNDER_MEMBER.test('__init__')).toBe(true)
expect(DUNDER_MEMBER.test('_private')).toBe(false)
expect(DUNDER_MEMBER.test('name')).toBe(false)
expect(DUNDER_MEMBER.test('__mid')).toBe(false)
})
it('PORTABLE_RESERVED_WORDS is the union of ECMAScript and Python reserved words', () => {
// ECMAScript-only keyword.
expect(PORTABLE_RESERVED_WORDS.has('function')).toBe(true)
// Python-only keyword — refused here so the list stays portable.
expect(PORTABLE_RESERVED_WORDS.has('lambda')).toBe(true)
expect(PORTABLE_RESERVED_WORDS.has('nonlocal')).toBe(true)
// Shared keyword.
expect(PORTABLE_RESERVED_WORDS.has('class')).toBe(true)
// Ordinary identifier is not reserved.
expect(PORTABLE_RESERVED_WORDS.has('tools')).toBe(false)
})
})