Skip to content

Add ClickOnce publish-layout resolution - #1051

Merged
dtivel merged 4 commits into
mainfrom
dtivel/clickonce-file-graph
Sep 21, 2026
Merged

dtivel merged 4 commits into
mainfrom
dtivel/clickonce-file-graph

Conversation

@dtivel

@dtivel dtivel commented Aug 9, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #1049.

Summary

  • Add dormant ClickOnce publish-layout resolution through:
    • ClickOnceApplicationPublishLayoutResolver
    • ClickOnceDeploymentPublishLayoutResolver
    • ClickOncePayloadResolver
  • Represent the resolved layout with strongly typed models:
    • ResolvedClickOncePublishLayout
    • ResolvedClickOnceDeployment
    • ResolvedClickOnceApplication
    • ResolvedClickOncePayload
    • ResolvedClickOnceAdjacentExecutable
  • Report resolution failures through ClickOncePublishLayoutResolutionException while preserving ordered manifest diagnostics.
  • Read deployment and application manifests through the preservation-safe typed manifest-reader API for every resolution read.
  • Require a deployment manifest model to expose exactly one assembly reference, no file references, and an entry point that is the same non-prerequisite, non-optional reference. Inspect the preserved deployment XML to reject resource-fallback attributes that ManifestUtilities neither models nor preserves. Reject unsupported shapes before file resolution so ManifestUtilities or mage.exe cannot reorder or rewrite them before signing.
  • Reject rooted deployment-entry-point and application-payload target paths before resolution because they cannot be represented beneath a staging root. Preserve relative target paths, including parent traversal, for containment validation against the eventual staging root.
  • Resolve the deployment manifest's referenced application manifest, then resolve physical application payloads using the specified mapping and directory-fallback rules.
  • Preserve each payload's source and manifest reference while snapshotting its target path. ResolvedClickOncePayload validates that the source filename matches the target filename, with or without exactly one additional .deploy suffix, and derives IsFileExtensionMapped from that relationship.
  • Model adjacent executables separately with a constrained setup/launcher kind. ResolvedClickOnceAdjacentExecutable derives its target path from its source filename and validates the filename against its kind.
  • Discover only applicable adjacent setup.exe and unreferenced root-level Launcher.exe files; referenced launchers remain application payloads.
  • Use neutral "deployment manifest" and "application manifest" terminology in user-facing resolution errors.
  • Add coverage for deployment-reference validation, required and optional physical references, multiple versions, malformed and wrong-type manifests, mapping, fallback and diagnostic ordering, adjacent executables, VSTO layouts, target-path validation and preservation, and model invariants.

Intentional scope boundary

This PR resolves and models the publish layout only. It does not register the resolvers with dependency injection and does not change production signing behavior or the CLI surface. Diagnostic text may reference the planned --clickonce-signing-version 2 --no-update-clickonce-manifest option, but the resolvers remain dormant until signer integration adds that option.

Staging, relative-path containment validation, copying, temporary .deploy suffix removal and restoration, manifest metadata updates, signing-operation coordination, and signing remain deferred to the signer-integration work. Resolution rejects rooted target paths but does not rewrite or remap accepted relative paths. Staging must ensure those relative paths remain within the staging directory and fail when a referenced layout cannot be represented safely.

The deployment resolver establishes that Application.Source is the file initially identified by Deployment.ApplicationManifestReference.ResolvedPath. This is a resolution-time postcondition rather than a durable model invariant because staging must redirect that mutable ResolvedPath. The staging implementation must stage Application.Source, redirect the deployment entry point to that exact staged copy, verify that it is contained within the staging directory, and only then update deployment-manifest metadata. At the start of staging, it must capture the deployment entry point's target path. Immediately before deployment-manifest metadata update and serialization, it must fail unless the live entry point is still the resolved ApplicationManifestReference, remains the sole assembly reference with no file references, and its target path still ordinally equals the captured value. It must not reconcile or serialize mutated deployment structure.

For payloads, ResolvedClickOncePayload.TargetPath is an immutable resolution-time snapshot of Reference.TargetPath, not a live projection. Staging must use that snapshot to determine the intended layout, preserve the manifest-relative meaning rather than remapping it, and bind each mutable reference's ResolvedPath to the corresponding contained staged file before updating metadata. Before computing staging destinations or collision claims, and again immediately before updating application-manifest metadata and serializing the manifest, staging must verify with ordinal comparison that each mutable Reference.TargetPath still equals its resolved snapshot. A mismatch must fail staging rather than reconcile or emit an inconsistent manifest.

The deployment-directory payload fallback is a source-discovery mechanism. Signer integration must retain an explicit mapping from every staged file to the original ResolvedClickOncePayload.Source it was staged from and copy signed bytes back to that exact source path. It must not derive copy-back destinations from staging-relative paths or reuse a directory-mirroring copy-back seam that would create a new application-directory file for a deployment-directory fallback source. Integration coverage must verify that a fallback payload is returned to its original source and that no unintended target-relative file is created.

Discovery can also produce two resolved references to one physical file: a root-level setup.exe referenced by the application manifest is classified as both a ResolvedClickOncePayload and a ResolvedClickOnceAdjacentExecutable, with both Source values identifying the same file and potentially the same target path. Signer integration must apply the same source-keyed coordinated signing operation to both references so the file is staged, signed, and copied back exactly once rather than processed as two independent operations.

@dtivel
dtivel requested a review from a team as a code owner August 9, 2026 22:44
@dtivel
dtivel requested a review from kartheekp-ms August 9, 2026 23:23
@dtivel
dtivel force-pushed the dtivel/clickonce-file-graph branch from 30bdd3a to 0eebbb8 Compare August 11, 2026 04:31
@dtivel
dtivel requested a balanced review from Copilot August 11, 2026 04:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds dormant ClickOnce file-graph resolution for the version 2 signing pipeline.

Changes:

  • Adds graph models and application/deployment manifest resolvers.
  • Implements payload mapping, fallback resolution, diagnostics, and adjacent executable discovery.
  • Adds comprehensive tests and localized resources.
Show a summary per file
File Description
test/Sign.Core.Test/Tools/ClickOnce/ClickOnceFileGraphResolverTests.cs Tests resolution behavior and edge cases.
test/Sign.Core.Test/Tools/ClickOnce/ClickOnceFileGraphModelTests.cs Tests graph models and validation.
src/Sign.Core/Tools/ClickOnce/ClickOncePayloadFileResolver.cs Resolves referenced payload files.
src/Sign.Core/Tools/ClickOnce/ClickOnceManifestDiagnostic.cs Models manifest diagnostics.
src/Sign.Core/Tools/ClickOnce/ClickOnceFileGraphResolutionException.cs Defines resolution failures.
src/Sign.Core/Tools/ClickOnce/ClickOnceFileGraphEntryKind.cs Defines graph entry categories.
src/Sign.Core/Tools/ClickOnce/ClickOnceFileGraphEntry.cs Models individual graph entries.
src/Sign.Core/Tools/ClickOnce/ClickOnceFileGraph.cs Models resolved ClickOnce graphs.
src/Sign.Core/Tools/ClickOnce/ClickOnceDeployManifestFileGraphResolver.cs Resolves deployment-manifest graphs.
src/Sign.Core/Tools/ClickOnce/ClickOnceApplicationManifestFileGraphResolver.cs Resolves application-manifest graphs.
src/Sign.Core/Resources.resx Adds resolution messages.
src/Sign.Core/Resources.Designer.cs Exposes generated resource properties.
src/Sign.Core/xlf/Resources.zh-Hant.xlf Adds Traditional Chinese localization entries.
src/Sign.Core/xlf/Resources.zh-Hans.xlf Adds Simplified Chinese localization entries.
src/Sign.Core/xlf/Resources.tr.xlf Adds Turkish localization entries.
src/Sign.Core/xlf/Resources.ru.xlf Adds Russian localization entries.
src/Sign.Core/xlf/Resources.pt-BR.xlf Adds Brazilian Portuguese localization entries.
src/Sign.Core/xlf/Resources.pl.xlf Adds Polish localization entries.
src/Sign.Core/xlf/Resources.ko.xlf Adds Korean localization entries.
src/Sign.Core/xlf/Resources.ja.xlf Adds Japanese localization entries.
src/Sign.Core/xlf/Resources.it.xlf Adds Italian localization entries.
src/Sign.Core/xlf/Resources.fr.xlf Adds French localization entries.
src/Sign.Core/xlf/Resources.es.xlf Adds Spanish localization entries.
src/Sign.Core/xlf/Resources.de.xlf Adds German localization entries.
src/Sign.Core/xlf/Resources.cs.xlf Adds Czech localization entries.

Review details

Tip

Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Files not reviewed (1)
  • src/Sign.Core/Resources.Designer.cs: Generated file
  • Files reviewed: 24/25 changed files
  • Comments generated: 0
  • Review effort level: Balanced

@dtivel
dtivel force-pushed the dtivel/clickonce-file-graph branch from 0eebbb8 to 24496c7 Compare August 12, 2026 19:14
@dtivel
dtivel requested a balanced review from Copilot August 12, 2026 19:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review details

Files not reviewed (1)
  • src/Sign.Core/Resources.Designer.cs: Generated file
  • Files reviewed: 24/25 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@dtivel
dtivel force-pushed the dtivel/clickonce-file-graph branch from 24496c7 to d99a714 Compare August 12, 2026 22:43
Base automatically changed from dtivel/clickonce-manifest-foundation to main August 14, 2026 14:06
@dtivel
dtivel force-pushed the dtivel/clickonce-file-graph branch 3 times, most recently from 8e4dc41 to 57f5560 Compare August 15, 2026 19:20
kartheekp-ms
kartheekp-ms previously approved these changes Aug 18, 2026
Comment thread src/Sign.Core/Tools/ClickOnce/ClickOnceFileGraph.cs Outdated
@dtivel dtivel changed the title Add ClickOnce file graph resolution Add ClickOnce publish-layout resolution Sep 6, 2026
@dtivel
dtivel force-pushed the dtivel/clickonce-file-graph branch from 57f5560 to 588e39d Compare September 6, 2026 04:11
@dtivel
dtivel requested review from kartheekp-ms and a balanced review from Copilot September 7, 2026 17:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Its broad manifest-resolution and path-safety behavior warrants final human review despite no unresolved comments.

Review tier: Balanced
Findings: None

Files not reviewed (1)
  • src/Sign.Core/Resources.Designer.cs: Generated file

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The broad manifest parsing and filesystem path-resolution surface warrants final human validation despite extensive tests and no identified blocking defect.

Review tier: Balanced
Findings: None

Files not reviewed (1)
  • src/Sign.Core/Resources.Designer.cs: Generated file

Comment thread src/Sign.Core/Tools/ClickOnce/IApplicationManifest.cs
Comment thread src/Sign.Core/Tools/ClickOnce/ClickOnceApplicationPublishLayoutResolver.cs Outdated
Comment thread src/Sign.Core/Tools/ClickOnce/ClickOncePayloadResolver.cs Outdated
Comment thread src/Sign.Core/Tools/ClickOnce/ResolvedClickOncePayload.cs
@dtivel
dtivel requested a review from kartheekp-ms September 19, 2026 22:51
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: be91288d-5106-4c59-a1fa-c5353dccb828
Add typed resolved models and enforce deployment, payload, mapping, diagnostic, and target-path invariants. Preserve manifest input, validate unsupported deployment shapes, and expand resolver regression coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: de6a34fe-6989-49f6-a128-ee13323c7c9e
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: de6a34fe-6989-49f6-a128-ee13323c7c9e
Remove preliminary application manifest resolution attempts whose diagnostics can be superseded by supported publish-layout fallback. Reserve ManifestUtilities resolution for authoritative validation of the staged layout.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: de6a34fe-6989-49f6-a128-ee13323c7c9e
@dtivel
dtivel force-pushed the dtivel/clickonce-file-graph branch from af8ec58 to ccd1584 Compare September 20, 2026 20:24
@dtivel
dtivel merged commit e27b92d into main Sep 21, 2026
3 checks passed
@dtivel
dtivel deleted the dtivel/clickonce-file-graph branch September 21, 2026 17:54
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.

3 participants