Repository navigation
feat(release): the changeset check compares unreleased packages' tarballs with their release tags - #66
Open
systemfsoftware-maker wants to merge 8 commits into
Open
feat(release): the changeset check compares unreleased packages' tarballs with their release tags#66systemfsoftware-maker wants to merge 8 commits into
systemfsoftware-maker wants to merge 8 commits into
Conversation
…ls against their release tags A changed publishable package passes the changeset gate once any intent names it, none included, and only plan on main compares its tarball with what its release tag records. The plan moves that comparison to pull-request time for the packages the diff affects and will not release, reusing plan's own resolution, and builds the flake tarballs only when such a package exists.
Coherence and feasibility found that the release app does not yet compose change evidence, that the refusal line had two specs, and that the scope wording forbade the extraction it asks for; the plan now adds ChangeEvidenceLive to the digests command, makes the digests command render one refusal line, and limits the scope to plan's behaviour. A caller with tags but no workspace-tarballs output now fails with a named line. Assumption 2 cites a cross-tree measurement: adoption-adapter's tarball built at main matches the integrity its tag recorded at 524505a. The DeepSeek peer's Sandbox-lane question is answered in KTD4: consumers run the reusable check, not this repository's sandbox.yml.
…the changeset check The changeset check passes a pull request whose intent gives a changed, released package a none bump, and only plan on main later finds that its tarball no longer matches the integrity its release tag records. The new phase changes @e2e/alpha after its 1.1.0 release under a none intent and runs the reusable check job over the stubbed flake tarballs; it expects one failing step that names @e2e/alpha@v1.1.0. On this commit the job passes, so the phase fails.
…ne function The per-member resolution plan ran before verifyIntegrity (ledger state, ledger tag commit, annotation, tarball digest) moves unchanged into releasedTarballChecks, so a second caller compares a released package's tarball by exactly plan's rules. plan's behaviour and tests are unchanged.
…alls with their release tags github-release-management digests <base> lists the released packages the change evidence names and the pending intents do not release. With --tarballs it compares each one's tarball with the integrity its release recorded, and refuses a mismatch with one line that names the tag, both integrities, the first differing file and the fix. The reusable changeset check runs the list after the intent verdict and builds the caller's .#workspace-tarballs only when the list is non-empty, so a pull request that releases what it changes pays one evidence pass and no build. The decision lives in compare-unreleased-tarballs.workflow.ts; the change-evidence refusal lines move to process-adapter's evidenceRefusalLines so both apps print them from one place.
…k and the evidence refusal schema Simplify review: both cells routed change evidence by strategy with the same five lines, both apps derived the gate task the same way, and the release app re-derived the evidence refusal union. release-language now exports evidenceOf and taskOf beside the port and ChangeEvidenceRefusal as a schema; the cells, both apps and the render use them. released-tarballs takes the existing IntegrityCandidate, and the digests render dispatches once. Behaviour and refusal lines are unchanged.
… test it over real adapters Code review: the command encoded its state by an empty list and an optional comparison that the workflow probed; it now carries one tagged targets union that the workflow matches. plan and the cell share unreleasedOf and ledgerEntries instead of copying the candidate selection. The feature runs over a real pnpm workspace, intent file and paths evidence instead of inline port doubles, and its archives pin a calendar mtime because git reads @0 as now, which made a row flaky. The e2e phase now also proves the named line when the caller's flake has no workspace-tarballs output. The refusal says when every packed file matches instead of naming an empty file. The plan is superseded by one that matches what shipped.
…evidence names Cross-model review (DeepSeek peer): under paths, a root-level or lockfile-only change can alter a released tarball without the evidence naming the package, so 'no released package changes without a release' overclaimed. The note now names the evidence as its scope. The refusal test also asserts both integrities and that they differ.
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.
Summary
A pull request could give a changed, already-released package a
noneintent. The changeset check accepted it, and onlygithub-release-management planonmainlater found that the package's tarball no longer matched the integrity its release tag records (TagIntegrityMismatch), so the Release run onmainwent red.The reusable changeset check now catches this before merge, with
plan's own comparison:github-release-management digests <base>lists the released packages (publishable, tagged at their current version) that the change evidence names and the pending intents do not release.--tarballs <dir>it compares each one's flake-built tarball with its recorded integrity, usingplan's rules, and refuses a mismatch withtag digests refused: <package>@v<version> changed without a release - recorded …, built …, first differing file …; give <package> a real bump in its intent, or revert its change.changeset-check.ymlruns the list after the intent verdict and builds the caller's.#workspace-tarballsonly when the list is non-empty. A caller whose flake lacks that output fails withtag digests: cannot build .#workspace-tarballs to compare <packages> with their release tags - the caller's flake must expose workspace-tarballs, as release.yml requires.planis unchanged in behaviour: its per-member integrity resolution moved verbatim intoreleased-tarballs.ts(releasedTarballChecks,unreleasedOf,ledgerEntries), and bothplanand the newunreleasedTarballsCellcall it. Repositories whose evidence names no such package see one note line and no build.Scope boundary, stated in the note itself: under
"strategy": "paths"a root-level or lockfile-only change can alter a released tarball without the evidence naming the package. That package is not compared here (pr-gate-runs-only-what-the-diff-affects), andplanstill refuses it after merge. A vacant list now saystag digests: the change evidence names no released package that changes without a release.Red before, green after
8467248(the e2e phase alone, unchanged product code): CI'scheckjob failed ine2e/tests/pipeline.integration.test.tsat the new phase,expected +0 to be 1(no failing step: the job passed the wrongnone), run 38465179714.efe46ec: the same phase passes, plus the R3 rerun without the flake output.Real-tool evidence (Nix-built tools, scratch clone of this repository)
adoption-adaptertest file touched, tarball unchangedadoption-adaptersource comment touchedprocess-adapter; verify refusedtag-annotation-lightweightforprocess-adapter@v0.0.0(plan's refusal, as expected for the lightweight tags)2 package(s) match their release tags, exit 0 (flake build 18-22 s)tarball-adaptertag digests refused: @systemfsoftware/tarball-adapter@v0.0.0 changed without a release - recorded sha512-VFDI…, built sha512-FHQF…, first differing file package/dist/index.d.ts; …, exit 1digests origin/mainpackages=empty, exit 0, 2.0 sChecks on
efe46ecFrom the GitHub Actions REST API (
run_watch), job start to finish:In this repository's own Changeset job the gate takes 1.6 s and the new
digestsstep 1.7 s; the job's first verdict step is unchanged, and the tarball build runs only when the list is non-empty.actionlint1.7.12 onchangeset-check.ymlreports only the twojob.workflow_repository/job.workflow_shafindings thatmainalso reports.Commits
f3247bf,64d88e8846724828c7f7fplan's released-tarball checks extracted, behaviour unchangedd3023a3digestscommand, workflow steps, README, intent13dd9b1evidenceOf,taskOf,ChangeEvidenceRefusalschema,evidenceRefusalLinessingle-sourceda635b6cefe46ecCode review receipt
Report-only
ce-code-review, FULL path. Baseef8970e78a5013fe9f8ec7daaebebce1ab23109e(main), reviewed head13dd9b17aa6c658c76ac893791498cab54ba16c6, run20261013-210000-ee16177c, 1135 changed lines.openrouter/deepseek/deepseek-v4.1-flashthrough omp (--no-tools, thinking high). The saved session transcript records one assistant turn,stopReason: stop, no model change. 1 P2 finding.packs-resolve.py, repository config):roots=6,errors=[],warnings=[], all fromhttps://github.com/systemfsoftware/systemfsoftware.git, pathcompound-packs:boundary-testingat8d071180798f9ea82ef5a4fd877152dacc02f6b6cell-architectureat8d071180798f9ea82ef5a4fd877152dacc02f6b6ci-wall-clockat8d071180798f9ea82ef5a4fd877152dacc02f6b6mutation-disciplineat8d071180798f9ea82ef5a4fd877152dacc02f6b6package-topologyat8d071180798f9ea82ef5a4fd877152dacc02f6b6schema-lawsat8d071180798f9ea82ef5a4fd877152dacc02f6b6a635b6c), 28 not matched.Matched pack rules
Cited lines are at the reviewed head
13dd9b1; each was checked against that tree.boundary-testingno-mocks-on-internal-glue.mdapps/github-release-management/tests/unreleased-tarballs.integration.test.ts:56boundary-testingport-fakes-pass-the-port-contract.mdapps/github-release-management/tests/unreleased-tarballs.integration.test.ts:150a635b6c: the feature now composesWorkspaceStoreLive,ChangesetsPortLiveandChangeEvidenceLive(unreleased-tarballs.integration.test.ts:122), no port double remainsboundary-testingreal-system-oracles.mdapps/github-release-management/tests/unreleased-tarballs.integration.test.ts:80cell-architecturepure-decision-workflows.mdpackages/github-release-engine/src/compare-unreleased-tarballs.workflow.ts:54a635b6c: the decide body is oneMatch.tagsExhaustiveovercommand.targets(compare-unreleased-tarballs.workflow.ts:57), noifcell-architecturesandwich-phase-order.mdpackages/github-release-engine/src/unreleased-tarballs.ts:104cell-architecturedecode-never-cast.mdapps/github-release-management/src/boundary.ts:73cell-architecturefour-channel-contracts.mdpackages/github-release-engine/src/unreleased-tarballs.ts:104cell-architectureservice-and-layer-boundaries.mdapps/github-release-management/src/main.ts:132cell-architecturesingle-namespace-barrel.mdpackages/github-release-engine/src/index.ts:11ci-wall-clockflake-outputs-build-once-on-main.md.github/workflows/changeset-check.yml:97ci-wall-clocklight-checks-run-the-pinned-tool.md.github/workflows/changeset-check.yml:101ci-wall-clockno-dev-shell-on-verdict-lanes.md.github/workflows/changeset-check.yml:97ci-wall-clockone-stuck-job-backstop.md.github/workflows/changeset-check.yml:101ci-wall-clockpr-gate-runs-only-what-the-diff-affects.md.github/workflows/changeset-check.yml:92ci-wall-clocksetup-under-a-minute.md.github/workflows/changeset-check.yml:97package-topologyenumerated-entry-modules.mdpackages/github-release-engine/src/index.ts:11package-topologyimport-time-inertness.mdapps/github-release-management/src/main.ts:132package-topologyone-access-path.mdpackages/release-language/src/index.ts:1package-topologypublic-signature-types.mdpackages/github-release-engine/src/index.ts:52package-topologysurface-changes-are-versioned.md.changeset/unreleased-tarballs-compared-at-pull-request.md:2schema-lawstagged-unions-over-state-by-presence.mdpackages/github-release-engine/src/compare-unreleased-tarballs.workflow.ts:41a635b6c: one requiredtargetsunion (compare-unreleased-tarballs.workflow.ts:48)schema-lawscross-field-invariants-as-struct-checks.mdpackages/github-release-engine/src/compare-unreleased-tarballs.workflow.ts:52a635b6c: the packages/comparison pairing is a union variant, nothing to cross-checkschema-lawsdata-only-schema-classes.mdpackages/github-release-engine/src/compare-unreleased-tarballs.workflow.ts:38schema-lawsinvariants-as-refinements.mdpackages/github-release-engine/src/unreleased-tarballs.ts:53schema-lawstests-own-no-schemas.mdapps/github-release-management/tests/unreleased-tarballs.integration.test.ts:36Not matched (28), each with its reason
boundary-testing/fake-and-real-store-laws.md: no store with a declared law suite is faked; the rule states "A port with no law suite is not a store, and the store rules do not apply to it", which is the case forWorkspaceStore/ChangesetsPorthere.boundary-testing/pin-dependency-semantics.md: no third-party driver/SDK is adopted, upgraded or wrapped, and no vendor protocol assumption is documented.boundary-testing/staged-protocol-evidence.md: no new multi-step connection/handshake/readiness protocol is modelled; git and tarball access go through existing adapters.cell-architecture/callable-vs-resource-syntax.md: no new ADT/blueprint/handle is given callable or interface-object invocation syntax.cell-architecture/handle-state-privacy.md: no per-instance state or driver token is stored on a handle/service; no storage mechanism chosen.cell-architecture/pipeable-dual-parity.md: no combinator or fluent operation is authored on a blueprint/handle.cell-architecture/pipeline-composition.md: the new cell is run directly by the CLI; no two cells are composed withCell.andThen-style combinators.cell-architecture/resource-vs-handle-duality.md: no cold blueprint vs runtime handle separation is designed.cell-architecture/scoped-lifecycle-boundaries.md: no acquired external target with a bounded lifetime, finalizer, or start/stop lifecycle is added.cell-architecture/staged-lawful-builders.md: no blueprint/builder algebra is authored.cell-architecture/store-serializable-unit-of-work.md: no read-decide-save against shared mutable state, and no serializable transaction or concurrency mechanism is chosen.cell-architecture/store-unit-of-work-handle.md: no store port whose reads feed a save, and no unit-of-work handle is declared.ci-wall-clock/gates-share-one-job.md: no pull-request gate job is split or matrixed; the modified job is a changeset check, which the pack carves out ("The commit-message, changeset and PR-title checks … are not gates here: they stay install-free jobs of their own").ci-wall-clock/heavy-lanes-state-their-daily-minutes.md: no heavy lane is added or re-budgeted; the added step is a conditional build inside an existing light check job.ci-wall-clock/heavy-suites-judge-every-main-commit.md: no postsubmit/main-only suite is added, moved, or removed.ci-wall-clock/no-lane-grades-the-repos-own-docs.md: no CI job or check takes the repo's own docs, plans or layout as its subject.ci-wall-clock/red-main-reverts-its-culprit-same-day.md: no postsubmit on-failure handling, P0 filing, or revert automation is added or changed.ci-wall-clock/release-promotes-the-staging-proven-build.md:release.yml, the tag/publish job and the image rollout are untouched; the change adds a presubmit comparison that reuses the same derivation.mutation-discipline/kill-survivors-with-contract-properties.md: no mutation report or surviving mutant is in scope; the new test is a behaviour test of the feature, not a test written to kill a reported mutant.mutation-discipline/suppress-by-shape-in-an-ignorer.md: no Stryker ignorer, disable comment or mutator exclusion is added or changed.mutation-discipline/unkillable-survivors-are-dead-code.md: no mutation baseline row is added, and no survivor reason about unreachable code is recorded.package-topology/condition-branch-agreement.md: no condition branch, types-build change, or tsdown entry restructuring is made.package-topology/declared-entry-points.md: no exports map entry, subpath or tsdown entry list changes;/renderand the packages' existing specifiers are reused as-is.schema-laws/arbitrary-filter-floors.md: no refinement or filter is added to a schema that property tests draw from, and no arbitrary/rejection-sampling failure is in scope.schema-laws/law-failure-is-a-codec-defect.md: no generated round-trip or encode-stability law fails, and no transformation/composition/custom encoding is authored.schema-laws/recursive-schema-suspend.md: no self-referential or mutually recursive schema is authored; noSchema.suspend/recursion-budget point arises.schema-laws/refusals-beside-generated-laws.md: no new refinement widens a rejection boundary that generated laws would leave green;NonEmptyArrayand the literals are stock refinements used, not authored.schema-laws/rich-type-over-foreign-encoded.md: no third-party wire shape (Node error, HTTP status line, OTLP span, vendor response) is decoded or reshaped.Findings and dispositions
schema-laws×2,cell-architectureUnreleasedTarballsCommandencoded its state by an empty list and an optional comparison that the workflow probed withifa635b6c: onetargetstagged union, matched exhaustivelyboundary-testingWorkspaceStore,ChangeEvidencePort,ChangesetsPortdoubles in the featurea635b6c: real pnpm workspace, real intent file, realpathsevidence over git; 13/13 app testsdigestsevidenceOf. This repository gates withturbo, so its own Changeset job runsdigeststhrough the turbo branch on every pull request (run 38470181768, step exit 0 in 1.7 s)paths, un-evidenced tarball changes pass the checkefe46ec: the vacant note names the evidence as its scope. Widening to every released package is rejected: it builds tarballs on every pull request, whichpr-gate-runs-only-what-the-diff-affectsandsetup-under-a-minuteforbid;planstill refuses after mergecannot build .#workspace-tarballsline untesteda635b6c: the e2e stub fails like nix when the output is absent; the AE1 phase reruns the job without it and asserts the line, including thepackagesoutput value. CI E2E passed onefe46ecplana635b6c:unreleasedOfandledgerEntries, called by botha635b6c: a new dated plan supersedes ita635b6c: says every packed file matches, so the archive metadata differsComparisonRefusaldeclared twicea635b6c: the cell imports the workflow's typegit archive --mtime=@0is read as "now", so the feature flaked across a second boundarya635b6c: calendar mtime; reproduced and proven withtar -tvnoneas an empty release lista635b6c: a realnoneintent throughChangesetsPortLiveefe46ec: asserts both and that they differchangeset-engine: nonemight trip its own checkcount-deleted-packages.mdreleaseschangeset-engineaspatch, and the check exits 0 on this branch locally and in CIplanmay project releases differentlya635b6c: both callunreleasedOfoverChangesetsPort.plan()nix build --rebuild .#workspace-tarballs); no permission is added$GITHUB_OUTPUTPackageName-decoded (no whitespace, newline or=) and the workflow reads the output only throughenv:git ls-remotehas no explicit timeouttimeout-minutes: 60is the backstopone-stuck-job-backstopprescribes;planmakes the sameremoteTagscallplan's verbatim-moved code; the real-tool table above exercises the lightweight refusal, two matched packages and the matched lineGateRefusalarms unreachable; tags only<name>@v<version>plan's, unchanged here@maintake the new step without a version boundaryworkspace-tarballsrequirementNo P0 or P1. With every finding fixed or rejected on evidence, the validator stage had no open finding to judge.
Delta after review (
13dd9b1..efe46ec)One named correctness lens through omp on
openrouter/deepseek/deepseek-v4.1-flash(tools off), given the outcome, the delta and the touched files. The saved transcript records one assistant turn,stop. 0 findings. Its residuals are covered above: the e2e tarball directory ends the phase in the same state it did before; no consumer reads the removed command fields (typecheck 32/32); R3's string andChangesetsPortLivewith anoneintent are proven by CI E2E and the 13/13 app tests.ce-simplify-code
Three lenses (reuse, quality, efficiency) over the settled diff. Applied in
13dd9b1: oneevidenceOfandtaskOfinrelease-languagefor both cells and apps,ChangeEvidenceRefusalas a schema,released-tarballstakingIntegrityCandidate, one dispatch in the digests render. Two lint gates moved the schema intoGate.schema.tsandtaskOfbeside the port.ce-compound
Learning recorded and validated, held for a docs-only pull request after this one merges:
docs/solutions/test-failures/git-archive-mtime-at-zero-means-now.md(git archivereads--mtime=@0as now; a pin is proven by reading it back).validate-doc-claims,validate-frontmatter, the solution-doc checker anddprintall exit 0.Privacy
Before each push and on this body: a scan of the diff, commit messages, branch name and body against all 54 of the org's private repository names, 0 hits. Mutation testing was not run locally.