epic #252: Publish the release as one package carrying both toolkits, the component payload and the changelog - #333
Merged
Merged
Conversation
Nexus has never been published. The root manifest was private, carried no binary declaration and no published-files list, and its version had sat at its initial value — an adopter who never clones the repository had nothing to install. The manifest now publishes `@sameera/nexus` at the single version `VERSION` declares, with two binaries: `nexus` (the bundled TypeScript executable) and `nexus-gh` (the Python toolkit's entry point). `pnpm nexus:build-release` stages all three published parts — the bundle, the Python toolkit's files and the component payload — under one release tree, so both halves walk up to the same `VERSION` and the payload travels inside the package with no install-time network step. The app runtime dependencies move down to `apps/prime`, leaving the published package with none. Two things had to change for an installed run to work at all. The bundle now carries a shebang, emitted in the esbuild banner so the pinned bytes are the shipped bytes; the five launcher sources drop their own, since two shebangs in one output is a syntax error. And the entry guard compares real paths, because a package manager links the declared binary as a symlink — the old string comparison made the installed executable exit 0 having done nothing. Closes #308
The payload walk applied no ignore filter. It swept up the toolkit's own test files and gitignored byte-code, which made the fingerprint a property of the machine — a clean checkout with no cached byte-code, or a different python3 minor version, hashed differently and failed the gate. What ships is now stated: a denylist of incidental categories (`__pycache__`, `*.pyc`, `tests`, `test_*.py`) rather than an allowlist of files, so a new capability module ships the moment it is written and a new test file never does. The manifest the fingerprint is taken over sorts by code unit, not by locale, so two clean checkouts of the same commit agree whatever the machine has cached. `nexus-gh` suppresses byte-code at the entry point, so a stage leaves no `__pycache__` in the repository it ran against. The build now produces one JavaScript bundle rather than six. The five standalone launchers have been reachable as verbs on `nexus` since #247 and only the vendored bundle ever needed them as separate files, so they are deleted and every spec that ran one runs the dispatcher with the matching verb. That is what lets the pin be two entries: the executable and the payload — one key covering the Python toolkit and the component tree together, since both now ship. Closes #309
The gate that protects the release pointed at the vendoring step. Its remediation told a maintainer to copy an artifact into a hub repository, which is an action the release no longer involves. It now rebuilds the executable, recomputes the payload hash from the stated payload sources, and compares both against the committed pin — consulting no copy of either inside any repository. A failure names the file that moved rather than only reporting that two digests differ: a per-file payload manifest is written beside the pin by the same step, so the two can never describe different runs, and the pin keeps its two entries as the sole pass/fail authority. The remediation names re-pinning, which copies nothing into any repository. Closes #310
Once the components leave every repository, an adopter no longer sees a component change in their own diff. Of the three failure classes, the one that fails silently is a change in what a stage decides — no version check can detect it, and nothing replaces that review surface unless the changelog does. So the changelog's rules are executable rather than editorial. Every item must name a pipeline stage or an adopter-visible behaviour; a commit subject, a file path or a library version is rejected, because none of those is something an adopter runs. A release whose diff touched a component body must name the stage a lead will now experience differently. A release that changed no stage behaviour says so explicitly rather than going absent. The suite runs these rules against the live entry, so a release cannot be cut past them. The release procedure is written out end to end — choose the version in the one place it is declared, write the entry, re-pin and verify, tag, publish, paste the section onto the releases page — and one check holds the tag, the published manifest and the newest entry to the version VERSION declares. Closes #312
An adopter installs one package carrying two toolkits, so the environment they must supply is two interpreters, not one. The manifest declared the Node floor and said nothing about Python — leaving the interpreter that runs the gh toolkit to be discovered at the first failure. So the manifest names both floors and the platform boundary: `engines.python` beside `engines.node`, and `os` limited to darwin and linux. The Python floor is 3.10 because a module-level `str | None` annotation in the toolkit is evaluated at import on 3.9. The readme repeats both floors where an adopter reads them before installing, and the suite holds the two in agreement. Refs #308
Reconciles epic #252 (publish the release as one package) with epic #257 (retire the hub's vendored tools directory), merged ahead of it in PR #335. Both epics touched the pin step from opposite ends: #257 removed where the artifacts were copied, #252 redefined what ships. Only the destination conflicted. - `vendorBundles` keeps #252's stated release payload (`PAYLOAD_KEY`, `hashPayload`, the diagnostic `payload-manifest.json`) and loses #257's retired copy half: no `--tools-dir`, no `parseArgs`, and `runCli` rejects every argument by name. Staging into the release tree stays in `pack-release.ts`, whose destination is the package, not a repository. - The pin script keeps main's accurate name, `nexus:pin-bundles`. The remediation hint, `docs/delivery/release-procedure.md` and its AC2 test now name it. - `checkFingerprint` keeps #252's payload-difference reporting; the pin and the payload manifest are regenerated over the merged tree. Full test suite green (506 portable-tools tests, 10 projects), gh-toolkit unittests pass, atlas check and concept validator OK.
sameera
marked this pull request as ready for review
August 27, 2026 10:08
This was referenced Aug 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the story sub-issues of #252, one commit per story in blocked_by order.
Each commit body carries its own
Closes #<story>line, so merging this PRinto
maincloses the stories it implements. The epic itself closes through/nxs.close, not by merge.Draft opened by
utils/implement-epic.sh;/nxs.analyzeruns against it next.