diff --git a/.agents/notes/proposed/process/2026-08-04-artifact-first-npm-baseline-publication.i18n.yaml b/.agents/notes/proposed/process/2026-08-04-artifact-first-npm-baseline-publication.i18n.yaml index 496af417cf..5418a920e5 100644 --- a/.agents/notes/proposed/process/2026-08-04-artifact-first-npm-baseline-publication.i18n.yaml +++ b/.agents/notes/proposed/process/2026-08-04-artifact-first-npm-baseline-publication.i18n.yaml @@ -2,5 +2,5 @@ # 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/proposed/process/2026-08-04-artifact-first-npm-baseline-publication.md -2026-08-04-artifact-first-npm-baseline-publication.md: 47a65ef574231cdf4ba36fc7a70349fa952aa513 -2026-08-04-artifact-first-npm-baseline-publication.zh.md: 713007445cf8578b241f23d0733cf06522ebd3b2 +2026-08-04-artifact-first-npm-baseline-publication.md: e79a988531ac266d72f7d2c036bdd10f3e4520a9 +2026-08-04-artifact-first-npm-baseline-publication.zh.md: d7322547c665700aeffce77ea0bb39f622220fe3 diff --git a/.agents/notes/proposed/process/2026-08-04-artifact-first-npm-baseline-publication.md b/.agents/notes/proposed/process/2026-08-04-artifact-first-npm-baseline-publication.md index 47a65ef574..e79a988531 100644 --- a/.agents/notes/proposed/process/2026-08-04-artifact-first-npm-baseline-publication.md +++ b/.agents/notes/proposed/process/2026-08-04-artifact-first-npm-baseline-publication.md @@ -23,17 +23,23 @@ The prerelease version consists of the package stable base version, a second-pre The pack phase runs in this order: 1. Resolve the ref to an immutable commit, capture the UTC timestamp, derive the version from that commit's root manifest, and display the commit, timestamp, version, tag, registry, and output path. Both `pack` and `release` wait for Enter at this point before expensive work; `--yes` skips this confirmation for automation. -2. Install the frozen lockfile in an isolated detached worktree. Uncommitted files and old build output from the caller's working tree must not affect publication. +2. Install the frozen lockfile in an isolated detached worktree and run source-manifest publication constraints before staging. Uncommitted files and old build output from the caller's working tree must not affect publication. 3. Stage every target manifest with the derived version, remove its publication-time `private` marker, and rewrite internal workspace dependencies in `dependencies`, `devDependencies`, `optionalDependencies`, and `peerDependencies` to the same exact version. -4. Build the target commit completely, then run publint, built-package invariants, and publication-payload gates. +4. Build the target commit completely, then run publint and built-package invariants. 5. Pack every package in the target set without performing a registry write. 6. Inspect each tarball's package manifest, file inventory, internal dependency versions, name, and version, rejecting missing, duplicate, or extra tarballs. 7. Generate a release manifest and checksums containing the commit, version, tag, registry, and each package's tarball path, SHA-256, and npm integrity. -8. Install isolated consumers from the local tarballs and run the artifact-plane integration tests defined below. +8. Install an isolated consumer from the local tarballs and run the installed-artifact probes available in the current implementation; expand those probes to the complete artifact-plane integration matrix defined below. 9. Print one directly executable publish command only after the complete set passes. The pack command itself always remains free of remote writes. The local `release` command composes pack and publish. It first uses the pack confirmation above to fix the expected timestamp and version, then waits for Enter again after a successful pack before publishing the same manifest; `release --yes` skips both confirmations. Separate `pack` and `publish --manifest` operations remain the primitives used by split CI jobs and recovery. +## Current implementation boundary + +The checked-in pack command implements fixed-commit staging, exact internal dependency pins, static and tarball payload checks, the immutable manifest, and an isolated npm installation with every release tarball as a local top-level dependency. It runs the installed `dsh --version` and `dsh --dump-default-config` entries under plain Node, then starts the installed default TUI in a POSIX PTY, waits for its `main-session-` ready signal, and exits through `/exit` before printing the publish command. Publish supports integrity-based resumption, separates read-only registry verification from the authenticated identity check, and finishes with a complete remote integrity and dist-tag verification pass. + +Pull-request CI does not invoke the pack command; the installed-entry probes are local release checks rather than merge gates. Credential-free CI execution, package-owned probes for every other bin and public runtime entry, workflow-artifact transfer, and the protected publication job remain proposal scope. + ## Publication payload contract Published packages contain only build artifacts required by consumers. `package.json#files` must not include `src` or `lib/types/**/*.d.ts.map`; an independent tarball-content gate must also confirm that no `package/src/**` or `package/**/*.d.ts.map` entry exists, preventing manifest patterns or pack behavior from bypassing the static constraint. Runtime JavaScript, `.d.ts` declarations, configuration, assets, worker files, and dynamic bundle chunks must cover the actual entrypoint closure. diff --git a/.agents/notes/proposed/process/2026-08-04-artifact-first-npm-baseline-publication.zh.md b/.agents/notes/proposed/process/2026-08-04-artifact-first-npm-baseline-publication.zh.md index 713007445c..d7322547c6 100644 --- a/.agents/notes/proposed/process/2026-08-04-artifact-first-npm-baseline-publication.zh.md +++ b/.agents/notes/proposed/process/2026-08-04-artifact-first-npm-baseline-publication.zh.md @@ -23,17 +23,23 @@ monorepo 中可运行的源码并不能证明发布后的包可运行。workspac pack 阶段按以下顺序执行: 1. 将 ref 解析成不可变 commit,采集 UTC 时间戳,从该 commit 的根 manifest 派生版本,并显示 commit、时间戳、版本、tag、注册表和输出路径。`pack` 与 `release` 此时都会在昂贵操作开始前等待 Enter;自动化可用 `--yes` 跳过该确认。 -2. 在隔离的 detached worktree 中安装 frozen lockfile;调用方工作树中的未提交文件和旧构建输出不得参与发布。 +2. 在隔离的 detached worktree 中安装 frozen lockfile,并在暂存发布 manifest 之前运行源码 manifest 发布约束;调用方工作树中的未提交文件和旧构建输出不得参与发布。 3. 将所有目标 manifest 暂存为派生版本,移除发布时的 `private` 标记,并把 `dependencies`、`devDependencies`、`optionalDependencies` 与 `peerDependencies` 中的内部 workspace 依赖全部改写为同一精确版本。 -4. 完整构建目标 commit,再运行 publint、已构建包不变式和发布 payload 门禁。 +4. 完整构建目标 commit,再运行 publint 和已构建包不变式。 5. 为目标集合中的每个包执行 pack,但不执行任何注册表写入。 6. 检查 tarball 内的 package manifest、文件清单、内部依赖版本、包名和版本,并拒绝缺失、重复或额外的 tarball。 7. 生成包含 commit、版本、tag、注册表、每个包的 tarball 路径、SHA-256 与 npm integrity 的 release manifest 和校验和文件。 -8. 从本地 tarball 安装隔离消费方并运行下文定义的产物平面集成测试。 +8. 从本地 tarball 安装一个隔离消费方,运行当前实现已有的安装态产物探测,并将这些探测扩展为下文定义的完整产物平面集成测试矩阵。 9. 仅当整个集合通过时输出一个可直接执行的 publish 命令;pack 命令本身始终保持无远端写入。 本地 `release` 命令组合 pack 与 publish。它先通过上述 pack 确认确定预期时间戳和版本,再在 pack 成功后等待第二次 Enter,随后发布同一 manifest;`release --yes` 跳过两次确认。独立的 `pack` 与 `publish --manifest` 仍是 CI 分 job 和断点恢复使用的基础操作。 +## 当前实现边界 + +已提交的 pack 命令实现了固定 commit 暂存、内部依赖精确固化、静态与 tarball payload 检查、不可变 manifest,以及把每个发布 tarball 都作为本地顶层依赖的隔离 npm 安装。它在输出 publish 命令前,用普通 Node 运行安装后的 `dsh --version` 与 `dsh --dump-default-config` 入口,再在 POSIX PTY 中启动安装后的默认 TUI,等待其 `main-session-` 就绪信号,并通过 `/exit` 退出。Publish 支持按 integrity 恢复,将只读注册表验证与认证身份检查分离,并以完整的远端 integrity 和 dist-tag 验证结束。 + +拉取请求 CI 不会调用 pack 命令;安装态入口探测属于本地发布检查,而不是合并门禁。免凭据 CI 执行、其他每个 bin 与公开运行时入口的包自有探测、workflow artifact 传递及受保护 publish job 仍属于提案范围。 + ## 发布 payload 契约 发布包只携带消费方需要的构建产物。`package.json#files` 禁止包含 `src` 和 `lib/types/**/*.d.ts.map`;tarball 内容门禁还要独立确认不存在任何 `package/src/**` 与 `package/**/*.d.ts.map`,避免 manifest pattern 或 pack 行为绕过静态约束。运行时 JS、声明文件 `.d.ts`、配置、资源、worker 文件和 bundle 动态 chunk 必须按实际入口闭包收齐。 diff --git a/package.json b/package.json index fef0a1eb53..905c39f33b 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "constraints": "tsx scripts/check-workspace-constraints.ts", "doc-sync": "tsx scripts/run-gates.ts doc-sync", "hygiene": "pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-package-invariants && pnpm run verify-built-package-invariants && pnpm run verify-cordis-config && pnpm run verify-node-next-types && pnpm run verify-runtime-closure && pnpm run verify-vendored-links", + "publish:npm-baseline": "tsx scripts/publish-npm-baseline.ts", "dsh": "node --import tsx/esm apps/cli/src/bin.ts", "demo:headless": "node --import tsx packages/examples/cli-demo/src/bin.ts --config examples/headless-agent/cordis.yml", "demo:code-mode": "node scripts/demo-code-mode.mjs", diff --git a/packages/pty/pty-local/package.json b/packages/pty/pty-local/package.json index a859f482fa..61351ffd9b 100644 --- a/packages/pty/pty-local/package.json +++ b/packages/pty/pty-local/package.json @@ -22,7 +22,9 @@ "lib/index.js", "lib/invariant.js", "scripts/ensure-spawn-helper.mjs", - "lib/types/**/*.d.ts" + "lib/types/**/*.d.ts", + "lib/types/**/*.d.ts.map", + "src" ], "scripts": { "postinstall": "node scripts/ensure-spawn-helper.mjs" diff --git a/scripts/publish-npm-baseline.ts b/scripts/publish-npm-baseline.ts index c91add8a54..ad4be25b78 100644 --- a/scripts/publish-npm-baseline.ts +++ b/scripts/publish-npm-baseline.ts @@ -8,13 +8,15 @@ import { mkdirSync, mkdtempSync, readFileSync, + realpathSync, readdirSync, rmSync, writeFileSync, } from 'node:fs' import { tmpdir } from 'node:os' -import { basename, dirname, isAbsolute, join, normalize, resolve } from 'node:path' +import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from 'node:path' import { createInterface } from 'node:readline/promises' +import { pathToFileURL } from 'node:url' import { parseArgs } from 'node:util' const DEFAULT_REGISTRY = 'https://registry.npm.harnessment.com' @@ -27,6 +29,67 @@ const DEPENDENCY_SECTIONS = [ 'peerDependencies', ] as const const RELEASE_MANIFEST_NAME = 'manifest.json' +const POSIX_TUI_PROBE = String.raw` +import errno, fcntl, os, pty, select, signal, struct, sys, termios, time +node, bin_path, cwd, timeout_seconds = sys.argv[1:] +pid, fd = pty.fork() +if pid == 0: + os.chdir(cwd) + os.execvpe(node, [node, bin_path], os.environ.copy()) +fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack("HHHH", 30, 100, 0, 0)) + +output = bytearray() +welcome_dismissed = False +welcome_dismissed_at = None +session_seen_at = None +exit_sent = False +deadline = time.monotonic() + float(timeout_seconds) +status = None +while time.monotonic() < deadline: + ready, _, _ = select.select([fd], [], [], 0.05) + if ready: + try: + chunk = os.read(fd, 65536) + except OSError as error: + if error.errno != errno.EIO: + raise + chunk = b"" + if chunk: + output.extend(chunk) + + now = time.monotonic() + snapshot = bytes(output) + if not welcome_dismissed and b"Enter " in snapshot: + os.write(fd, b"\r") + welcome_dismissed = True + welcome_dismissed_at = now + if b"main-session-" in snapshot and session_seen_at is None: + session_seen_at = now + if not exit_sent and session_seen_at is not None: + if welcome_dismissed_at is not None and now - welcome_dismissed_at >= 0.2: + os.write(fd, b"/exit\r") + exit_sent = True + elif not welcome_dismissed and now - session_seen_at >= 1.0: + os.write(fd, b"/exit\r") + exit_sent = True + + waited, candidate = os.waitpid(pid, os.WNOHANG) + if waited == pid: + status = candidate + break + +if status is None: + os.kill(pid, signal.SIGKILL) + _, status = os.waitpid(pid, 0) +sys.stdout.buffer.write(output) +if session_seen_at is None: + sys.stderr.write("installed dsh TUI did not reach its main-session ready signal\n") + sys.exit(124) +actual_exit = os.waitstatus_to_exitcode(status) +if actual_exit != 0: + sys.stderr.write(f"installed dsh TUI exited {actual_exit}, expected 0\n") + sys.exit(125) +` interface CommandResult { status: number @@ -132,8 +195,8 @@ class CommandRunner { if (result.error !== undefined) throw result.error return { status: result.status ?? 1, - stdout: result.stdout ?? '', - stderr: result.stderr ?? '', + stdout: result.stdout, + stderr: result.stderr, } } } @@ -346,6 +409,83 @@ class ReleaseBundle { } } +/** Installs one complete bundle outside the workspace and probes the shipped dsh entry. */ +class InstalledBundleSmoke { + constructor( + private readonly bundle: ReleaseBundle, + private readonly runner: CommandRunner, + ) {} + + run(): void { + const consumerRoot = mkdtempSync(join(tmpdir(), 'dsh-npm-consumer-')) + try { + const dependencies = Object.fromEntries(this.bundle.manifest.packages.map(pkg => [ + pkg.name, + pathToFileURL(this.bundle.tarballPath(pkg)).href, + ])) + writeFileSync(resolve(consumerRoot, 'package.json'), `${JSON.stringify({ + name: 'dsh-npm-baseline-consumer', + version: '0.0.0', + private: true, + dependencies, + }, null, 2)}\n`) + + console.log( + `publish-npm-baseline: installing ${this.bundle.manifest.packages.length} local tarballs`, + ) + this.runner.run('npm', [ + 'install', + '--no-audit', + '--no-fund', + '--package-lock=false', + `--registry=${this.bundle.manifest.registry}`, + ], consumerRoot, npmClientEnvironment()) + + const bin = resolve(consumerRoot, 'node_modules/@deepseek-ai/dsh/lib/bin.js') + assertPathWithin(consumerRoot, bin, 'installed dsh bin') + const environment = installedArtifactEnvironment(consumerRoot) + const version = this.runner.capture( + process.execPath, + [bin, '--version'], + consumerRoot, + environment, + ) + if (version !== this.bundle.manifest.version) { + throw new Error( + `installed dsh --version returned ${JSON.stringify(version)}; ` + + `expected ${this.bundle.manifest.version}`, + ) + } + const config = this.runner.capture( + process.execPath, + [bin, '--dump-default-config'], + consumerRoot, + environment, + ) + if (config === '') throw new Error('installed dsh --dump-default-config returned no output') + this.probeTui(bin, consumerRoot, environment) + console.log('publish-npm-baseline: installed dsh entry and TUI probes passed') + } finally { + rmSync(consumerRoot, { recursive: true, force: true }) + } + } + + private probeTui(bin: string, consumerRoot: string, environment: NodeJS.ProcessEnv): void { + if (process.platform === 'win32') { + throw new Error('installed dsh TUI probe requires a POSIX host with python3') + } + const result = this.runner.result( + 'python3', + ['-c', POSIX_TUI_PROBE, process.execPath, bin, consumerRoot, '60'], + consumerRoot, + environment, + ) + if (result.status !== 0) { + throw commandFailure('python3', ['installed-dsh-tui-probe'], result) + } + } +} + /** Builds a release bundle without mutating the caller's checkout. */ class BaselinePackager { constructor( @@ -410,6 +550,7 @@ class BaselinePackager { console.log(`publish-npm-baseline: installing detached worktree ${plan.shortCommit}`) this.runner.run('pnpm', ['install', '--frozen-lockfile'], worktree.path) + this.runner.run('pnpm', ['run', 'constraints'], worktree.path) packageSet.stage(worktree.path, plan.version) mkdirSync(artifactDirectory, { recursive: true }) createdArtifactDirectory = true @@ -437,6 +578,7 @@ class BaselinePackager { plan.registry, this.runner, ) + new InstalledBundleSmoke(bundle, this.runner).run() createdArtifactDirectory = false console.log(`publish-npm-baseline: packed ${bundle.manifest.packages.length} packages`) console.log(` version: ${bundle.manifest.version}`) @@ -474,7 +616,8 @@ class RegistryPublication { ) {} async publish(assumeYes: boolean): Promise { - this.preflight() + this.pingRegistry() + this.requireIdentity() if (!assumeYes) await this.confirm() for (const pkg of this.bundle.manifest.packages) { @@ -498,13 +641,15 @@ class RegistryPublication { } this.ensureDistTag(pkg.name) } - console.log( - `publish-npm-baseline: published or confirmed ${this.bundle.manifest.packages.length} packages`, - ) + this.verifyRemote() } verify(): void { - this.preflight() + this.pingRegistry() + this.verifyRemote() + } + + private verifyRemote(): void { for (const pkg of this.bundle.manifest.packages) { const integrity = this.remoteIntegrity(pkg.name) if (integrity === undefined) { @@ -528,11 +673,15 @@ class RegistryPublication { ) } - private preflight(): void { + private pingRegistry(): void { const { registry } = this.bundle.manifest this.runner.capture( 'npm', ['ping', `--registry=${registry}`], this.npmWorkingDirectory, this.npmEnvironment, ) + } + + private requireIdentity(): void { + const { registry } = this.bundle.manifest const identity = this.runner.capture( 'npm', ['whoami', `--registry=${registry}`], this.npmWorkingDirectory, this.npmEnvironment, ) @@ -648,13 +797,8 @@ function stageInternalDependencies( releaseVersion: string, context: string, ): void { - for (const section of DEPENDENCY_SECTIONS) { - const dependencies = manifest[section] - if (dependencies === undefined) continue - if (!isRecord(dependencies)) throw new Error(`${context} ${section} must be an object`) - for (const name of Object.keys(dependencies)) { - if (internalNames.has(name)) dependencies[name] = releaseVersion - } + for (const { dependencies, name } of internalDependencyEntries(manifest, internalNames, context)) { + dependencies[name] = releaseVersion } } @@ -664,18 +808,33 @@ function validateInternalDependencyPins( releaseVersion: string, context: string, ): void { + for (const { section, name, range } of internalDependencyEntries(manifest, internalNames, context)) { + if (range !== releaseVersion) { + throw new Error( + `${context} has internal ${section} ${name}@${String(range)}; ` + + `expected exact version ${releaseVersion}`, + ) + } + } +} + +function* internalDependencyEntries( + manifest: Record, + internalNames: ReadonlySet, + context: string, +): Generator<{ + section: typeof DEPENDENCY_SECTIONS[number] + dependencies: Record + name: string + range: unknown +}> { for (const section of DEPENDENCY_SECTIONS) { const dependencies = manifest[section] if (dependencies === undefined) continue if (!isRecord(dependencies)) throw new Error(`${context} ${section} must be an object`) for (const [name, range] of Object.entries(dependencies)) { if (!internalNames.has(name)) continue - if (range !== releaseVersion) { - throw new Error( - `${context} has internal ${section} ${name}@${String(range)}; ` - + `expected exact version ${releaseVersion}`, - ) - } + yield { section, dependencies, name, range } } } } @@ -717,6 +876,33 @@ function npmClientEnvironment(): NodeJS.ProcessEnv { return environment } +function installedArtifactEnvironment(consumerRoot: string): NodeJS.ProcessEnv { + const environment = npmClientEnvironment() + delete environment.NODE_OPTIONS + delete environment.NODE_PATH + environment.DSH_HOME = resolve(consumerRoot, '.dsh') + environment.DSH_AGENTS_HOME = resolve(consumerRoot, '.agents') + environment.DSH_TELEMETRY_DISABLED = '1' + environment.DEEPSEEK_API_KEY = 'keyless-installed-tui-no-call' + environment.LANG = 'en_US.UTF-8' + environment.LC_ALL = 'en_US.UTF-8' + environment.LC_CTYPE = 'en_US.UTF-8' + environment.TERM = 'xterm-256color' + environment.COLUMNS = '100' + environment.LINES = '30' + delete environment.COLORTERM + return environment +} + +function assertPathWithin(root: string, path: string, label: string): void { + const rootPath = realpathSync.native(root) + const candidate = realpathSync.native(path) + const fromRoot = relative(rootPath, candidate) + if (fromRoot === '..' || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot)) { + throw new Error(`${label} resolved outside the isolated consumer: ${candidate}`) + } +} + function validateDistTag(value: string): void { if (value === '' || /\s/.test(value)) throw new Error(`invalid dist-tag: ${JSON.stringify(value)}`) } @@ -779,7 +965,9 @@ function formatCopyableCommand(command: string, args: string[]): string { function quoteShellArgument(value: string): string { if (/^[\w./:@=+-]+$/.test(value)) return value - return `'${value.replaceAll("'", `'"'"'`)}'` + const singleQuote = String.fromCodePoint(39) + const escapedSingleQuote = `${singleQuote}"${singleQuote}"${singleQuote}` + return `${singleQuote}${value.replaceAll(singleQuote, escapedSingleQuote)}${singleQuote}` } function printUsage(): void { diff --git a/vendor/README.md b/vendor/README.md index 6788409cb3..c59a86ccca 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -31,7 +31,7 @@ Intentionally **not** vendored (verified unused by this set): `reggol`, `@cordis Keep this log exhaustive — every divergence from upstream must be listed. 1. **`hmr/src/index.ts`**: removed the `./locales/en-US.yml` / `./locales/zh-CN.yml` imports, the `.i18n({...})` call on the `Config` schema, and the `src/locales/` directory. Rationale: those imports require a runtime YAML loader hook (`@cordisjs/unyaml`) that we do not vendor; the i18n texts only localize config descriptions. -2. **All `package.json` files**: regenerated — added `private: true`, added precise `files` entries for bundled runtime files and `lib/types/**/*.d.ts` / `.d.ts.map`, preserved `src` in `files` only for packages whose previous file list already shipped it, added a `./src/*` export where missing, pointed declaration metadata at `lib/types`, and removed upstream `devDependencies`/`scripts`/`repository` fields. Dependency and peer-dependency ranges preserved, except `hmr` declares `esbuild` as a direct dev dependency because its source imports the `BuildFailure` type and pnpm's strict workspace resolution requires the owner package to name that dependency. +2. **All `package.json` files**: regenerated — added `private: true`, added precise `files` entries for bundled runtime files and `lib/types/**/*.d.ts` / `.d.ts.map`, preserved `src` in `files` only for packages whose previous file list already shipped it, added a `./src/*` export where missing, pointed declaration metadata at `lib/types`, and removed upstream `devDependencies`/`scripts`/`repository` fields. Dependency and peer-dependency ranges are preserved except that `hmr` declares `esbuild` as a direct dev dependency because its source imports the `BuildFailure` type and pnpm's strict workspace resolution requires the owner package to name that dependency, and `loader` requires `node-addon-require-builtin@^0.1.4` to match the runtime used by published app packages. 3. **All `tsconfig.json` files**: regenerated to extend the repo-root `tsconfig.base.json`, emit TypeScript intermediates to `lib/types`, and declare project references. 4. **Vendored TypeScript source internal specifiers**: changed local relative imports/exports from upstream's specifier shape to explicit `.ts` specifiers so TypeScript rewrites emitted JS to `.js` while declarations keep explicit, NodeNext-safe `.ts` specifiers. This includes `loader/src/config/isolate.ts` using `declare module './entry.ts'`. 5. **`schemastery/tsdown.config.ts` and `logger-console/tsdown.config.ts`**: ours, not upstream files — per-package build-shape overrides (dual ESM+CJS output; separate node/browser entries) for the repo-root tsdown build. They read the JS emitted under `lib/types` and then write the publish runtime entries under `lib/`. Like the regenerated tsconfigs, they are not part of the upstream sync surface.