Skip to content

Module publication verifies ownership before Gallery upload - #548

Open
Marius Storhaug (MariusStorhaug) wants to merge 21 commits into
mainfrom
module-publication-safety
Open

Marius Storhaug (MariusStorhaug) wants to merge 21 commits into
mainfrom
module-publication-safety

Conversation

@MariusStorhaug

@MariusStorhaug Marius Storhaug (MariusStorhaug) commented Oct 31, 2026 •

Copy link
Copy Markdown
Member

Standalone publication integrations now supply the resolver's exact tag, original tested source and repository-scoped contents-write GH_TOKEN; non-default prefixes also pass VersionPrefix. Normal framework callers receive those existing values internally and need no configuration, code, or invocation changes beyond selecting the compatible delivered release.

Publication checks immutable source, producing context and exact tested bytes before taking a Gallery version. Unknown or foreign proof stops; identical published contents are retained and missing GitHub uploads can resume without replacing them.

Breaking: Standalone publication requires verified source and producing context

Pass Publish.Module.Resolution.FullVersion as ReleaseTag, preserve Context.CommitSha or the original GITHUB_SHA that produced the tested artifact, and provide repository-scoped contents-write GH_TOKEN. Standalone Publish/Release calls using a prefix other than v pass the existing Publish.Module.VersionPrefix as VersionPrefix. Use the same source, prefix, producing context and tested artifact for both actions. Bundled callers receive these existing settings internally; no reusable-workflow scalar bump input, settings rewrite or consumer metadata change is added.

Unknown legacy packages or PR release context cannot be reused from numeric equality or a matching tag alone. Existing PR-produced stable and prerelease releases require the same final repository/PR/source footer. Retain the original tested bytes and coordinates for exact retry; unknown records require reviewed forward-only reconciliation, not retagging, asset replacement, package import, metadata edits or a fallback number. Genuine direct stable context remains supported.

Technical details
Coverage field Evidence
Product / target / channel Process-PSModule framework Actions/callable workflows, native GitHub repository release, stable
Actual successful inventory v8.0.7, release 386444646, published 2026-09-10T16:20:37Z; latest publication timestamp across 143 stable inventory records, not highest version or Latest
Artifact source readback PSModule/Process-PSModule 105e4dcefe1946782066aa50a5c7d3658319895a, Git tree 37093e61f57bbef368cb0f996891e0558167bc9b
Retained correspondence #538 merged to that SHA; native run 34501435941 succeeded contemporaneously
Receipt limit Legacy release has no versioned source footer; retained run logs return HTTP 410. Current artifact readback/correspondence are recorded, but historical publication-source receipt linkage is not fabricated. Coverage finalization remains held for that disposition.
Exact candidate PSModule/Process-PSModule 8ed237742e1a98b869310a8e14c770ba55bde0db
Complete compared range Published-source readback to candidate: 23 files, 3,852 insertions / 235 deletions; ancestry verified
Scoped implementation Main base 64ffb1d87f86bd5d487891fdb140b78385a00531 to candidate: 18 files, 3,843 insertions / 226 deletions, retained separately

The complete range includes the earlier documentation-address change below. The current PR-base diff does not define consumer release coverage. Standalone input/authentication and unproven-publication behavior are Major compatibility effects regardless of implementation size.

Related references

Changed: Framework guidance uses the project documentation address

Framework documentation and plugin/agent entry points use https://psmodule.io/Process-PSModule/ instead of https://psmodule.io/docs/ or the older repository-default/standards addresses. Update bookmarks or documentation links using the old address. No workflow input, module code, invocation or consumer metadata change is required for this guidance update.

Technical details

This is earlier unshipped guidance between published-source readback 105e4dcefe1946782066aa50a5c7d3658319895a and main 64ffb1d87f86bd5d487891fdb140b78385a00531: five files, nine insertions / nine deletions. It changes the plugin homepage, AGENTS/README links, repository-standard links and Zensical site_url. It is covered by the complete release range, not implemented or copied by this PR.

Fixed: Ownership is checked before irreversible publication

Verified absence, API/authentication failure and unknown proof are separate outcomes. The original source, producing PR/direct context, exact resolved tag/version and tested file-tree/ZIP bytes must agree with GitHub and exact raw Gallery evidence before publication. A prefix-independent module/version reservation prevents another prefix from taking the same Gallery coordinate. An observed tag lost or replaced during reservation cannot be recreated in that attempt.

The original JSON object/scalar shapes are checked before projection, including event fallback and resource metadata; a verified empty tag collection remains valid absence. Malformed terminal markers fail even when indented or inline. PR-produced stable and prerelease notes remain complete before the same provenance footer. Valid dotted module names are unchanged; their internal Git-coordinate dots are escaped as %2e to avoid Git-invalid .. or .lock components. Tested paths and NuGet-reserved metadata paths are verified before claims/uploads rather than silently renamed or discarded afterward.

Retain the exact original source, context, resolved coordinates and tested artifact when retrying. The publisher skips only a proven identical Gallery package; release creation/upload recheck both immutable refs and paired ZIP/payload proof, resume missing work and never clobber assets. Proof-driven version selection before Build is separately delivered by #513 / #549. A supported omitted-GUID rebuild can change payload bytes; its full-run recovery remains held for an explicit original-artifact selection/test-provenance contract, not a Build/GUID/consumer metadata rewrite here.

Technical details

Canonical source: 8ed237742e1a98b869310a8e14c770ba55bde0db, main base 64ffb1d87f86bd5d487891fdb140b78385a00531. The reservation PSModule-Publication/<escaped-lowercase-module>/<lowercase-version> and actual release tag bind the same annotated claim. ReleaseTag = VersionPrefix + GalleryVersion; both refs and tested bytes are rechecked before Gallery, creation and upload. Existing claims are never retargeted/deleted or fabricated for historical packages. The actual lightweight/annotated-tag peeler and footer algorithm originate at frozen #531 d06a8775f5e37d14fc9e641ef364cb42ada260b0; no second identity heuristic, checksum bump policy or lifecycle journal is introduced.

Plan and tests: #547 ordering reproduction, frozen contract, preflight, behavior coverage and current guidance/adoption evidence are persistent. The initial baseline is 59 tests; seven initial unsafe cases are red. Later review regressions include disappeared claims, raw arrays, version/prefix binding, reserved paths, shared-coordinate competition, inconsistent ZIP/tree proof, terminal markers and protocol/ref compatibility. The latest selected boundary experiment has 11 failures/one valid control before the correction, then all 12 pass. Stable authored/generated-note and missing-upload controls preserve complete notes and the footer. Current full local execution has 202 cases: 201 pass, one genuine Unix-only Windows skip, zero failures/not-run, with actual shuffle seed 42 and parallel throttle 2. Actual repository analysis and three-page documentation lint pass. Exact-head hosted checks pass; Test-Actions run 41247200090 is successful. No package code is imported for proof, no real artifacts are published in fixtures, and no consumer fleet is run.

Complete review: first/second inline findings and the late-reservation root 4432744396 are narrowly replied/resolved after their actual fixes. Summary-only reviews 5723821259, 5724308472 and 5725043230 were read and addressed through concrete source experiments, not inferred clean from zero comments. Latest owner correction response: #548 (comment). A fresh complete review is explicitly requested for exact 8ed2377; unpublished finding details/human assessment are not substituted with runner success. No overall clearance is claimed.

The original #531 footer trace is #531 (comment). Its literal guard remains unchanged at frozen 50f16425797ec67375d47262c8c79d3507fb2b21; the permanent-test root 4432389858 is verified narrowly resolved at #531 (comment). That does not clear terminal/helper composition, post-selection cleanup mutation or native #547/#513 prerequisites. No parallel #531 controller/test edits are made.

Template and alignment: bundled caller/settings and default/with-manifest fixtures at main 64ffb1d87f86bd5d487891fdb140b78385a00531 retain their interface and current-head checks pass. No external template/fleet adoption is claimed. #541 artifact workflows/fixtures/pipeline docs at 2001551566898e59efc764e68450c2238667d607 and #524 DefaultBump/Skip remain separately owned and untouched. The issue-convergence sweep covers #547 and related #325/#427/#513/#530/#531/#541; none of those additional leaves is fully satisfied. This PR closes only #547.

Changed surface Standards checked Framework docs checked Result
Immutable source/ref/footer and ZIP/nupkg proof PowerShell, Functions, Error Handling, Testing Source/tag/artifact/notes contract Fixed in this PR
Gallery/API metadata and mutation guards PowerShell, Error Handling Publish/Release interfaces Fixed in this PR
Composite actions/caller wiring GitHub Actions, YAML Existing Settings/FullVersion/Context/prefix contract Fixed in this PR
Identity/path/provenance/protocol fixtures Pester, Testing Existing action-suite validation Aligned
Current publication guidance Markdown, Natural Language Versioning/action contract Fixed in this PR
Native decision enforcement/adoption Release Management Actual released v2.0.3 pin/defaults Exception - PSModule/Release-GHRepository#108 and PSModule/Release-GHRepository#109

Authoring correspondence is finalized, ready but unmerged MSXOrg/docs#209 at bc03a5992615f65d1d5346dc48e7506e80b8b6ca; prior be8c340/27ff sources are historical evidence only. This changes authoring, not publisher, Build, artifact promotion, historical or governance authorization.

The separate #549 host receives only ordinary corrections and source ae7c3334aed87c30578a84efc89022cc87b6462d; its own delta against this preflight is 11 files, 928 insertions / 46 deletions and its local composed suite passes 230/one Windows Unix-only skip. Its actual host SHA/checks/full review and original-artifact decision remain independent gates. Safe order remains native prerequisites -> preflight -> Gallery-only recovery -> bounded #531 composition, without a native dependency cycle.

Keep this PR draft for full exact-head review/human assessment, historical publication-source evidence disposition and required native decision enforcement. No merge, auto-merge, live publication/enforcement, extra labels/pins/rules or historical edits are authorized.

Related references
Release decisions
  • Release: Yes - the native framework artifact and supported standalone/publication contract change.
  • Version bump: Major - the user selected existing Major plus owned release:major on this PR only for the breaking contract. The actual released Release-GHRepository v2.0.3 pin 750abed0ecab02394554b610dc3aa191e75e3248 consumes Major; release:major alone would select patch under its AutoPatching=true/MajorLabels=major,breaking defaults. This current-PR bridge makes intent effective without a pin change, but does not replace required decision enforcement or coverage finalization.
  • Mode: Stable - main-targeting native route; no prerelease override is authorized.
  • Labels: Major, release:major - exactly the existing user-approved current-PR transitional bridge. No historical/global migration or further label/pin/rule/publication action is taken. Broader producer/caller adoption requires a delivered reviewed release and immutable pin, not an unmerged dependency.

@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Super-linter summary

Language Validation result
CHECKOV Pass ✅
CSS Pass ✅
CSS_PRETTIER Pass ✅
GITHUB_ACTIONS Pass ✅
GITHUB_ACTIONS_ZIZMOR Pass ✅
GITLEAKS Pass ✅
GIT_MERGE_CONFLICT_MARKERS Pass ✅
HTML Pass ✅
JAVASCRIPT_ES Pass ✅
JAVASCRIPT_PRETTIER Pass ✅
MARKDOWN Pass ✅
NATURAL_LANGUAGE Pass ✅
POWERSHELL Pass ✅
PRE_COMMIT Pass ✅
SPELL_CODESPELL Pass ✅
TRIVY Pass ✅
YAML Pass ✅

All files and directories linted successfully

For more information, see the GitHub Actions workflow run

Powered by Super-linter

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical validation and race-condition flaws can produce an incorrect prerelease version and permit publication or release creation with invalid provenance or a changed tag claim.

5 open issues
What changed in this PR

This PR adds source and artifact ownership checks before PowerShell Gallery publication, sharing the same proof checks with GitHub release creation and recovery. Standalone publishers now require a resolved ReleaseTag and repository contents-write access; reusable-workflow callers receive the required context internally.

Changes:

  • Adds create-only annotated claims, deterministic ZIPs, and raw Gallery package verification.
  • Reworks publication and release recovery to reject conflicting ownership and preserve existing asset bytes.
  • Tightens Gallery lookup error handling and adds fixtures, tests, and adoption guidance.
File Description
docs/​content/​guides/​versioning-and-releases.md Documents publication ownership and safe retries.
.github/​workflows/​Publish-Module.yml Passes resolved tag and source to the publisher.
.github/​actions/​Resolve-PSModuleVersion/​tests/​Resolve-PSModuleVersion.ReadErrors.Tests.ps1 Tests Gallery errors and missing version metadata.
.github/​actions/​Resolve-PSModuleVersion/​src/​Resolve-PSModuleVersion.Helpers.psm1 Separates Gallery absence from lookup failures.
.github/​actions/​Release-PSModule/​tests/​Release-PSModule.WhatIf.Tests.ps1 Expands release recovery, provenance, and dry-run tests.
.github/​actions/​Release-PSModule/​tests/​Release-PSModule.Ownership.Tests.ps1 Tests claims, artifact digests, and package identity.
.github/​actions/​Release-PSModule/​src/​release.ps1 Adds ownership checks and non-clobbering asset uploads.
.github/​actions/​Release-PSModule/​src/​Release-PSModule.Helpers.psm1 Adds provenance readers and artifact proof helpers.
.github/​actions/​Release-PSModule/​README.md Documents source-bound release and recovery requirements.
.github/​actions/​Publish-PSModule/​tests/​Publish-PSModule.TestFixture.psm1 Provides isolated GitHub and Gallery fixtures.
.github/​actions/​Publish-PSModule/​tests/​Publish-PSModule.Recovery.Tests.ps1 Requires ownership proof for Gallery recovery.
.github/​actions/​Publish-PSModule/​tests/​Publish-PSModule.Preflight.Tests.ps1 Tests preflight failures, concurrent claims, and matching-byte retries.
.github/​actions/​Publish-PSModule/​src/​publish.ps1 Checks and claims ownership before Gallery upload.
.github/​actions/​Publish-PSModule/​src/​Publish-PSModule.Helpers.psm1 Implements publication readers, validation, and atomic tag claims.
.github/​actions/​Publish-PSModule/​README.md Explains new standalone inputs and authentication requirements.
.github/​actions/​Publish-PSModule/​action.yml Exposes resolved tag and immutable source inputs.

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Critical validation and path-normalization flaws allow malformed pull-request context to publish as identity 0 and silently rename Unix files, invalidating the release’s claim about tested content.

2 open issues
5 resolved since last review

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Ownership checks can permit publication under competing claims or unreadable provenance, while accepted NuGet-reserved artifact paths can cause verification to fail only after upload.

No open issues 🎉

3 previously missed issues

In code that hasn't changed since last review

High severity Reserve module/version ownership independently of tag prefix

.github/​actions/​Publish-PSModule/​src/​Publish-PSModule.Helpers.psm1:562

The create-only claim reserves ReleaseTag, not the Gallery module/version: candidates for TestModule 1.2.4 can independently claim v1.2.4 and 1.2.4 while the package is absent. If their bytes match but their source or PR differs, one can publish and the other's recheck accepts that package by payload digest without checking the competing source claim. Reserve and revalidate a shared module/version ownership coordinate independent of the configurable tag prefix.

High severity Reject malformed multiline terminal ownership comments

.github/​actions/​Release-PSModule/​src/​Release-PSModule.Helpers.psm1:102

A line-wrapped terminal ownership comment, such as a newline after PSModule.PrereleaseOwner:v1:, matches neither regex and is treated as absent. With a matching stable tag claim, this permits Gallery publication or asset upload despite unreadable final provenance. Recognize malformed multiline terminal comments while still ignoring completed markers followed by authored notes, and add a zero-write regression.

Medium severity Reject NuGet-reserved paths before claiming or uploading

.github/​actions/​Release-PSModule/​src/​Release-PSModule.Helpers.psm1:353

The artifact validator accepts NuGet-reserved paths that the package verifier later discards, such as package/services/metadata/core-properties/abc.psmdcp. That tested file contributes to ArtifactSha256 but is excluded at lines 630–633, so even an identical package fails proof and a new publication can discover the mismatch only after Gallery upload. Reject these reserved paths during candidate validation, before claiming a tag or uploading.

2 resolved since last review

Review effort: Balanced

@MariusStorhaug

Copy link
Copy Markdown
Member Author

Addressed the three actionable findings in the full summary of review 5723821259, rather than treating zero inline issues as clearance. aa86256 reserves one module/version coordinate independently of release-tag prefix and revalidates both immutable refs; the configured prefix also binds tag to tested version. The canonical footer reader rejects unreadable multiline terminal comments while retaining completed authored markers followed by notes. Candidate validation and raw package verification now share one NuGet metadata-path classifier, rejecting reserved tested paths before any claim/upload. Isolated Git and local zero-write tests cover the reported cases; no production tag/package mutation or bump policy was introduced. A fresh current-head review and hosted checks remain required.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A critical race condition allows a previously observed release tag to be recreated if it disappears during shared ref creation, permitting Gallery publication instead of failing.

1 open issue

Review effort: Balanced

Three isolated disappearance/replacement races each reach one Gallery spy write before the guard correction. Two partial-claim controls pass. No live publication is used.
Recheck the immediately preceding tag SHA after create-only reservation, including a tag first observed after preflight. Retain valid partial-claim completion without recreating a disappeared or replaced observed tag.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human assessment is needed because unpublished findings identify critical provenance-validation gaps and artifact-digest inconsistencies that can permit publication without valid provenance or leave ownership claims inconsistent.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

Malformed outer and field arrays are accepted as ownership, and a mismatched ZIP payload reaches Gallery in the isolated real-controller fixture. Preserve raw shape and bind both byte digests before mutation.
Validate raw producing context and proof shapes before projection. Verify ZIP bytes and payload as one snapshot against the tested tree before claims, Gallery writes, release creation and upload.
@MariusStorhaug

Copy link
Copy Markdown
Member Author

Read the full exact-head review 5724308472; zero inline issues is not clearance, and the unpublished finding details are not available through its comments endpoint. The direct source assessment reproduced two concrete gaps: malformed outer/field JSON arrays normalize into ownership, and a mismatched local ZIP/tree tuple can reach Gallery. Permanent regressions are in 2afb929, with event-projection regressions in 131c30b. Correction e59ac33 validates original raw object/scalar shapes, including the event fallback, and verifies archive bytes/payload together against the tested tree before claims and each publication mutation. The real-controller failure cases now make zero Gallery and zero GitHub writes; valid/direct/partial-claim controls remain. All 179 local cases pass with one real Unix-only Windows skip; actual repository analyzer and documentation rules pass. No Build/GUID/original-artifact promotion, settings policy or parallel #531 controller/test change is included. Fresh full review and exact-head hosted checks remain required; this is an owner correction, not human approval or native readiness.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human assessment is needed to evaluate a critical provenance-validation bypass that could permit publication with unreadable or foreign ownership footers, alongside publication-identity, metadata-validation, and Git-ref compatibility risks.

No open issues 🎉

Review effort: Balanced

…ity gaps

Publication spies accept malformed terminal markers and projected API arrays; stable PR provenance is absent, empty verified arrays fail, and valid module names can form invalid Git refs. All experiments are isolated.
Reuse the provenance footer for stable PR releases, preserve raw API object and collection shapes, and share typed immutable tag reads. Escape module-name dots without altering Gallery names or payload metadata.
@MariusStorhaug

Copy link
Copy Markdown
Member Author

The direct assessment of full summary 5725043230 found and reproduced the remaining concrete boundaries instead of treating No open issues as clearance. Red commit3faf9fb1315471a3b11f62c1856b914fd263dd19 records terminal marker placement/spacing bypasses, projected API metadata, absent stable-PR footer and invalid module Git coordinates. Correction8ed237742e1a98b869310a8e14c770ba55bde0db preserves raw transport shape and validates resource scalars; a verified empty array remains a valid collection. Both immutable refs share one tag reader. The same existing footer algorithm now covers stable PR releases with complete authored/generated notes, and unknown or malformed terminal context stops before publication. Dot escaping in the canonical module key retains valid Gallery/module names and unchanged payloads. All201 local cases pass with one genuine Unix-only Windows skip under actual shuffle/parallel; repository analysis and documentation lint pass. No live artifact, Build/GUID, policy, governance or parallel #531 controller/test change is taken. Unpublished review details remain unavailable; these are source-bound owner findings and fixes, not human/native clearance. Fresh exact-head review/checks remain gates.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human assessment is needed because a release-replacement race can bypass producing-context validation, while malformed tag metadata can still permit publication.

No open issues 🎉

Review effort: Balanced

@MariusStorhaug Marius Storhaug (MariusStorhaug) changed the title 🌟 [Major]: Module publication verifies source ownership before Gallery uploads Module publication verifies ownership before Gallery upload Oct 31, 2026
@MariusStorhaug
Marius Storhaug (MariusStorhaug) marked this pull request as ready for review October 31, 2026 22:39
Reject missing or malformed draft metadata and retain valid published controls. The branch-specific Release WhatIf fixture handback adds only two boolean fields and changes no assertions.
Copilot AI balanced review requested due to automatic review settings October 31, 2026 23:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human assessment is needed because critical validation gaps allow malformed ownership and permission metadata to authorize publication or resumed asset uploads.

No open issues 🎉

Review effort: Balanced

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Major release:major Publish a major release.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validate source-bound ownership before module publication

2 participants