Skip to content

epic #252: Publish the release as one package carrying both toolkits, the component payload and the changelog - #333

Merged
sameera merged 6 commits into
mainfrom
feat/252-publish-one-package
Aug 27, 2026
Merged

epic #252: Publish the release as one package carrying both toolkits, the component payload and the changelog#333
sameera merged 6 commits into
mainfrom
feat/252-publish-one-package

Conversation

@sameera

@sameera sameera commented Aug 27, 2026

Copy link
Copy Markdown
Owner

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 PR
into main closes the stories it implements. The epic itself closes through
/nxs.close, not by merge.

Draft opened by utils/implement-epic.sh; /nxs.analyze runs against it next.

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
sameera marked this pull request as ready for review August 27, 2026 10:08
@sameera
sameera merged commit e9b8bb6 into main Aug 27, 2026
@sameera
sameera deleted the feat/252-publish-one-package branch August 27, 2026 10:08
@sameera
sameera restored the feat/252-publish-one-package branch August 27, 2026 10:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant