Skip to content

Commit aa9409d

Browse files
committed
infra: apply final portal spec updates and remove portal submodule
Apply three upstream spec changes from dfinity/portal@fe646b2 (the final commit before deprecation): - canister_metrics endpoint: new management canister query method that returns cycle consumption broken down by memory, compute, ingress, instructions, http outcalls, etc. Applied to ic.did, changelog (v0.61.0), management-canister.md (method doc + reference entry), and abstract-behavior.md (method-list, abstract rule, query call conditions). - sender_info validation refactor (dc29431): move sender_info checks into verify_envelope predicate; remove five now-redundant per-call blocks from update and query call conditions in abstract-behavior.md. - Ingress expiry typo fix (8304ab0): add missing "or RS.sender = anonymous_id" to the subnet read-state ingress_expiry condition. Remove the dfinity/portal git submodule (.sources/portal). The spec files it synced — ic.did, certificates.cddl, requests.cddl, http-gateway.did, the ic-interface-spec split pages, and http-gateway-spec.md — are now maintained directly in this repository. Update AGENTS.md: remove portal from the submodule table and per-submodule bump checklist; replace the "Synced files from submodules" / "Portal bump checklist" sections with a "Directly maintained spec files" table; remove the "Portal tracking" section. Update README.md, .sources/VERSIONS, and .docs-plan/content-authoring.md to remove remaining operational portal references.
1 parent f71943e commit aa9409d

11 files changed

Lines changed: 160 additions & 102 deletions

File tree

.docs-plan/content-authoring.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
When drafting a new docs page:
66

77
1. Read the stub page — it contains content brief, source material, and cross-links
8-
2. Read source material from `.sources/`. Stub references use shorthand — resolve them per the mapping in "Source material repos" above (e.g., `Portal: building-apps/foo.mdx``.sources/portal/docs/building-apps/foo.mdx`).
9-
> **If source material is unavailable at the expected path:** (1) search `.sources/portal/` for the content under a different path, (2) if truly unavailable, write from the content brief + icskills + your training knowledge, and add `<!-- Source unavailable: [path] — written from content brief -->` so future contributors know to verify.
8+
2. Read source material from `.sources/`. Resolve paths using the "Source material repos" table in AGENTS.md.
9+
> **If source material is unavailable at the expected path:** write from the content brief + icskills + your training knowledge, and add `<!-- Source unavailable: [path] — written from content brief -->` so future contributors know to verify.
1010
3. Read any related icskills skill file from `.sources/icskills/` for accurate canister IDs and code patterns.
1111
4. Write the content:
1212
- Follow the content brief in the stub

.gitmodules

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
[submodule ".sources/portal"]
2-
path = .sources/portal
3-
url = git@github.com:dfinity/portal.git
4-
branch = master
51
[submodule ".sources/icp-cli"]
62
path = .sources/icp-cli
73
url = git@github.com:dfinity/icp-cli.git

.sources/VERSIONS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Pinned submodule versions (release-pinned repos only)
22
# -------------------------------------------------------
33
# Repos that track main/master are NOT listed here — they have no fixed release
4-
# version. Those repos are: portal, examples, icskills, dotskills,
4+
# version. Those repos are: examples, icskills, dotskills,
55
# icp-cli-recipes, icp-cli-templates, icp-js-sdk-docs.
66
#
77
# FORMAT: <submodule> <version label> <7-char hash>

.sources/portal

Lines changed: 0 additions & 1 deletion
This file was deleted.

AGENTS.md

Lines changed: 15 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,6 @@ For current release hashes, see `.sources/VERSIONS`.
199199

200200
| Submodule | Repo | Pinned to | What it provides |
201201
|-----------|------|-----------|-----------------|
202-
| `.sources/portal` | `dfinity/portal` | `master` | Old docs content referenced in stub `<!-- Source Material -->` comments |
203202
| `.sources/icp-cli` | `dfinity/icp-cli` | latest release | CLI reference, command syntax verification |
204203
| `.sources/icp-cli-recipes` | `dfinity/icp-cli-recipes` | `main` | Recipe examples for CLI guides |
205204
| `.sources/icp-cli-templates` | `dfinity/icp-cli-templates` | `main` | Project templates for getting-started |
@@ -236,7 +235,6 @@ git -C .sources/<repo> checkout <commit>
236235
### Rules for agents
237236

238237
- **Always read source material from `.sources/`** — never from local clones, `gh api`, or training data
239-
- **Stub shorthand mapping:** `Portal: building-apps/foo.mdx``.sources/portal/docs/building-apps/foo.mdx`
240238
- **Consult relevant repos when writing or reviewing:**
241239
- **Motoko code**`motoko-core` (API signatures) + `motoko` (compiler: system function names, keywords)
242240
- **Rust code**`cdk-rs` (`ic-cdk`, `ic-cdk-timers`, management canister types)
@@ -246,7 +244,6 @@ git -C .sources/<repo> checkout <commit>
246244
- **JavaScript / TypeScript**`icp-js-sdk-docs` (unzip archives before reading; never guess JS SDK API)
247245
- **Code examples**`examples` (link to for snippets >30 lines)
248246
- **Do not modify `.sources/`** — read-only. Edits go to the upstream repos.
249-
- **Portal `file=` references:** Resolve `file=../../../../submodules/samples/...` paths via `.sources/examples` instead.
250247

251248
### Bumping submodules
252249

@@ -279,7 +276,6 @@ EOF
279276

280277
| Submodule | Extra checks on bump |
281278
|---|---|
282-
| `portal` | Follow the full portal checklist in "Synced files from submodules" below |
283279
| `motoko` | **Automated**`.github/workflows/sync-motoko.yml` opens a PR with the submodule bump, synced docs, and VERSIONS update already committed. Review the content diff and merge. Also check for changed/removed API signatures — grep all Motoko code blocks in docs. |
284280
| `motoko-core` | Check for changed/removed API signatures — grep all Motoko code blocks in docs |
285281
| `cdk-rs` | Check `ic-cdk`, `ic-cdk-timers`, `ic-cdk-macros` API changes — grep all Rust code blocks |
@@ -326,68 +322,34 @@ Any link of the form `internetcomputer.org/.../ic-interface-spec#<anchor>` or `.
326322
grep -r "{#<anchor>}" docs/references/ic-interface-spec/
327323
```
328324

329-
### Synced files from submodules
325+
### Directly maintained spec files
330326

331-
| Local file | Source | Affects |
332-
|-----------|--------|---------|
333-
| `public/reference/ic.did` | `.sources/portal/docs/references/_attachments/ic.did` | Management canister reference — new/changed methods require updating `docs/references/management-canister.md` |
334-
| `public/references/_attachments/certificates.cddl` | `.sources/portal/docs/references/_attachments/certificates.cddl` | Downloadable CDDL linked from `docs/references/ic-interface-spec/certification.md` |
335-
| `public/references/_attachments/requests.cddl` | `.sources/portal/docs/references/_attachments/requests.cddl` | Downloadable CDDL linked from `docs/references/ic-interface-spec/https-interface.md` |
336-
| `public/references/_attachments/http-gateway.did` | `.sources/portal/docs/references/_attachments/http-gateway.did` | Downloadable Candid interface linked from `docs/references/http-gateway-spec.md` |
337-
| `docs/references/ic-interface-spec/` | `.sources/portal/docs/references/ic-interface-spec.md` | IC interface spec split into 7 focused pages — apply portal diffs by section (see checklist below) |
338-
| `docs/references/http-gateway-spec.md` | `.sources/portal/docs/references/http-gateway-protocol-spec.md` | HTTP Gateway spec — apply portal diff as a patch on every bump |
327+
The following files are no longer synced from an external submodule — they are maintained directly in this repository. Update them manually when the IC team announces a new version of the relevant specification.
339328

340-
**Portal bump checklist (run on every portal bump):**
329+
| Local file | What it is | When to update |
330+
|-----------|-----------|---------------|
331+
| `public/references/ic.did` | Candid interface of the IC management canister | New management canister methods or changed types; update `docs/references/management-canister.md` and affected guides alongside |
332+
| `public/references/_attachments/certificates.cddl` | Certificate CDDL schema (linked from `docs/references/ic-interface-spec/certification.md`) | IC certification spec changes |
333+
| `public/references/_attachments/requests.cddl` | Request CDDL schema (linked from `docs/references/ic-interface-spec/https-interface.md`) | IC HTTPS interface spec changes |
334+
| `public/references/_attachments/http-gateway.did` | HTTP Gateway Candid interface (linked from `docs/references/http-gateway-spec.md`) | HTTP Gateway spec changes |
335+
| `docs/references/ic-interface-spec/` | IC Interface Spec split into 7 focused pages | IC spec version bumps — apply changes to the matching file (see section mapping below) |
336+
| `docs/references/http-gateway-spec.md` | HTTP Gateway Protocol Spec | HTTP Gateway spec version bumps |
341337

342-
**Step 1 — `ic.did` and `_attachments/`:**
343-
1. `diff public/reference/ic.did .sources/portal/docs/references/_attachments/ic.did`
344-
2. If changed: `cp .sources/portal/docs/references/_attachments/ic.did public/reference/ic.did`
345-
3. Review diff for new/changed/removed methods
346-
4. Update `docs/references/management-canister.md` and any affected guides
347-
5. For each of `certificates.cddl`, `requests.cddl`, `http-gateway.did`: `diff public/references/_attachments/<file> .sources/portal/docs/references/_attachments/<file>` — if changed, copy the updated file to `public/references/_attachments/`
338+
**IC Interface Spec — section-to-file mapping:**
348339

349-
**Step 2 — `ic-interface-spec/`:** The spec is now split into 7 files under `docs/references/ic-interface-spec/`. Each file maps to a section of the portal source:
350-
351-
| File | Portal section (## heading) |
340+
| File | IC spec section |
352341
|---|---|
353342
| `index.md` | Introduction, Pervasive concepts, The system state tree |
354343
| `https-interface.md` | HTTPS Interface |
355344
| `canister-interface.md` | Canister module format, Canister interface (System API) |
356345
| `management-canister.md` | The IC management canister, The IC Bitcoin API, The IC Provisional API |
357346
| `certification.md` | Certification, The HTTP Gateway protocol |
358347
| `abstract-behavior.md` | Abstract behavior |
359-
| `changelog.md` | `.sources/portal/docs/references/_attachments/interface-spec-changelog.md` (NOT `ic-interface-spec.md`) |
360-
361-
For every commit in the bump range that touched `docs/references/ic-interface-spec.md`:
362-
1. `git -C .sources/portal show <commit> -- docs/references/ic-interface-spec.md > /tmp/spec.diff`
363-
2. Inspect the diff: identify which section(s) changed
364-
3. Apply the relevant hunks manually to the corresponding file(s) in `docs/references/ic-interface-spec/`
365-
4. Update any cross-file anchor links (`(./other.md#anchor)`) if headings were added or removed
366-
5. Verify new methods/fields are reflected in `docs/references/management-canister.md` if they touch the management canister
367-
368-
For every commit in the bump range that touched `docs/references/_attachments/interface-spec-changelog.md`:
369-
1. `git -C .sources/portal show <commit> -- docs/references/_attachments/interface-spec-changelog.md > /tmp/changelog.diff`
370-
2. Apply the new version entries to `docs/references/ic-interface-spec/changelog.md`
371-
372-
**Step 3 — `http-gateway-spec.md`:** For every commit in the bump range that touched `docs/references/http-gateway-protocol-spec.md`:
373-
1. `git -C .sources/portal show <commit> -- docs/references/http-gateway-protocol-spec.md > /tmp/patch.diff`
374-
2. `patch -F 5 -p1 --input=/tmp/patch.diff docs/references/http-gateway-spec.md`
375-
3. Resolve any rejects manually (see note below on link adaptation)
376-
4. Run `grep -n "ic-interface-spec" docs/references/http-gateway-spec.md` and convert any newly introduced links
377-
378-
**Link adaptation for `http-gateway-spec.md`:** The portal source uses absolute `/references/ic-interface-spec#anchor` URLs. Our file uses relative paths into the split `ic-interface-spec/` directory. After every sync, any link of the form `/references/ic-interface-spec#<anchor>` or `./ic-interface-spec.md#<anchor>` must be converted. Use the anchor-to-file mapping at the bottom of `docs/references/http-gateway-spec.md` as the authoritative guide. If a new anchor appears that is not in the comment, find its file with:
379-
```bash
380-
grep -r "{#<anchor>}" docs/references/ic-interface-spec/
381-
```
348+
| `changelog.md` | IC spec changelog |
382349

383-
**Finding which commits touched which files:**
350+
**Link adaptation for `http-gateway-spec.md`:** If an update introduces absolute `/references/ic-interface-spec#<anchor>` links, convert them to relative paths using the anchor-to-file mapping at the bottom of `docs/references/http-gateway-spec.md`. If a new anchor is not in that comment, find its file with:
384351
```bash
385-
git -C .sources/portal log --oneline <old-ref>..<new-ref> -- docs/references/ic-interface-spec.md
386-
git -C .sources/portal show <commit> -- docs/references/ic-interface-spec.md | grep "^[+-]## " | head -20 # identify which sections changed
387-
git -C .sources/portal log --oneline <old-ref>..<new-ref> -- docs/references/_attachments/interface-spec-changelog.md
388-
git -C .sources/portal log --oneline <old-ref>..<new-ref> -- docs/references/http-gateway-protocol-spec.md
389-
git -C .sources/portal log --oneline <old-ref>..<new-ref> -- docs/references/_attachments/ic.did
390-
git -C .sources/portal log --oneline <old-ref>..<new-ref> -- docs/references/_attachments/certificates.cddl docs/references/_attachments/requests.cddl docs/references/_attachments/http-gateway.did
352+
grep -r "{#<anchor>}" docs/references/ic-interface-spec/
391353
```
392354

393355
## Planning artifacts (`.docs-plan/`)
@@ -450,10 +412,6 @@ sidebar:
450412
---
451413
```
452414

453-
## Portal tracking
454-
455-
The old portal (`dfinity/portal`) has been replaced by this site. The `.sources/portal` submodule is kept as a read-only reference for spec content (see `ic.did` sync checklist above) but no longer needs active monitoring for content changes.
456-
457415
## Commands
458416

459417
- `npm run dev` — Local dev server

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ docs/ # All documentation (.md only)
3939
└── reference/ # Specs and reference
4040
.sources/ # Pinned upstream source repos (read-only)
4141
├── VERSIONS # Current pinned versions for versioned submodules
42-
└── ... # portal, icp-cli, motoko, cdk-rs, icskills, examples, ...
42+
└── ... # icp-cli, motoko, cdk-rs, icskills, examples, ...
4343
.docs-plan/ # Planning artifacts and authoring workflow
4444
AGENTS.md # Contributor and agent instructions
4545
```

docs/references/ic-interface-spec/abstract-behavior.md

Lines changed: 85 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -696,10 +696,16 @@ The following predicate describes when an envelope `E` correctly signs the enclo
696696
```
697697
verify_envelope({ content = C }, U, T)
698698
= { p : p is CanisterID } if U = anonymous_id
699+
∧ C.sender_info = null
699700
verify_envelope({ content = C, sender_pubkey = PK, sender_sig = Sig, sender_delegation = DS}, U, T)
700-
= TS if U = mk_self_authenticating_id E.sender_pubkey
701+
= TS if U = mk_self_authenticating_id PK
701702
∧ (PK', TS) = verify_delegations(DS, PK, T, { p : p is CanisterId })
702703
∧ verify_signature PK' Sig ("\x0Aic-request" · hash_of_map(C))
704+
∧ (if PK = canister_signature_pk Signing_canister_id _:
705+
C.sender_info = null
706+
∨ (verify_signature PK C.sender_info.sig ("\x0Eic-sender-info" · C.sender_info.info)
707+
∧ C.sender_info.signer = Signing_canister_id)
708+
else C.sender_info = null)
703709
verify_delegations([], PK, T, TS) = (PK, TS)
704710
verify_delegations([D] · DS, PK, T, TS)
705711
= verify_delegations(DS, D.pubkey, T, TS ∩ delegation_targets(D))
@@ -762,12 +768,6 @@ Conditions
762768
```html
763769

764770
E.content.canister_id ∈ verify_envelope(E, E.content.sender, S.system_time)
765-
if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed:
766-
if not (E.content.sender_info = null):
767-
verify_signature E.sender_pubkey E.content.sender_info.sig ("\x0Eic-sender-info" · E.content.sender_info.info)
768-
E.content.sender_info.signer = Signing_canister_id
769-
else:
770-
E.content.sender_info = null
771771
if E.content.sender = mk_self_authenticating_id (canister_signature_pk Signing_canister_id Seed):
772772
if E.content.sender_info = null:
773773
Caller_info_data = ""
@@ -797,7 +797,7 @@ liquid_balance(S, E.content.canister_id) ≥ 0
797797
E.content.arg = candid({canister_id = CanisterId, …})
798798
E.content.sender ∈ S.controllers[CanisterId] ∪ S.subnet_admins[S.canister_subnet[CanisterId]]
799799
E.content.method_name ∈
800-
{ "start_canister", "stop_canister", "uninstall_code", "delete_canister", "canister_status" }
800+
{ "start_canister", "stop_canister", "uninstall_code", "delete_canister", "canister_status", "canister_metrics" }
801801
) ∨ (
802802
E.content.canister_id = ic_principal
803803
E.content.sender ∈ S.subnet_admins[S.canister_subnet[ECID]]
@@ -867,12 +867,6 @@ Conditions
867867
```html
868868

869869
E.content.canister_id ∈ verify_envelope(E, E.content.sender, S.system_time)
870-
if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed:
871-
if not (E.content.sender_info = null):
872-
verify_signature E.sender_pubkey E.content.sender_info.sig ("\x0Eic-sender-info" · E.content.sender_info.info)
873-
E.content.sender_info.signer = Signing_canister_id
874-
else:
875-
E.content.sender_info = null
876870
|E.content.nonce| <= 32
877871
E.content ∉ dom(S.requests)
878872
S.system_time <= E.content.ingress_expiry
@@ -1995,12 +1989,6 @@ is_effective_canister_id(E.content, ECID)
19951989
S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id
19961990
Q.arg = candid(A)
19971991
A.canister_id ∈ verify_envelope(E, Q.sender, S.system_time)
1998-
if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed:
1999-
if not (Q.sender_info = null):
2000-
verify_signature E.sender_pubkey Q.sender_info.sig ("\x0Eic-sender-info" · Q.sender_info.info)
2001-
Q.sender_info.signer = Signing_canister_id
2002-
else:
2003-
Q.sender_info = null
20041992
Q.sender ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]]
20051993
20061994
```
@@ -2021,6 +2009,82 @@ verify_response(Q, R, Cert) ∧ lookup(["time"], Cert) = Found S.system_time //
20212009
20222010
```
20232011
2012+
#### IC Management Canister: Canister metrics
2013+
2014+
Only the controllers of the given canister or subnet admins can get metrics about it.
2015+
2016+
```html
2017+
2018+
S.messages = Older_messages · CallMessage M · Younger_messages
2019+
(M.queue = Unordered) or (∀ msg ∈ Older_messages. msg.queue ≠ M.queue)
2020+
M.callee = ic_principal
2021+
M.method_name = 'canister_metrics'
2022+
M.arg = candid(A)
2023+
M.caller ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]]
2024+
2025+
R = <implementation-specific>
2026+
2027+
```
2028+
2029+
State after
2030+
2031+
```html
2032+
2033+
S with
2034+
messages = Older_messages · Younger_messages ·
2035+
ResponseMessage {
2036+
origin = M.origin
2037+
response = Reply (candid(R))
2038+
refunded_cycles = M.transferred_cycles
2039+
}
2040+
2041+
```
2042+
2043+
The IC method `canister_metrics` can also be invoked via management canister query calls.
2044+
They are calls to `/api/v3/canister/<ECID>/query`
2045+
with CBOR content `Q` such that `Q.canister_id = ic_principal`.
2046+
2047+
Submitted request to `/api/v3/canister/<ECID>/query`
2048+
2049+
```html
2050+
2051+
E : Envelope
2052+
2053+
```
2054+
2055+
Conditions
2056+
2057+
```html
2058+
2059+
E.content = CanisterQuery Q
2060+
Q.canister_id = ic_principal
2061+
Q.method_name = 'canister_metrics'
2062+
|Q.nonce| <= 32
2063+
is_effective_canister_id(E.content, ECID)
2064+
S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id
2065+
Q.arg = candid(A)
2066+
A.canister_id ∈ verify_envelope(E, Q.sender, S.system_time)
2067+
Q.sender ∈ S.controllers[A.canister_id] ∪ S.subnet_admins[S.canister_subnet[A.canister_id]]
2068+
2069+
```
2070+
2071+
Query response `R`:
2072+
2073+
```html
2074+
2075+
{status: "replied"; reply: {arg: candid(<implementation-specific>)}, signatures: Sigs}
2076+
2077+
```
2078+
2079+
where the query `Q`, the response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister/<ECID>/read_state` satisfy the following:
2080+
2081+
```html
2082+
2083+
verify_response(Q, R, Cert) ∧ lookup(["time"], Cert) = Found S.system_time // or "recent enough"
2084+
2085+
```
2086+
2087+
20242088
#### IC Management Canister: Canister information
20252089
20262090
Every canister can retrieve the canister history, current module hash, and current controllers of every other canister (including itself).
@@ -4288,12 +4352,6 @@ is_effective_canister_id(E.content, ECID)
42884352
S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id
42894353
Q.arg = candid(A)
42904354
A.canister_id ∈ verify_envelope(E, Q.sender, S.system_time)
4291-
if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed:
4292-
if not (Q.sender_info = null):
4293-
verify_signature E.sender_pubkey Q.sender_info.sig ("\x0Eic-sender-info" · Q.sender_info.info)
4294-
Q.sender_info.signer = Signing_canister_id
4295-
else:
4296-
Q.sender_info = null
42974355
(S[A.canister_id].canister_log_visibility = Public)
42984356
or
42994357
(S[A.canister_id].canister_log_visibility = Controllers and Q.sender in S[A.canister_id].controllers)
@@ -4511,12 +4569,6 @@ Conditions
45114569

45124570
E.content = CanisterQuery Q
45134571
Q.canister_id ∈ verify_envelope(E, Q.sender, S.system_time)
4514-
if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed:
4515-
if not (Q.sender_info = null):
4516-
verify_signature E.sender_pubkey Q.sender_info.sig ("\x0Eic-sender-info" · Q.sender_info.info)
4517-
Q.sender_info.signer = Signing_canister_id
4518-
else:
4519-
Q.sender_info = null
45204572
|Q.nonce| <= 32
45214573
is_effective_canister_id(E.content, ECID)
45224574
S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id
@@ -4655,7 +4707,7 @@ Conditions
46554707
E.content = ReadState RS
46564708
TS = verify_envelope(E, RS.sender, S.system_time)
46574709
|E.content.nonce| <= 32
4658-
S.system_time <= RS.ingress_expiry
4710+
S.system_time <= RS.ingress_expiry or RS.sender = anonymous_id
46594711
∀ path ∈ RS.paths. may_read_path_for_subnet(S, RS.sender, path)
46604712
∀ (["request_status", Rid] · _) ∈ RS.paths. ∀ R ∈ dom(S.requests). hash_of_map(R) = Rid => R.canister_id ∈ TS
46614713

0 commit comments

Comments
 (0)