One observation from running Smart Contract Upgrade on a sandbox, with the exact errors, in case it saves someone a debugging session.
Setup: dpm 1.0.22, Daml SDK 3.4.11, single-participant dpm sandbox (its log banner says Starting Canton version 3.5.18), Daml Script 3.4.11 and the JSON API. Two versions of one package, 0.1.0 and 0.2.0, the second adding a trailing Optional field to one template; dpm upgrade-check --both passes for the pair.
The rule itself is documented: with no package_id_selection_preference, the participant resolves the package name to the highest vetted version. What the documentation does not show is how that looks from the client.
After 0.2.0 was vetted, a Daml Script compiled against 0.1.0 submitted an exercise with no preference. The ledger executed the 0.2.0 code and the resulting contract carried the 0.2.0 package id. The only failure was on the client, when the script runner could not translate the result: Failed to translate create argument: Lookup(NotFound(Package(00a85a8b...))). The ledger side effect happened under a version the client had never seen, and the client’s error says nothing about versions.
The mirror case: a script compiled against 0.2.0 and run before 0.2.0 was uploaded was not rejected. The participant resolved the name to the only vetted version, 0.1.0, executed that, and again only the runner failed, with Lookup(NotFound(Package(3b3ac851...))). So “package not vetted” never appears as an error in this configuration.
Pinning works as documented: packagePreference in Daml Script, packageIdSelectionPreference on the JSON API. All of this is on one participant; we have not looked at how it behaves across participants.
One question, mostly for the Digital Asset folks here: is there a recommended way for a client to state the version it was compiled against, so that a mismatch fails at submission rather than executing under another version and failing only on result translation?