Skip to content

Commit d24fb18

Browse files
ggreifclaudemraszyklwshang
authored
spec: service <: principal + release candid 0.10.32 (#748)
## Summary A service reference is represented by the principal of its canister, so it is natural to use one wherever a `principal` is expected. This adds the subtyping rule `service <actortype> <: principal` and its coercion. Long discussed in caffeinelabs/motoko#2264 (rossberg, chenyan-dfinity, and nomeata converged on it being reasonable at the Candid level). This PR also cuts the corresponding **`candid` 0.10.32** release (see *Release* below). ## Spec (`spec/Candid.md`) Modelled exactly on the existing `nat <: int` rule — an unconditional, premise-free widening in the *Primitive Types* subsections (`principal` is itself primitive), with a value-preserving coercion: - subtyping: `service <actortype> <: principal` - coercion: `service <text> : service <actortype> ~> principal <text> : principal` It is a **conservative extension**: it only enlarges `<:` (no existing judgment is lost), and it is decode-sound because a service reference is *already* wire-encoded exactly like a principal — `M`/`R` are byte-identical (`i8(0)` opaque; `i8(1) M(v* : vec nat8)` transparent) — so the coercion is the identity on the reference. **No change to the binary format** (`T`/`M`/`R`). The reverse (`principal <: service`) is intentionally not added. ## Reference implementation (`rust/candid`) - `types/subtype.rs`: `(Service(_), Principal)` accepted in both matchers, unconditional (like `(Nat, Int)`). - `de.rs`: `deserialize_principal` now matches the wire type directly against `principal` or `service <…>` (instead of asserting `wire == principal`), so a service reference decodes at `principal` (identical `PrincipalBytes`). It deliberately does *not* use the general `check_subtype()`, which would also admit `empty` (bottom) `<: principal` and let a payload be decoded as a principal from arbitrary trailing bytes. ## Tests - `test/subtypes.test.did`: `service {}` and `service {m:()->()}` `<: principal`; `principal` `</:` `service {}`. - `test/reference.test.did`: a service reference *value* decodes at `principal` (empty & non-empty); the reverse was already rejected. Both were confirmed to fail without the corresponding checker/decoder change. ## Metatheory (`coq/`) - `MiniCandid.v`: extended with `ServiceT`/`PrincipalT` + `ServiceV`/`PrincipalV`, the rule `ServiceT <: PrincipalT` (modelled on `NatT <: IntT`), and the coercion. `soundness` and `transitive_coherence` re-verify with the new constructors — the rule is machine-checked sound. - Build: re-pinned `coq/nix` to a modern nixpkgs that provides `coq_8_18` (the `nixos-20.09` niv pin no longer evaluates; the default `coq` is now Rocq 9.x, which renamed `Coq.*`→`Stdlib.*` and moved `FunInd`). Removed the now-unused niv machinery (`sources.{nix,json}`) and pinned the `fetchTarball` NAR hash. `nix-build -A theories coq` is green in CI. ## Release (`candid` / `candid_derive` 0.10.32) This PR also cuts the `candid` release that carries the change: - Bump `candid` and `candid_derive` `0.10.31 → 0.10.32` (kept in lockstep per the "sync with the version" comments) and the internal `candid_derive` dependency pin; refresh the workspace and `rust/bench` lockfiles. - `CHANGELOG.md`: new dated section documenting `service <: principal` as a non-breaking change. ## Follow-ups (not in this PR) - Porting the Coq development to Rocq 9.1.1 (separate issue). --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: mraszyk <31483726+mraszyk@users.noreply.github.com> Co-authored-by: Linwei Shang <linwei.shang@dfinity.org>
1 parent 294ec65 commit d24fb18

14 files changed

Lines changed: 185 additions & 236 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## 2026-07-06
4+
5+
### Candid 0.10.32
6+
7+
* Non-breaking changes:
8+
+ A service reference now decodes where a `principal` is expected: `service <actortype>` is a subtype of `principal` (spec addition: `service <: principal`, modelled analogously to `nat <: int`). The subtype checker and the deserializer accept a service reference at type `principal`; the two share an identical wire encoding, so the coercion is the identity on the reference. The reverse (a `principal` at a `service` type) remains rejected.
9+
310
## 2026-07-03
411

512
### ic_principal 0.1.5

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coq/MiniCandid.v

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ CoInductive T :=
3535
| NullT : T
3636
| OptT : T -> T
3737
| FuncT : T -> T -> T
38+
| ServiceT : T
39+
| PrincipalT : T
3840
| VoidT : T
3941
| ReservedT : T
4042
.
@@ -54,6 +56,8 @@ Inductive V :=
5456
| NullV : V
5557
| SomeV : V -> V
5658
| FuncV : RefV -> V
59+
| ServiceV : RefV -> V
60+
| PrincipalV : RefV -> V
5761
| ReservedV : V
5862
.
5963
(**
@@ -91,6 +95,12 @@ Inductive HasType : V -> T -> Prop :=
9195
| FuncHT:
9296
case funcHT,
9397
forall rv t1 t2, FuncV rv :: FuncT t1 t2
98+
| ServiceHT:
99+
case serviceHT,
100+
forall rv, ServiceV rv :: ServiceT
101+
| PrincipalHT:
102+
case principalHT,
103+
forall rv, PrincipalV rv :: PrincipalT
94104
| ReservedHT:
95105
case reservedHT,
96106
ReservedV :: ReservedT
@@ -105,6 +115,9 @@ CoInductive Subtype : T -> T -> Prop :=
105115
| NatIntST :
106116
case natIntST,
107117
NatT <: IntT
118+
| ServicePrincipalST :
119+
case servicePrincipalST,
120+
ServiceT <: PrincipalT
108121
| OptST :
109122
case optST,
110123
forall t1 t2,
@@ -179,6 +192,9 @@ Function coerce (t1 : T) (t2 : T) (v1 : V) : V :=
179192
| IntV n, IntT, IntT => IntV n
180193
| NatV n, NatT, IntT => IntV (Z.of_nat n)
181194
| FuncV r, FuncT ta1 tr1, FuncT ta2 tr2 => FuncV r
195+
| ServiceV r, ServiceT, ServiceT => ServiceV r
196+
| PrincipalV r, PrincipalT, PrincipalT => PrincipalV r
197+
| ServiceV r, ServiceT, PrincipalT => PrincipalV r
182198

183199
| SomeV v, OptT t1, OptT t2 =>
184200
if t1 <:? t2
@@ -195,6 +211,9 @@ Function coerce (t1 : T) (t2 : T) (v1 : V) : V :=
195211
| FuncV r, FuncT ta1 tr1, OptT (FuncT ta2 tr2) =>
196212
if ta2 <:? ta1
197213
then if tr1 <:? tr2 then SomeV (FuncV r) else NullV else NullV
214+
| ServiceV r, ServiceT, OptT ServiceT => SomeV (ServiceV r)
215+
| PrincipalV r, PrincipalT, OptT PrincipalT => SomeV (PrincipalV r)
216+
| ServiceV r, ServiceT, OptT PrincipalT => SomeV (PrincipalV r)
198217

199218
| v, t, ReservedT => ReservedV
200219

@@ -237,6 +256,16 @@ Proof.
237256
destruct (t3 <:? t2_2); try reflexivity.
238257
contradict HNotST; named_constructor; assumption.
239258
}
259+
[serviceHT]: {
260+
destruct (ServiceT <:? t2) as [HST | HNotST].
261+
- inversion HST; subst; clear HST; simpl; reflexivity.
262+
- destruct t2; try reflexivity; contradict HNotST; named_constructor.
263+
}
264+
[principalHT]: {
265+
destruct (PrincipalT <:? t2) as [HST | HNotST].
266+
- inversion HST; subst; clear HST; simpl; reflexivity.
267+
- destruct t2; try reflexivity; contradict HNotST; named_constructor.
268+
}
240269
Qed.
241270

242271
Lemma coerce_reservedT:
@@ -254,6 +283,9 @@ Lemma coerce_nice_ind:
254283
(case natC, forall n, P NatT NatT (NatV n) (NatV n)) ->
255284
(case intC, forall n, P IntT IntT (IntV n) (IntV n)) ->
256285
(case natIntC, forall n, P NatT IntT (NatV n) (IntV (Z.of_nat n))) ->
286+
(case serviceC, forall r, P ServiceT ServiceT (ServiceV r) (ServiceV r)) ->
287+
(case principalC, forall r, P PrincipalT PrincipalT (PrincipalV r) (PrincipalV r)) ->
288+
(case servicePrincipalC, forall r, P ServiceT PrincipalT (ServiceV r) (PrincipalV r)) ->
257289
(case nullC, P NullT NullT NullV NullV) ->
258290
(case nullOptC, forall t, P NullT (OptT t) NullV NullV) ->
259291
(case optNullC, forall t1 t2, P (OptT t1) (OptT t2) NullV NullV) ->
@@ -293,7 +325,7 @@ Lemma coerce_nice_ind:
293325
(forall t1 t2 v1, t1 <: t2 -> v1 :: t1 -> P t1 t2 v1 (coerce t1 t2 v1)).
294326
Proof.
295327
intros P.
296-
intros NatC IntC NatIntC NullC NullOptC OptNullC OptSomeC OpportunisticOptC ReservedOptC ConstituentOptC OpportunisticConstituentOptC FuncC ReservedC.
328+
intros NatC IntC NatIntC ServiceC PrincipalC ServicePrincipalC NullC NullOptC OptNullC OptSomeC OpportunisticOptC ReservedOptC ConstituentOptC OpportunisticConstituentOptC FuncC ReservedC.
297329
intros t1 t2 v1 HST HHT.
298330
revert t2 HST.
299331
induction HHT; name_cases.
@@ -319,6 +351,8 @@ Proof.
319351
- contradict n0. named_constructor.
320352
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
321353
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
354+
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
355+
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
322356
}
323357
[reservedST]: { apply ReservedC; clear_names. named_constructor. }
324358
}
@@ -338,6 +372,8 @@ Proof.
338372
- contradict n0. named_constructor.
339373
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
340374
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
375+
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
376+
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
341377
}
342378
[reservedST]: { apply ReservedC; clear_names. named_constructor. }
343379
}
@@ -370,6 +406,8 @@ Proof.
370406
++ apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
371407
** apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
372408
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
409+
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
410+
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
373411
}
374412
[funcST]: { apply FuncC; clear_names; assumption. }
375413
[reservedST]: { apply ReservedC; clear_names. named_constructor. }
@@ -405,13 +443,57 @@ Proof.
405443
}
406444
[reservedST]: { apply ReservedC; clear_names. named_constructor; assumption. }
407445
}
408-
[reservedHT]: {
446+
[reservedHT]: {
409447
intros.
410448
inversion HST; subst; clear HST; name_cases.
411449
[reflST]: { apply ReservedC; clear_names. named_constructor. }
412450
[optST]: { apply ReservedOptC; clear_names. }
413451
[reservedST]: { apply ReservedC; clear_names. named_constructor. }
414452
}
453+
[serviceHT]: {
454+
intros.
455+
inversion HST; subst; clear HST; name_cases.
456+
[reflST]: { apply ServiceC; clear_names. }
457+
[servicePrincipalST]: { apply ServicePrincipalC; clear_names. }
458+
[optST]: {
459+
destruct (is_opt_like_type t0) eqn:His_opt_like.
460+
* destruct t0; inversion His_opt_like; simpl; clear His_opt_like;
461+
apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
462+
* destruct (subtyping_decidable ServiceT t0) as [s | Hn].
463+
+ destruct t0; inversion s; subst; clear s; inversion His_opt_like; clear His_opt_like.
464+
- apply ConstituentOptC; clear_names; simpl; intuition; named_constructor.
465+
- apply ConstituentOptC; clear_names; simpl; intuition; named_constructor.
466+
+ destruct t0; inversion His_opt_like; clear His_opt_like.
467+
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
468+
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
469+
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
470+
- contradict Hn. named_constructor.
471+
- contradict Hn. named_constructor.
472+
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
473+
}
474+
[reservedST]: { apply ReservedC; clear_names. named_constructor. }
475+
}
476+
[principalHT]: {
477+
intros.
478+
inversion HST; subst; clear HST; name_cases.
479+
[reflST]: { apply PrincipalC; clear_names. }
480+
[optST]: {
481+
destruct (is_opt_like_type t0) eqn:His_opt_like.
482+
* destruct t0; inversion His_opt_like; simpl; clear His_opt_like;
483+
apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
484+
* destruct (subtyping_decidable PrincipalT t0) as [s | Hn].
485+
+ destruct t0; inversion s; subst; clear s; inversion His_opt_like; clear His_opt_like.
486+
- apply ConstituentOptC; clear_names; simpl; intuition; named_constructor.
487+
+ destruct t0; inversion His_opt_like; clear His_opt_like.
488+
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
489+
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
490+
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
491+
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
492+
- contradict Hn. named_constructor.
493+
- apply OpportunisticConstituentOptC; clear_names; simpl; intuition named_constructor.
494+
}
495+
[reservedST]: { apply ReservedC; clear_names. named_constructor. }
496+
}
415497
Qed.
416498

417499
(**
@@ -737,6 +819,10 @@ CoInductive UpToNull : V -> V -> Prop :=
737819
SomeV v1 ~~ SomeV v2
738820
| FuncUT:
739821
forall r, FuncV r ~~ FuncV r
822+
| ServiceUT:
823+
forall r, ServiceV r ~~ ServiceV r
824+
| PrincipalUT:
825+
forall r, PrincipalV r ~~ PrincipalV r
740826
| ReservedUT:
741827
ReservedV ~~ ReservedV
742828
where "v1 ~~ v2" := (UpToNull v1 v2).

coq/nix/default.nix

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
{}:
22
let
3-
sources = import ./sources.nix;
4-
53
subpath = import ./gitSource.nix;
64

5+
# Re-pinned to a modern nixpkgs that provides coq_8_18.
6+
#
7+
# The previous niv pin (nixos-20.09) no longer evaluates on current nix, and
8+
# the default `coq` is now Rocq 9.x, which renamed the stdlib namespace
9+
# (Coq.* -> Stdlib.*) and relocated FunInd — both of which these proofs use.
10+
# coq_8_18 is the newest coq that still accepts them unchanged.
11+
nixpkgs = fetchTarball {
12+
url = "https://github.com/NixOS/nixpkgs/archive/e8273b29fe1390ec8d4603f2477357555291432e.tar.gz";
13+
sha256 = "1k8idy9ka87c7gjb9aiqrcx4l9kwblv8fs4pwf0byjivsmbjfdhi";
14+
};
15+
716
overlays = [
817
(pkgs: super: { local = {
9-
# Fetch niv from nix/sources.json, newer than nixpkgs
10-
niv = (import sources.niv {}).niv;
11-
1218
# the main product: building the theories
1319
theories = pkgs.stdenv.mkDerivation {
1420
name = "candid-coq";
1521
src = subpath ./..;
1622
buildInputs = [
1723
pkgs.dune_2
18-
pkgs.coq
19-
pkgs.ocaml
24+
pkgs.coq_8_18
25+
pkgs.coq_8_18.ocaml
26+
pkgs.coq_8_18.ocamlPackages.findlib
2027
];
2128
buildPhase = ''
2229
dune build --display=short
@@ -31,24 +38,8 @@ let
3138
inputsFrom = [
3239
pkgs.local.theories
3340
];
34-
propagatedBuildInputs = [
35-
pkgs.niv
36-
];
37-
38-
# This helps with using GUI programs like coqide
39-
LOCALE_ARCHIVE = pkgs.stdenv.lib.optionalString pkgs.stdenv.isLinux "${pkgs.glibcLocales}/lib/locale/locale-archive";
40-
41-
# allow building this as a derivation, so that CI can biuld and cache
42-
# the dependencies of shell
43-
phases = ["installPhase" "fixupPhase"];
44-
installPhase = ''
45-
mkdir $out
46-
'';
47-
preferLocalBuild = true;
48-
allowSubstitutes = true;
4941
};
5042
};})
5143
];
5244

53-
in import sources.nixpkgs { inherit overlays; }
54-
45+
in import nixpkgs { inherit overlays; }

coq/nix/sources.json

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)