ktx/docs/superpowers/plans/2026-05-11-single-public-runtime-artifact-cleanup.md
Andrey Avtomonov 9dad936ac7
feat: npm-managed Python runtime for @kaelio/ktx (#7)
* docs: add npm managed python runtime design

* build: add bundled python runtime wheel builder

* build: make local embedding dependencies optional

* build: bundle python runtime wheel in cli artifacts

* build: track bundled python runtime release artifact

* test: verify bundled python runtime wheel

* docs: add plan for bundled python runtime wheel

* test: cover managed python runtime lifecycle

* feat: add managed python runtime installer

* feat: add runtime command runner

* feat: expose runtime management commands

* test: verify managed python runtime commands

* docs: add plan for managed python runtime installer

* feat: add managed python command helper

* feat: use managed runtime for sl query compute

* feat: route sl query managed runtime policy

* docs: add plan for managed runtime sl query integration

* feat: add managed runtime daemon metadata

* feat: manage python daemon lifecycle

* feat: add runtime daemon start stop commands

* fix: verify managed runtime daemon lifecycle

* docs: add plan for managed runtime daemon lifecycle

* feat: add managed local embeddings config marker

* feat: add managed local embeddings daemon helper

* feat: use managed runtime for local embedding setup

* feat: pass managed runtime policy through setup

* docs: add plan for managed local embeddings runtime

* feat: read CLI package metadata dynamically

* feat: assemble public kaelio ktx npm package

* feat: release one public kaelio ktx npm artifact

* test: cover public kaelio ktx package invocations

* chore: verify public kaelio ktx package artifacts

* docs: add plan for public kaelio ktx npm package

* test: verify managed runtime in public package smoke

* test: finalize managed runtime release smoke

* docs: add plan for managed runtime release smoke

* test: specify local embeddings release smoke

* feat: add local embeddings runtime smoke

* chore: register local embeddings smoke

* fix: verify local embeddings smoke

* fix: restore artifact smoke python env helper

* docs: add plan for managed local embeddings release smoke

* refactor: share managed runtime install policy parsing

* feat: use managed runtime for agent semantic queries

* feat: use managed runtime for MCP semantic compute

* docs: add plan for managed agent and MCP semantic runtime

* feat(cli): add managed daemon HTTP helpers

* feat(cli): route local adapters through managed daemon

* feat(cli): use managed daemon for ingest helpers

* feat(cli): pass managed daemon options to scan

* feat(context): pass MCP ingest pull config options

* feat(cli): pass managed daemon options to serve ingest

* test: verify managed local ingest daemon runtime

* docs: add plan for managed local ingest daemon runtime

* docs: align managed runtime examples

* docs: add plan for managed runtime docs cleanup

* test: cover published package runtime smoke commands

* test: validate published package smoke outputs

* docs: add plan for published package runtime smoke

* build: stamp public npm package version

* release: add npm public release policy

* release: add guarded npm publish script

* release: document public npm release handoff

* docs: add plan for public npm release handoff

* test: cover managed runtime prune in package smoke

* docs: document managed runtime prune

* docs: add plan for managed runtime prune smoke and docs

* chore: encode uv runtime prerequisite policy

* fix: clarify missing uv runtime error

* docs: document uv runtime prerequisite

* docs: add plan for uv runtime prerequisite contract

* refactor: limit release artifacts to public package runtime

* chore: align release policy with bundled runtime wheel

* docs: describe single public runtime artifact surface

* test: verify single public runtime artifact contract

* docs: add plan for single public runtime artifact cleanup

* fix: align local embeddings smoke with public version

* docs: add plan for local embeddings smoke public version

* release: soft-launch as @kaelio/ktx@0.1.0-rc.0 on next tag

Publish target moves to the pre-release version 0.1.0-rc.0 under the next
dist-tag so npm install @kaelio/ktx (which resolves to latest) does not
pick up the soft-launch build. Users opt in via @kaelio/ktx@next.

* Fix release script boundary checks

* Remove PostHog from public package bundle
2026-05-11 15:50:34 +02:00

31 KiB

Single Public Runtime Artifact Cleanup Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Make release artifacts match the npm-managed Python runtime design: one public @kaelio/ktx npm tarball plus one bundled kaelio-ktx runtime wheel, with no standalone ktx-sl or ktx-daemon release artifacts.

Architecture: Keep python/ktx-sl and python/ktx-daemon as source packages used to assemble the bundled runtime wheel. Remove direct standalone Python wheel and source-distribution builds from the release artifact path, manifest, readiness policy, and artifact smoke docs. The packed npm package remains the only user-visible package; Python-backed verification continues through the managed runtime installed from the bundled wheel.

Tech Stack: Node 22 ESM scripts, node:test, pnpm, uv-built bundled runtime wheel, JSON release policy, Markdown.


Current state

This plan follows docs/superpowers/specs/2026-05-11-npm-managed-python-runtime-design.md.

The following plan files are based on that spec and are implemented in the current tree:

  • docs/superpowers/plans/2026-05-11-bundled-python-runtime-wheel.md
  • docs/superpowers/plans/2026-05-11-managed-python-runtime-installer.md
  • docs/superpowers/plans/2026-05-11-managed-python-runtime-command-integration.md
  • docs/superpowers/plans/2026-05-11-managed-python-runtime-daemon-lifecycle.md
  • docs/superpowers/plans/2026-05-11-managed-local-embeddings-runtime.md
  • docs/superpowers/plans/2026-05-11-public-kaelio-ktx-npm-package.md
  • docs/superpowers/plans/2026-05-11-managed-python-runtime-release-smoke.md
  • docs/superpowers/plans/2026-05-11-managed-local-embeddings-release-smoke.md
  • docs/superpowers/plans/2026-05-11-managed-agent-mcp-semantic-runtime.md
  • docs/superpowers/plans/2026-05-11-managed-local-ingest-daemon-runtime.md
  • docs/superpowers/plans/2026-05-11-managed-runtime-docs-and-postgres-smoke-cleanup.md
  • docs/superpowers/plans/2026-05-11-published-package-managed-runtime-smoke.md
  • docs/superpowers/plans/2026-05-11-public-npm-release-handoff.md
  • docs/superpowers/plans/2026-05-11-managed-runtime-prune-smoke-and-docs.md
  • docs/superpowers/plans/2026-05-11-managed-runtime-uv-prerequisite-contract.md

Implementation evidence found before writing this plan includes:

  • packages/cli/assets/python/manifest.json and packages/cli/assets/python/kaelio_ktx-0.1.0-py3-none-any.whl.
  • packages/cli/src/managed-python-runtime.ts, packages/cli/src/managed-python-command.ts, packages/cli/src/managed-python-daemon.ts, packages/cli/src/managed-local-embeddings.ts, packages/cli/src/managed-python-http.ts, and packages/cli/src/runtime.ts.
  • scripts/build-public-npm-package.mjs, scripts/package-artifacts.mjs, scripts/published-package-smoke.mjs, scripts/local-embeddings-runtime-smoke.mjs, scripts/publish-public-npm-package.mjs, and .github/workflows/release.yml.
  • release-policy.json is in npm-public-release-ready mode, publishes @kaelio/ktx, disables Python package publishing, and encodes the hard uv prerequisite.
  • README.md and examples/package-artifacts/README.md document public npm usage, managed runtime commands, runtime prune, and the uv prerequisite.

The remaining mismatch is in the artifact release surface:

  • scripts/package-artifacts.mjs still runs uv build --package ktx-sl and uv build --package ktx-daemon.
  • scripts/package-artifacts.mjs still adds ktx-sl and ktx-daemon wheel and source-distribution files to the artifact manifest.
  • scripts/package-artifacts.mjs still runs a direct Python clean-install smoke, even though the npm artifact smoke already proves Python-backed commands through the managed runtime.
  • release-policy.json still lists ktx-sl and ktx-daemon under python.packages.
  • examples/package-artifacts/README.md says the Python smoke installs standalone Python artifacts directly.

This plan removes those release artifacts. It does not delete the Python source packages because the bundled runtime wheel builder still copies from python/ktx-sl/semantic_layer and python/ktx-daemon/src/ktx_daemon.

File structure

  • Modify scripts/package-artifacts.test.mjs: make artifact tests expect only @kaelio/ktx plus the kaelio-ktx bundled runtime wheel, and add a guard that direct standalone Python artifact smoke code is gone.
  • Modify scripts/package-artifacts.mjs: stop building standalone Python artifacts, stop looking for their wheel and source-distribution files, remove their release metadata, and remove the direct Python artifact verification path.
  • Modify scripts/release-readiness.test.mjs: update release policy fixtures and readiness reports so the only Python release metadata is kaelio-ktx.
  • Modify release-policy.json: set python.packages to ["kaelio-ktx"].
  • Modify scripts/examples-docs.test.mjs: require docs to describe the single npm tarball plus runtime wheel artifact shape and reject the old direct Python-artifact smoke wording.
  • Modify README.md: clarify that python/ktx-sl and python/ktx-daemon are source packages, not release artifacts for the first npm release.
  • Modify examples/package-artifacts/README.md: replace the stale standalone Python smoke paragraph with the managed-runtime artifact contract.

Task 1: Make package artifact tests expect one runtime wheel

Files:

  • Modify: scripts/package-artifacts.test.mjs

  • Test: scripts/package-artifacts.test.mjs

  • Step 1: Update package artifact imports

In scripts/package-artifacts.test.mjs, replace the import from ./package-artifacts.mjs with this import:

import {
  CLI_PYTHON_ASSET_MANIFEST,
  INTERNAL_NPM_WORKSPACE_PACKAGES,
  RUNTIME_WHEEL_DISTRIBUTION_NAME,
  RUNTIME_WHEEL_NORMALIZED_NAME,
  RUNTIME_WHEEL_PACKAGE_VERSION,
  artifactManifestPath,
  buildArtifactCommands,
  copyRuntimeWheelAssets,
  findPythonArtifacts,
  NPM_ARTIFACT_PACKAGES,
  npmDemoSmokeSource,
  npmRuntimeSmokeSource,
  npmSmokePackageJson,
  npmVerifySource,
  packageArtifactLayout,
  packageReleaseMetadata,
  verifyArtifactManifest,
  writeArtifactManifest,
} from './package-artifacts.mjs';
  • Step 2: Remove standalone Python fixture setup

In scripts/package-artifacts.test.mjs, replace writeReleaseMetadataInputs with this function:

async function writeReleaseMetadataInputs(root) {
  for (const packageInfo of INTERNAL_NPM_WORKSPACE_PACKAGES) {
    await mkdir(join(root, packageInfo.packageRoot), { recursive: true });
    await writeJson(join(root, packageInfo.packageRoot, 'package.json'), {
      name: packageInfo.name,
      version: '0.0.0-private',
      private: true,
    });
  }
}

Replace writeUploadableArtifactFixtures with this function:

async function writeUploadableArtifactFixtures(layout) {
  await mkdir(layout.npmDir, { recursive: true });
  await mkdir(layout.pythonDir, { recursive: true });

  const fileContents = new Map([
    ...NPM_ARTIFACT_PACKAGES.map((packageInfo) => [
      layout.npmTarballs[packageInfo.name],
      `${packageInfo.name}-tarball`,
    ]),
    [
      join(layout.pythonDir, 'kaelio_ktx-0.1.0-py3-none-any.whl'),
      'kaelio-ktx-runtime-wheel',
    ],
  ]);

  for (const [path, contents] of fileContents) {
    await writeFile(path, contents);
  }
}
  • Step 3: Change build command expectations

In the buildArtifactCommands test, replace the body with this code:

  it('builds TypeScript packages and the runtime wheel before packing npm artifacts', () => {
    const layout = packageArtifactLayout('/repo/ktx');
    const commands = buildArtifactCommands(layout);

    assert.deepEqual(
      commands.slice(0, NPM_BUILD_PACKAGE_ORDER.length).map((command) => [command.command, command.args]),
      NPM_BUILD_PACKAGE_ORDER.map((packageName) => ['pnpm', ['--filter', packageName, 'run', 'build']]),
    );
    assert.deepEqual(
      commands.slice(NPM_BUILD_PACKAGE_ORDER.length, NPM_BUILD_PACKAGE_ORDER.length + 1).map((command) => [
        command.command,
        command.args,
      ]),
      [[process.execPath, ['scripts/build-python-runtime-wheel.mjs']]],
    );
    assert.deepEqual(
      commands.slice(NPM_BUILD_PACKAGE_ORDER.length + 1).map((command) => [command.command, command.args]),
      [[process.execPath, ['scripts/build-public-npm-package.mjs']]],
    );
  });
  • Step 4: Change release metadata expectations

In the packageReleaseMetadata test, replace the expected array with this array:

      assert.deepEqual(await packageReleaseMetadata(root), [
        {
          ecosystem: 'npm',
          packageName: '@kaelio/ktx',
          packageRoot: 'packages/cli',
          packageVersion: '0.1.0',
          private: false,
          releaseMode: 'ci-artifact-only',
        },
        {
          ecosystem: 'python',
          packageName: 'kaelio-ktx',
          packageRoot: 'python/runtime-wheel',
          packageVersion: '0.1.0',
          private: false,
          releaseMode: 'ci-artifact-only',
        },
      ]);
  • Step 5: Change Python artifact discovery expectations

Replace the findPythonArtifacts success test with this test:

  it('finds the bundled runtime wheel only', async () => {
    const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-test-'));
    try {
      await writeFile(join(root, 'kaelio_ktx-0.1.0-py3-none-any.whl'), '');

      assert.deepEqual(await findPythonArtifacts(root), {
        runtimeWheel: join(root, 'kaelio_ktx-0.1.0-py3-none-any.whl'),
      });
    } finally {
      await rm(root, { recursive: true, force: true });
    }
  });
  • Step 6: Change artifact manifest expectations

Inside the artifact manifest test, replace the Python package assertion with:

      assert.deepEqual(
        manifest.packages.filter((entry) => entry.ecosystem === 'python'),
        [
          {
            ecosystem: 'python',
            packageName: 'kaelio-ktx',
            packageRoot: 'python/runtime-wheel',
            packageVersion: '0.1.0',
            private: false,
            releaseMode: 'ci-artifact-only',
          },
        ],
      );

Replace the Python file assertion with:

      assert.deepEqual(
        manifest.files
          .filter((file) => file.ecosystem === 'python')
          .map((file) => ({
            artifactKind: file.artifactKind,
            ecosystem: file.ecosystem,
            packageName: file.packageName,
            packageVersion: file.packageVersion,
            path: file.path,
          })),
        [
          {
            artifactKind: 'wheel',
            ecosystem: 'python',
            packageName: 'kaelio-ktx',
            packageVersion: '0.1.0',
            path: 'python/kaelio_ktx-0.1.0-py3-none-any.whl',
          },
        ],
      );

In the verifyArtifactManifest success test, replace the file-count assertion with:

      assert.equal(manifest.files.length, NPM_ARTIFACT_PACKAGES.length + 1);
  • Step 7: Replace direct Python smoke tests with a dead-code guard

Remove the whole describe('pythonArtifactInstallArgs', ...) block.

In describe('verification snippets', ...), remove the test named asserts the Python modules that clean installs must expose.

Add this test after the verifyNpmArtifacts test:

describe('standalone Python artifact cleanup', () => {
  it('does not build or verify standalone Python package artifacts', async () => {
    const source = await readFile(new URL('./package-artifacts.mjs', import.meta.url), 'utf8');

    assert.doesNotMatch(source, /uv', \['build', '--package', 'ktx-sl'/);
    assert.doesNotMatch(source, /uv', \['build', '--package', 'ktx-daemon'/);
    assert.doesNotMatch(source, /async function verifyPythonArtifacts/);
    assert.doesNotMatch(source, /pythonArtifactInstallArgs/);
    assert.doesNotMatch(source, /pythonVerifySource/);
    assert.doesNotMatch(source, /ktx_sl-0\.1\.0/);
    assert.doesNotMatch(source, /ktx_daemon-0\.1\.0/);
  });
});
  • Step 8: Run package artifact tests and verify failure

Run:

node --test scripts/package-artifacts.test.mjs

Expected: FAIL. The failures mention the extra ktx-sl and ktx-daemon artifact commands, metadata entries, manifest files, or direct Python smoke helpers.

Task 2: Remove standalone Python artifacts from package artifacts

Files:

  • Modify: scripts/package-artifacts.mjs

  • Test: scripts/package-artifacts.test.mjs

  • Step 1: Remove dead constants and imports

In scripts/package-artifacts.mjs, replace the node:path import with this import:

import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';

Remove these constants:

const PACKAGE_VERSION = '0.0.0-private';
const PYTHON_PACKAGE_VERSION = '0.1.0';

Remove the whole ordersSource constant block.

  • Step 2: Make npm artifact names public-package only

Replace npmPackageTarballName with this function:

function npmPackageTarballName(packageName) {
  if (packageName !== PUBLIC_NPM_PACKAGE_NAME) {
    throw new Error(`Unsupported npm artifact package: ${packageName}`);
  }
  return publicNpmPackageTarballName(PUBLIC_NPM_PACKAGE_VERSION);
}
  • Step 3: Remove standalone Python build commands

Replace buildArtifactCommands with this function:

export function buildArtifactCommands(layout) {
  const packagesByName = new Map(INTERNAL_NPM_WORKSPACE_PACKAGES.map((packageInfo) => [packageInfo.name, packageInfo]));
  const npmBuildCommands = NPM_ARTIFACT_BUILD_ORDER.map((packageName) => {
    const packageInfo = packagesByName.get(packageName);
    if (!packageInfo) {
      throw new Error(`Unknown npm artifact build package: ${packageName}`);
    }
    return {
      command: 'pnpm',
      args: ['--filter', packageInfo.name, 'run', 'build'],
      cwd: layout.rootDir,
    };
  });
  const publicPackageCommand = {
    command: process.execPath,
    args: ['scripts/build-public-npm-package.mjs'],
    cwd: layout.rootDir,
  };

  return [
    ...npmBuildCommands,
    {
      command: process.execPath,
      args: ['scripts/build-python-runtime-wheel.mjs'],
      cwd: layout.rootDir,
    },
    publicPackageCommand,
  ];
}
  • Step 4: Discover only the bundled runtime wheel

Replace findOne and findPythonArtifacts with these functions:

function findOne(files, distributionName, suffix, label, pythonDir, version) {
  const normalized = normalizePythonDistributionName(distributionName);
  const found = files.find((file) => file.startsWith(`${normalized}-${version}`) && file.endsWith(suffix));
  if (!found) {
    throw new Error(`Missing Python artifact: ${label}`);
  }
  return join(pythonDir, found);
}

export async function findPythonArtifacts(pythonDir) {
  const files = await readdir(pythonDir);

  return {
    runtimeWheel: findOne(
      files,
      RUNTIME_WHEEL_DISTRIBUTION_NAME,
      '.whl',
      'kaelio-ktx runtime wheel',
      pythonDir,
      RUNTIME_WHEEL_PACKAGE_VERSION,
    ),
  };
}
  • Step 5: Emit release metadata only for npm and runtime wheel

Replace packageReleaseMetadata with this function:

export async function packageReleaseMetadata(rootDir = scriptRootDir()) {
  const npmPackages = await Promise.all(
    NPM_ARTIFACT_PACKAGES.map((packageInfo) => readNpmPackageMetadata(rootDir, packageInfo)),
  );

  return [
    ...npmPackages,
    releaseMetadataEntry({
      ecosystem: 'python',
      packageName: RUNTIME_WHEEL_DISTRIBUTION_NAME,
      packageRoot: 'python/runtime-wheel',
      packageVersion: RUNTIME_WHEEL_PACKAGE_VERSION,
      privatePackage: false,
    }),
  ];
}
  • Step 6: Remove dead TOML metadata helpers

Delete these helper functions from scripts/package-artifacts.mjs because release metadata no longer reads standalone Python pyproject.toml files:

function readProjectBlock(toml, sourcePath) {
  const lines = toml.split(/\r?\n/);
  const block = [];
  let inProject = false;

  for (const line of lines) {
    if (/^\[project\]\s*$/.test(line)) {
      inProject = true;
      continue;
    }
    if (inProject && /^\[.*\]\s*$/.test(line)) {
      break;
    }
    if (inProject) {
      block.push(line);
    }
  }

  if (!inProject) {
    throw new Error(`Missing [project] table in ${sourcePath}`);
  }
  return block.join('\n');
}
function readTomlStringField(projectBlock, fieldName, sourcePath) {
  const match = projectBlock.match(new RegExp(`^${fieldName}\\s*=\\s*"([^"]+)"\\s*$`, 'm'));
  if (!match) {
    throw new Error(`Missing project.${fieldName} in ${sourcePath}`);
  }
  return match[1];
}
async function readPyprojectMetadata(path) {
  const toml = await readFile(path, 'utf-8');
  const projectBlock = readProjectBlock(toml, path);
  return {
    name: readTomlStringField(projectBlock, 'name', path),
    version: readTomlStringField(projectBlock, 'version', path),
  };
}
  • Step 7: Emit manifest records only for npm and runtime wheel

Replace artifactPackageRecords with this function:

function artifactPackageRecords(layout, pythonArtifacts, packages) {
  const packagesByName = packageMetadataByName(packages);
  const npmRecords = NPM_ARTIFACT_PACKAGES.map((packageInfo) => ({
    artifactKind: 'tarball',
    artifactPath: layout.npmTarballs[packageInfo.name],
    metadata: requirePackageMetadata(packagesByName, packageInfo.name),
  }));

  return [
    ...npmRecords,
    {
      artifactKind: 'wheel',
      artifactPath: pythonArtifacts.runtimeWheel,
      metadata: requirePackageMetadata(packagesByName, RUNTIME_WHEEL_DISTRIBUTION_NAME),
    },
  ];
}
  • Step 8: Remove direct Python artifact verification helpers

Delete these exports and functions from scripts/package-artifacts.mjs:

export function pythonArtifactInstallArgs(python, pythonArtifacts) {
  return ['pip', 'install', '--python', python, pythonArtifacts.runtimeWheel];
}
export function pythonVerifySource() {
  return `
import importlib.metadata

import semantic_layer
import ktx_daemon

assert importlib.metadata.version("kaelio-ktx") == "0.1.0"
assert semantic_layer is not None
assert ktx_daemon.PACKAGE_NAME == "ktx-daemon"
`;
}
function pythonExecutable(projectDir) {
  if (process.platform === 'win32') {
    return join(projectDir, '.venv', 'Scripts', 'python.exe');
  }
  return join(projectDir, '.venv', 'bin', 'python');
}
export function npmSmokePythonEnv(projectDir, baseEnv = process.env) {
  const binDir = process.platform === 'win32' ? join(projectDir, '.venv', 'Scripts') : join(projectDir, '.venv', 'bin');
  const existingPath = baseEnv.PATH ?? '';

  return {
    ...baseEnv,
    PATH: existingPath ? `${binDir}${delimiter}${existingPath}` : binDir,
  };
}
async function verifyPythonArtifacts(layout, tmpRoot) {
  const pythonArtifacts = await findPythonArtifacts(layout.pythonDir);

  const projectDir = join(tmpRoot, 'python-clean-install');
  await mkdir(projectDir, { recursive: true });
  const python = pythonExecutable(projectDir);
  await writeFile(join(projectDir, 'verify_python.py'), pythonVerifySource());

  await runCommand('uv', ['venv', '.venv'], { cwd: projectDir });
  await runCommand('uv', pythonArtifactInstallArgs(python, pythonArtifacts), {
    cwd: projectDir,
  });
  await runCommand(python, ['verify_python.py'], { cwd: projectDir });
  await runCommand(python, ['-m', 'ktx_daemon', 'semantic-validate'], {
    cwd: projectDir,
    input: `${JSON.stringify({ sources: [ordersSource], dialect: 'postgres' })}\n`,
  });
}
  • Step 9: Verify artifacts through npm only

Replace verifyArtifacts with this function:

async function verifyArtifacts(layout) {
  await verifyArtifactManifest(layout);

  const tmpRoot = await mkdtemp(join(tmpdir(), 'ktx-artifacts-'));
  try {
    await verifyNpmArtifacts(layout, tmpRoot);
  } finally {
    await rm(tmpRoot, { recursive: true, force: true });
  }
}
  • Step 10: Run package artifact tests and verify pass

Run:

node --test scripts/package-artifacts.test.mjs

Expected: PASS. The output includes # fail 0.

  • Step 11: Commit package artifact cleanup

Run:

git add scripts/package-artifacts.mjs scripts/package-artifacts.test.mjs
git commit -m "refactor: limit release artifacts to public package runtime"

Task 3: Align release policy and readiness reports

Files:

  • Modify: release-policy.json

  • Modify: scripts/release-readiness.test.mjs

  • Test: scripts/release-readiness.test.mjs

  • Step 1: Update release readiness fixtures

In scripts/release-readiness.test.mjs, replace writeReleaseMetadataInputs with:

async function writeReleaseMetadataInputs(root) {
  for (const packageInfo of INTERNAL_NPM_WORKSPACE_PACKAGES) {
    await mkdir(join(root, packageInfo.packageRoot), { recursive: true });
    await writeJson(join(root, packageInfo.packageRoot, 'package.json'), {
      name: packageInfo.name,
      version: '0.0.0-private',
      private: true,
    });
  }
}

Replace writeUploadableArtifactFixtures with:

async function writeUploadableArtifactFixtures(layout) {
  await mkdir(layout.npmDir, { recursive: true });
  await mkdir(layout.pythonDir, { recursive: true });

  const fileContents = new Map([
    ...NPM_ARTIFACT_PACKAGES.map((packageInfo) => [
      layout.npmTarballs[packageInfo.name],
      `${packageInfo.name}-tarball`,
    ]),
    [join(layout.pythonDir, 'kaelio_ktx-0.1.0-py3-none-any.whl'), 'kaelio-ktx-runtime-wheel'],
  ]);

  for (const [path, contents] of fileContents) {
    await writeFile(path, contents);
  }
}

In releasePolicy, replace the python object with:

    python: {
      publish: false,
      repository: null,
      packages: ['kaelio-ktx'],
      ...pythonOverrides,
    },
  • Step 2: Update readiness report expectations

In scripts/release-readiness.test.mjs, replace every expected packageNames array with:

        packageNames: ['@kaelio/ktx', 'kaelio-ktx'],

There are three report assertions to update:

  • accepts the current ci-artifact-only policy, package metadata, and artifact manifest

  • reports required published package smoke when release mode requires it

  • accepts the npm public release ready policy

  • Step 3: Update checked release policy

In release-policy.json, replace the python.packages value with:

    "packages": ["kaelio-ktx"]
  • Step 4: Run readiness tests and verify pass

Run:

node --test scripts/release-readiness.test.mjs

Expected: PASS. The output includes # fail 0.

  • Step 5: Commit release policy cleanup

Run:

git add release-policy.json scripts/release-readiness.test.mjs
git commit -m "chore: align release policy with bundled runtime wheel"

Task 4: Document the single release artifact surface

Files:

  • Modify: scripts/examples-docs.test.mjs

  • Modify: README.md

  • Modify: examples/package-artifacts/README.md

  • Test: scripts/examples-docs.test.mjs

  • Step 1: Add failing docs assertions

In scripts/examples-docs.test.mjs, inside it('documents the public package artifact smoke shape', ...), add these assertions after the existing assert.match(readme, /managed Python runtime/); line:

    assert.match(readme, /public `@kaelio\/ktx` npm tarball and the bundled `kaelio-ktx` runtime wheel/);
    assert.match(readme, /does not install standalone Python packages directly/);
    assert.doesNotMatch(readme, /standalone Python distributions/);
    assert.doesNotMatch(readme, /installs the Python artifacts directly/);

In it('documents public npm and managed runtime usage in the README', ...), add these assertions after the existing uv assertions:

    assert.match(rootReadme, /release artifact manifest contains the public npm tarball and the bundled `kaelio-ktx` runtime wheel/);
    assert.match(rootReadme, /source packages for development, not public release artifacts/);
  • Step 2: Run docs tests and verify failure

Run:

node --test scripts/examples-docs.test.mjs

Expected: FAIL. The failure mentions the missing single-artifact wording in README.md or examples/package-artifacts/README.md.

  • Step 3: Update the package artifact example README

In examples/package-artifacts/README.md, replace:

The Python smoke project still installs the Python artifacts directly because
it verifies the standalone Python distributions that feed the bundled runtime
wheel.

with:

The artifact manifest contains the public `@kaelio/ktx` npm tarball and the
bundled `kaelio-ktx` runtime wheel. The smoke does not install standalone
Python packages directly; Python-backed behavior is verified through the
managed runtime installed from the npm package.
  • Step 4: Update the root README release status

In README.md, in the ## Release status section, replace this paragraph:

This repository builds one public npm artifact named `@kaelio/ktx`. The first
public npm handoff is policy-gated through `release-policy.json`, which keeps
Python package publishing disabled because KTX-owned Python code ships inside
the npm package as a bundled wheel.

with:

This repository builds one public npm artifact named `@kaelio/ktx`. The release
artifact manifest contains the public npm tarball and the bundled `kaelio-ktx`
runtime wheel. The first public npm handoff is policy-gated through
`release-policy.json`, which keeps Python package publishing disabled because
KTX-owned Python code ships inside the npm package as a bundled wheel. The
`python/ktx-sl` and `python/ktx-daemon` directories remain source packages for
development, not public release artifacts.
  • Step 5: Run docs tests and verify pass

Run:

node --test scripts/examples-docs.test.mjs

Expected: PASS. The output includes # fail 0.

  • Step 6: Commit docs cleanup

Run:

git add README.md examples/package-artifacts/README.md scripts/examples-docs.test.mjs
git commit -m "docs: describe single public runtime artifact surface"

Task 5: Verify the cleaned release artifact contract

Files:

  • Verify: scripts/package-artifacts.mjs

  • Verify: scripts/package-artifacts.test.mjs

  • Verify: scripts/release-readiness.test.mjs

  • Verify: scripts/examples-docs.test.mjs

  • Verify: release-policy.json

  • Verify: README.md

  • Verify: examples/package-artifacts/README.md

  • Step 1: Run focused tests

Run:

node --test scripts/package-artifacts.test.mjs scripts/release-readiness.test.mjs scripts/examples-docs.test.mjs

Expected: PASS. The output includes # fail 0.

  • Step 2: Verify stale artifact strings are gone from production/docs files

Run (scans only production and docs files, not test files — test files keep guard assertions that reference the removed strings):

rg -n "uv', \\['build', '--package', 'ktx-sl'|uv', \\['build', '--package', 'ktx-daemon'|ktx_sl-0\\.1\\.0|ktx_daemon-0\\.1\\.0|pythonArtifactInstallArgs|pythonVerifySource|verifyPythonArtifacts|standalone Python distributions|installs the Python artifacts directly" scripts/package-artifacts.mjs scripts/release-readiness.mjs README.md examples/package-artifacts/README.md release-policy.json

Expected: no matches.

  • Step 3: Verify release readiness against the current artifact manifest

Run:

pnpm run release:readiness -- --json

Expected: PASS when dist/artifacts/manifest.json has been rebuilt after this change. The JSON output contains:

{
  "releaseMode": "npm-public-release-ready",
  "packageNames": ["@kaelio/ktx", "kaelio-ktx"],
  "pythonPublishEnabled": false
}

If this command fails because the local artifact manifest was generated before the cleanup, run:

pnpm run artifacts:check
pnpm run release:readiness -- --json

Expected: both commands pass. The rebuilt manifest contains only npm/kaelio-ktx-0.1.0.tgz and python/kaelio_ktx-0.1.0-py3-none-any.whl under files.

  • Step 4: Run pre-commit on changed files when configured

Run:

uv run pre-commit run --files scripts/package-artifacts.mjs scripts/package-artifacts.test.mjs scripts/release-readiness.test.mjs scripts/examples-docs.test.mjs release-policy.json README.md examples/package-artifacts/README.md

Expected: PASS. If pre-commit is not installed or no pre-commit config exists, record the exact error and keep the focused Node test output from Step 1.

  • Step 5: Commit final verification fixes if needed

If Step 1, Step 2, Step 3, or Step 4 required code or docs fixes, commit them:

git add scripts/package-artifacts.mjs scripts/package-artifacts.test.mjs scripts/release-readiness.test.mjs scripts/examples-docs.test.mjs release-policy.json README.md examples/package-artifacts/README.md
git commit -m "test: verify single public runtime artifact contract"

If no fixes were required after the previous commits, do not create an empty commit.

Acceptance criteria

  • scripts/package-artifacts.mjs builds TypeScript packages, builds the bundled kaelio-ktx runtime wheel, copies it into CLI assets, and packs the public @kaelio/ktx npm tarball.
  • scripts/package-artifacts.mjs no longer builds ktx-sl or ktx-daemon standalone wheel or source-distribution artifacts.
  • Artifact manifests contain release metadata for @kaelio/ktx and kaelio-ktx only.
  • release-policy.json lists only @kaelio/ktx under npm.packages and only kaelio-ktx under python.packages.
  • The artifact smoke verifies Python-backed behavior through the installed public npm package and managed runtime, not by installing standalone Python artifacts directly.
  • Public docs state that python/ktx-sl and python/ktx-daemon remain source packages for development, not public release artifacts.

Self-review

Spec coverage:

  • The plan preserves the single public npm package requirement.
  • The plan preserves the bundled KTX-owned Python wheel requirement.
  • The plan keeps Python package publishing disabled.
  • The plan removes the only remaining artifact path that treated KTX-owned Python source packages as standalone release artifacts.

Placeholder scan:

  • No steps contain placeholder implementation text.
  • Every code-changing step names exact files and provides concrete replacement snippets.

Type and name consistency:

  • Public npm package name remains @kaelio/ktx.
  • Bundled runtime distribution name remains kaelio-ktx.
  • Runtime wheel filename remains kaelio_ktx-0.1.0-py3-none-any.whl.
  • Removed standalone Python artifact names are consistently ktx-sl and ktx-daemon.