Skip to content
Merged
  •  
  •  
  •  
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,42 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

### Changed

- Use native PowerShellGet staging and `Update-Module` for existing
installations without Force or an install fallback, while retaining
PSResourceGet discovery, exact target/source validation, consent and recovery.
A worker handoff preserves the original installation lock during mutation.
Operation-owned repository initialization handles defaults populated lazily
during native registration, preventing intermittent pre-installation failure
without changing the user's repositories.
Native updates use the alphanumeric prerelease labels generated by
Gerber/NovaModuleTools; dotted labels are outside this provider's install
capabilities. Historical qualification does not establish untested platform
support.

### Deprecated

### Removed

### Fixed

- Declare Microsoft.PowerShell.PSResourceGet 1.2.0 as PMU's exact runtime
dependency so generated module manifests enforce the qualified provider
baseline before PMU commands run.
- Linux worker ownership checks no longer reject a live approving process
because of inconsistent cross-process timestamps. Exact PID-reuse protection
remains enabled. Legacy Linux repository receipts require explicit recovery
rather than automatic cleanup.
- CI dependency provisioning initializes a missing default PSGallery
registration before downloading and removes only a registration it explicitly
created afterward, preserving existing sources and trust. Package downloads have
five bounded attempts against the same source, destination and pinned
versions; exhausted downloads and isolated-import failures still fail CI
and clean up the owned dependency directory.
- Forward the complete planned target version unchanged at the provider
boundary instead of wrapping it in NuGet range brackets.
Bracketed prerelease targets could be discovered but failed during staging
from PSGallery. Exact target verification, consent and dependency checks
remain enabled.
- Fixed installed-copy discovery failing before repository lookup when a legacy
flat installation leaves resource directories beside numeric version folders.
Ordinary resource folders are excluded, while numeric folders with missing
Expand Down
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Thank you for contributing. Keep changes small, reviewable, and easy to validate.

The unreleased native-update transition is not covered by historical G-4A3
acceptance. Read the [current qualification and native provider scope](docs/architecture/TESTING.md#native-update-transition)
before changing its provider, lifecycle tests, or release requirements.

G-4A3's [accepted closure](docs/architecture/G4A3_CLOSURE.md) is separate from future platform
qualification. Review the proposed [cross-platform strategy](docs/architecture/CROSS_PLATFORM_TESTING.md)
before implementing testing infrastructure; its matrices and roadmap are not
Expand Down Expand Up @@ -68,6 +72,14 @@ PSResourceGet repository state is preserved by the provider fixtures, not by
assuming that `HOME` or `PSModulePath` redirects registrations. Unexpected
skips and incomplete XML/log/runtime evidence fail the lane.

Dependency provisioning checks that PSGallery points to its official endpoint.
If explicit native default registration is needed, it is removed afterward only
if its source and trust remain unchanged; existing registrations are not changed.
Each package download has at most three attempts with the same
source, version selection and owned destination. Exhausted retries or a failed
isolated import probe still fail provisioning and remove its owned dependency
directory.

The separate `Run Tests` workflow also saves dependencies to an owned temporary
root. Each PowerShell consumer step uses the install step's `moduleRoot` output
to set an isolated `PSModulePath`; installed modules and imports do not carry
Expand Down
37 changes: 29 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,22 @@ on PMU or contain any PMU-specific code.

PSModuleUpdate separates read-only discovery from an explicitly approved
installation. It targets PowerShell 7.4+ and uses
`Microsoft.PowerShell.PSResourceGet` 1.2.0 as its qualified provider baseline.
It never persistently configures consumer repositories, stores credentials,
elevates privileges, or reloads the updated module.
`Microsoft.PowerShell.PSResourceGet` 1.2.0 for discovery and declares that exact
version as a module dependency. The unreleased update implementation uses
PowerShellGet 2.2.5 and PackageManagement 1.4.8.1 for staging and native
`Update-Module`, without Force or an `Install-Module` fallback. It never
persistently configures consumer repositories, stores credentials, elevates
privileges, or reloads the updated module.

**Native prerelease scope:** use provider-compatible labels such as `preview1`
or `preview03`, matching the Gerber/NovaModuleTools release workflow.
PowerShellGet 2.2.5 rejects dotted labels such as `preview.2`; those labels remain
valid for PMU's semantic comparison but are outside this native update scope.
No Force, beta provider or alternate installer is introduced to bypass that
limitation. See
[current qualification](docs/architecture/TESTING.md#native-update-transition)
for successful Gallery evidence and the remaining gates. Historical G-4A3
acceptance does not qualify this replacement implementation.

Pass an installed module object, not just its name. The target need not be the
caller or already loaded:
Expand Down Expand Up @@ -69,6 +82,8 @@ module's manifest remain subject to manifest and layout validation.
versions use semantic prerelease ordering: `preview.10` follows `preview.2`,
and a stable release follows prereleases with the same numeric base.
Optional policy fields are `ExpectedRepositoryUri` and `VersionRange`.
Planning support does not imply that the current native provider can install
every selected version; the native label restriction above still applies.

### Private repositories

Expand Down Expand Up @@ -107,21 +122,27 @@ a new plan after restarting the shell or changing installation/repository state.
Repository selection is always caller-owned and source-bound. PMU never
searches another feed just because it contains the same package name. PSGallery,
public NuGet v3 repositories such as NuGet.org, and authenticated private
NuGet v3 repositories use the same PSResourceGet integration. General .NET
NuGet v3 repositories use the same PSResourceGet discovery integration.
Native installation has separate source/protocol qualification requirements;
successful discovery alone does not establish update support. General .NET
NuGet packages are not installable PowerShell modules merely because they can
be discovered or downloaded. Nexus is expected to work with compatible NuGet v3
capabilities, but no live Nexus qualification is implied.

## Replacement, recovery, and restart

Same-base transitions such as `2.0.0-preview.1` to `2.0.0-preview.2` and then
`2.0.0` require staged replacement because those versions share a numeric
Same-base transitions such as `2.0.0-preview1` to `2.0.0-preview2` and then
`2.0.0` share a numeric
installation directory. PMU validates the staged identity and GUID, preserves a
verified backup outside the Modules tree, records recovery state, and verifies
the installed file tree. Provider success alone is not proof of installation.
Equivalent dotted-label installation is not part of the Gerber/Nova native
update contract.

A cooperating PMU process holds an exclusive filesystem lock throughout the
operation. Interrupted replacements are checked on the next approved
A cooperating PMU process holds an exclusive filesystem lock during mutation.
The native worker acquires the same original installation lock through a
validated handoff; recovery cannot proceed while that worker still owns it.
Interrupted replacements are checked on the next approved
installation attempt. Recoverable failures trigger best-effort rollback;
unsafe or unsuccessful recovery stops further installation and preserves
available evidence. This is not an atomic installation or a power-loss
Expand Down
17 changes: 17 additions & 0 deletions RELEASE_NOTE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,29 @@ This file summarizes public cmdlet, CLI, configuration, and migration changes fo

### Changed

- Existing-module updates now use native `Update-Module` without Force or an
install fallback. Exact target/source, consent and restart requirements stay
in effect. Prerelease updates use native-compatible labels such as `preview03`;
dotted labels such as `preview.3` are not supported by the native installer.
Temporary repository setup also handles automatically registered defaults
without changing your repository settings.

### Deprecated

### Removed

### Fixed

- PMU now declares its qualified PSResourceGet 1.2.0 runtime dependency.
- Linux updates no longer falsely reject a live approving process. Legacy
Linux repository receipts require
[authorized manual review](docs/architecture/RECOVERY.md#interrupted-repository-transport)
rather than automatic cleanup.
- Contributor CI setup handles a missing Gallery registration and temporary
package download failures without changing the selected dependency versions.
- Updates now request the exact selected version in the form accepted by the
download provider, including prereleases such as `0.1.1-preview01`.
Dependencies must still resolve successfully before installation can finish.
- Update checks no longer fail merely because `en-US` or `resources` folders
coexist with a versioned module installation. Existing files are left intact.

Expand Down
36 changes: 34 additions & 2 deletions docs/architecture/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,50 @@

PSModuleUpdate (PMU) is an independent PowerShell 7.4+ module that plans and
applies an update for one explicitly identified PowerShell module. It is a thin
orchestration layer over `Microsoft.PowerShell.PSResourceGet`, not a package
orchestration layer over standard PowerShell module providers, not a package
manager, repository registry, credential store, background updater, or general
installed-module inventory.

This document is the implementation architecture for G-4A3. It transfers the
The original architecture below records G-4A3. It transfers the
approved G-4A1 contract and the superseding G-4A2-FIX2 decisions into the PMU
repository. Source, tests, and command help now exist; this architecture is
not a substitute for executed qualification. G-4A3 is now functionally complete
and owner-accepted within its qualified scope; see
[closure evidence and accepted findings](G4A3_CLOSURE.md). Remaining environment
qualification is a separate phase, not a universal production-readiness claim.

### Unreleased native-update transition

Discovery continues through PSResourceGet 1.2.0. Mutation now uses the runtime's
PowerShellGet 2.2.5 and PackageManagement 1.4.8.1 through a disposable native
worker. `Save-Module` stages the approved target and `Update-Module` updates the
existing selected installation. Production does not call `Install-Module`,
use Force, or hot reload the target.

The worker uses owned provider configuration/cache/temp roots without changing
the selected CurrentUser or AllUsers destination. A handoff transfers the
original installation lock, not a second worker-only lock. After acquisition,
the worker revalidates the approving process identity, journal transaction,
state/hash and selected installation before invoking native mutation.
The parent must reacquire ownership before interpreting results or recovering.
Requests and credentials travel over redirected stdin, not process arguments.

Exact full-version pinning remains required. The only qualified ceiling-only
case is same-numeric-base promotion to the approved stable release; it omits
`RequiredVersion` and `AllowPrerelease` and uses that stable `MaximumVersion`.
This exception must not be generalized to arbitrary prerelease or numeric
targets.

The native update scope uses the alphanumeric prerelease labels generated by
Gerber/NovaModuleTools. PowerShellGet 2.2.5 cannot install dotted labels such
as `preview.2`; the owner confirmed that those labels are not required for
this workflow. Semantic comparison support does not widen that provider
capability. The old PSResourceGet
mutation design below is historical, not a fallback or proof of current
compatibility. [D-002 and D-008](DECISIONS.md) and
[current qualification](TESTING.md#native-update-transition) distinguish the
chosen mechanism from remaining platform qualification.

The historical evidence remains owned by the read-only sibling Gerber
repository:

Expand Down
44 changes: 30 additions & 14 deletions docs/architecture/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,20 @@ An installed target need not be the caller, be loaded, declare PMU in
`RequiredModules`, or contain PMU-specific code. Consumer-specific discovery,
configuration, scheduling, and UI remain consumer responsibilities.

## D-002 — Standard provider foundation (DECIDED)
## D-002 — Standard provider foundation (native transition; QUALIFICATION GAP)

PMU uses `Microsoft.PowerShell.PSResourceGet` rather than implementing feed
transport, package resolution, or Azure authentication. Version 1.2.0 is the
qualified behavioral baseline. Compatible later stable 1.x versions require
explicit matrix evidence; PMU never silently switches provider versions or
falls back to PowerShellGet.
The original G-4A3 implementation used PSResourceGet 1.2.0 for discovery and
mutation. The approved unreleased direction retains that discovery provider
but uses native PowerShellGet `Update-Module`, with NovaModuleTools' established
Force-free behavior as the reference. PowerShellGet 2.2.5 and PackageManagement
1.4.8.1 are the current native implementation baseline.

PMU must not add an `Install-Module` production fallback, Force, custom NuGet
transport, or a silently substituted provider. Exact target/source, scope,
consent, verification and recovery remain requirements. The transition is not
qualified merely because the original PSResourceGet implementation was accepted.
PowerShellGet's dotted-prerelease limitation is explicit and outside the
Gerber/Nova update scope; see D-008.

## D-003 — Two-command public surface (DECIDED)

Expand Down Expand Up @@ -105,22 +112,31 @@ one exact target; later publication does not change it.
Four-component PowerShell versions are preserved and compared without blindly
casting them to `SemanticVersion`. Unsupported forms fail explicitly.

## D-008 — Same-base progression through Strategy C (DECIDED; supersedes G-4A2)
## D-008 — Same-base progression (native-compatible scope DECIDED)

Normal release progression must support:

```text
2.0.0-preview.1 -> 2.0.0-preview.2 -> 2.0.0-preview.3 -> 2.0.0
2.0.0-preview1 -> 2.0.0-preview2 -> 2.0.0-preview3 -> 2.0.0
```

Same-numeric-base collisions are not rejected as a class. PMU stages with
`Save-PSResource`, validates identity, creates a recoverable backup and journal,
uses exact `Install-PSResource -Reinstall` only for the collision, verifies the
manifest and tree, and rolls back on recoverable failure.
Same-numeric-base collisions must not be rejected as a class. The original
Strategy C used `Save-PSResource` and selective `Install-PSResource -Reinstall`.
The native transition instead stages with `Save-Module`, validates identity,
creates a recoverable backup/journal and uses `Update-Module` without Force.
Manifest/tree verification and recoverable-failure rollback remain required.

PowerShellGet 2.2.5 rejects dotted labels before staging or updating. On
2026-10-26 the owner confirmed that NovaModuleTools does not generate those
labels and that they are not required for this update workflow. The offline
fixture therefore uses native-compatible labels while retaining the same
five transitions, exact target/source, license consent, WhatIf, inactive-target
and registration-preservation assertions. General semantic-version comparison
tests retain dotted labels; installation does not promise to support them.

The earlier G-4A1/G-4A2 fail-closed collision policy and blanket ban on
`-Reinstall` are **SUPERSEDED**. Raw `-Reinstall` is not exposed publicly or
used indiscriminately.
`-Reinstall` were superseded for original Strategy C. That historical exception
does not authorize Force or an install fallback in the native transition.

## D-009 — GUID continuity (DECIDED)

Expand Down
32 changes: 32 additions & 0 deletions docs/architecture/RECOVERY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,28 @@ never part of this record.

## Interrupted operations

### Native worker ownership

The unreleased native updater transfers the same original installation lock
between parent and worker. Once the worker acquires it, it rechecks the approving
process's PID/start identity, journal transaction/state/hash and selected
installation. A dead approver or a recovery/replacement that wins the handoff
gap invalidates the request before native mutation.

An active writer retains that lock even if its approver exits or its input pipe
closes. Recovery must acquire the original lock; a timeout is not permission to
kill a potentially writing worker, release its exclusion or start rollback.
The parent reacquires ownership before interpreting results, rollback or
cleanup. Transaction-less historical journals remain subject to normal
recovery validation, but do not authorize a new native handoff.

Permanent cross-process ownership tests live in
`tests/NativeWorkerLifetime.Tests.ps1`. They run real worker/approver processes
and the real lock/journal/protocol code with a simulated provider boundary.
They do not replace real provider installation or platform qualification.

### Next approved attempt

On an approved installation attempt, PMU acquires the same installation lock
and inspects unfinished recovery state before attempting another update.
It validates journal contents and referenced paths rather than following
Expand Down Expand Up @@ -61,6 +83,16 @@ named receipt and registration, stop concurrent registry writers, and obtain
authorized manual review of that individual registration. Never reset the
provider XML store or bulk-remove PMU-prefixed names.

Linux process identities now use the kernel's stable start counter instead of
cross-process .NET timestamps. A receipt written by the previous Linux
implementation cannot prove its owner's identity, even after that process
exits. PMU therefore stops with `PMU.RepositoryRecoveryRequired` rather than
automatically deleting the registration. Preserve that individual receipt and
registration for the authorized manual review described above. There is no
public receipt-repair command; a restart, fresh plan, or approved installation
attempt does not itself migrate these receipts. This is separate from
installation-journal recovery.

## When recovery cannot finish

Errors such as `PMU.RecoveryRequired`, `PMU.RecoveryFailed`, or
Expand Down
Loading
Loading