Skip to content

Commit c3d29bc

Browse files
committed
Update Modality ID documentation and handling across various components
- Revised the terminology from "Modality form" to "Modality spelling" for clarity in documentation. - Updated references to `.id` values in contract state to reflect the new Modality spelling. - Enhanced the status page and templates to display IDs in the Modality spelling, ensuring consistency in representation. - Added JavaScript functions to handle Modality IDs in the frontend, allowing for proper display and interaction. - Included tests to verify the correct rendering of Modality IDs in various contexts. These changes aim to improve user understanding and ensure consistent handling of Modality IDs throughout the system.
1 parent f271e6c commit c3d29bc

13 files changed

Lines changed: 466 additions & 38 deletions

File tree

‎docs/concepts/modality-ids.md‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,31 @@ libp2p writes the same peer ID in more than one way. For one key:
1919
| base58 multihash (libp2p's legacy form) | `12D3KooW9pte76rpnggcLYkFaawuTEs5DC5axHkg3cK3cewGxxHd` |
2020
| CIDv1, base32 (libp2p's newer form) | `bafzaajaiaejcaabcornvmd5g5bolzmcr6smsje7owdd3xk7lojqydmthdt47iqmi` |
2121
| CIDv1, base36 | `k51qzi5uqu5d…` |
22-
| **Modality form**: the base32 CID, backwards | `imqi74tdhtmdyqjol7kx3ddwo7ejsms6rcmzlob5g5dmvnrocbaacjeaiajaazfab` |
22+
| **Modality spelling** | lowercase base32 CID, mirrored so the type is at the end | `imqi74tdhtmdyqjol7kx3ddwo7ejsms6rcmzlob5g5dmvnrocbaacjeaiajaazfab` |
2323

24-
The fixed header makes every forward form begin with the same characters
25-
for every key: `12D3KooW`, `bafzaajaiaejc`, `k51qzi5uqu5d`. Written
26-
backwards, the base32 CID begins with the key and ends with the header, so
27-
its first characters differ from ID to ID and it is all lowercase.
24+
The Modality spelling is the lowercase CIDv1 base32 form, mirrored so the
25+
type is at the end of the string. Forward forms open with a fixed type
26+
header (`12D3KooW`, `bafzaajaiaejc`, `k51qzi5uqu5d`). Mirrored, that type
27+
is the tail (`…aiajaazfab`) and the key comes first, so IDs differ from
28+
character one.
2829

29-
**The Modality form is the standard Modality ID.** Shorten it from the
30+
**The Modality spelling is the standard Modality ID.** Shorten it from the
3031
front (`imqi74td…`), never by keeping its end: every Modality ID ends in
3132
`…aiajaazfab`.
3233

3334
## Where each form is used
3435

35-
- **`.id` values** in contract state hold the Modality form. `modal id
36+
- **`.id` values** in contract state hold the Modality spelling. `modal id
3637
create` prints it and writes it to `~/.modality/ids/<name>.id`;
3738
`modal set-named-id`, `modal c set`, `modal c commit --post`, and
3839
`modal c create --signer` write it. A `.id` value posted earlier in base58
3940
stays valid.
4041
- **Contract IDs** and the keys of `head.signatures` are base58, and so is
4142
`head.payer`.
43+
- **A node's status page** shows node and contract IDs, and `.id` values,
44+
in the Modality spelling, shortened from the front. Its links, anchors and
45+
`status.json` keep base58, so nodes of different versions still match
46+
each other; `/contracts/<id>` takes either form.
4247
- **A `/p2p/` part of a multiaddr** must be base58: libp2p's multiaddr
4348
parser accepts no other form there.
4449
- Anywhere you type an ID (`--signer`, `--payer`, `--to`, `--to-contract`,
@@ -52,7 +57,7 @@ front (`imqi74td…`), never by keeping its end: every Modality ID ends in
5257
`signed_by`, `any_signed`, `all_signed`, `threshold`, `posts_own_key`, and
5358
`sent_to` read an ID from state and compare it with a commit's signers or
5459
sends. They compare the key each one names, so a signature keyed in base58
55-
matches a `.id` value in the Modality form, and a 32-byte ed25519 key in
60+
matches a `.id` value in the Modality spelling, and a 32-byte ed25519 key in
5661
hex matches both. One key is one signer: signing twice under two spellings
5762
counts once toward a threshold, and respelling a key does not get it past
5863
`-signed_by`.

‎docs/reference/commit-methods.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Paths must end with a known extension:
4141
| `.datetime` | Date and time | `"2024-01-15T10:30:00Z"` |
4242
| `.json` | JSON object | `{"key": "value"}` |
4343
| `.md` | Markdown | `"# Title\n\nContent..."` |
44-
| `.id` | [Modality ID](../concepts/modality-ids.md), standard form | `"imqi74td...aiajaazfab"` |
44+
| `.id` | [Modality spelling](../concepts/modality-ids.md): lowercase, mirrored, type at the end | `"imqi74td...aiajaazfab"` |
4545
| `.wasm` | WebAssembly | Base64-encoded WASM |
4646
| `.modality` | Rules/formulas | Modality syntax |
4747

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
sidebar_position: 7
3+
title: Agent on a Git Repo — Test Results
4+
---
5+
6+
# Agent on a Git Repo: Test Results
7+
8+
All 46 checks passed at commit `f271e6c6b005` on 2026-10-03.
9+
10+
These are the results of the two suites behind
11+
[An Agent on a Git Repo](agent-git-landing.md). The page is written by
12+
`tests/network/agent-git-landing/report.py` from a real run; it is not
13+
edited by hand.
14+
15+
| Suite | What it checks | Checks | Passed | Failed |
16+
| --- | --- | --- | --- | --- |
17+
| Contract proofs | The rules and witness model under theory `v2` and `v3`, commit by commit | 7 | 7 | 0 |
18+
| End-to-end run | A local hub, a bare origin, five keys, and `modal-git` doing each step | 39 | 39 | 0 |
19+
20+
## Contract proofs
21+
22+
`landing_tests` in `rust/modality-common` reads the example's own
23+
`model/default.modality` and `rules.txt`. Each case runs under theory
24+
`v2` and again under `v3`.
25+
26+
| Case | Result |
27+
| --- | --- |
28+
| Sets from writes the value accepted at another path | Pass |
29+
| The landing rules hold for the witness | Pass |
30+
| A replacement model cannot drop the path | Pass |
31+
| A clean change lands along the path | Pass |
32+
| A protected change needs a review of that sha | Pass |
33+
| A land without the right evidence is refused | Pass |
34+
| The agent cannot write evidence or change the contract | Pass |
35+
36+
## End-to-end run
37+
38+
`tests/network/agent-git-landing/test.sh`. A check that something is
39+
refused passes when it is refused. For the forged land, the run also
40+
checks that the hub's refusal names the rule it breaks.
41+
42+
| Stage | Check | Result |
43+
| --- | --- | --- |
44+
| Setup | Should create steward's passfile | Pass |
45+
| Setup | Should create agent's passfile | Pass |
46+
| Setup | Should create ci's passfile | Pass |
47+
| Setup | Should create reviewer's passfile | Pass |
48+
| Setup | Should create mallory's passfile | Pass |
49+
| Setup | The hub should listen on 18571 | Pass |
50+
| Setup | The steward bootstraps the contract and the origin hook | Pass |
51+
| Editing and local commits are free | The agent commits locally with no contract step | Pass |
52+
| Editing and local commits are free | Pushing main straight to the origin is refused | Pass |
53+
| A clean change lands along the path | A key that is not an agent's cannot propose | Pass |
54+
| A clean change lands along the path | The agent proposes its commit | Pass |
55+
| A clean change lands along the path | Landing before any attestation is refused | Pass |
56+
| A clean change lands along the path | The agent cannot attest its own change | Pass |
57+
| A clean change lands along the path | The runner attests | Pass |
58+
| A clean change lands along the path | The attestation: tests pass, nothing protected | Pass |
59+
| A clean change lands along the path | The runner cannot land | Pass |
60+
| A clean change lands along the path | The agent lands | Pass |
61+
| A clean change lands along the path | The origin's main is the landed sha | Pass |
62+
| A protected change needs a review of that sha | The agent proposes a change to the tests | Pass |
63+
| A protected change needs a review of that sha | The runner attests | Pass |
64+
| A protected change needs a review of that sha | The attestation marks it protected | Pass |
65+
| A protected change needs a review of that sha | Landing a protected change without a review is refused | Pass |
66+
| A protected change needs a review of that sha | The agent cannot review its own change | Pass |
67+
| A protected change needs a review of that sha | The reviewer approves that sha | Pass |
68+
| A protected change needs a review of that sha | The reviewed change lands | Pass |
69+
| A protected change needs a review of that sha | The origin's main is the reviewed sha | Pass |
70+
| A failing change does not land | The agent proposes a breaking change | Pass |
71+
| A failing change does not land | The runner attests | Pass |
72+
| A failing change does not land | The attestation records the failure | Pass |
73+
| A failing change does not land | A failed run is refused | Pass |
74+
| A failing change does not land | The origin refuses the unlanded sha | Pass |
75+
| A failing change does not land | Status names what is missing | Pass |
76+
| A client that skips local verify is refused by the hub | A client that skips the rules signs a head write | Pass |
77+
| A client that skips local verify is refused by the hub | The hub refuses the forged land | Pass |
78+
| A client that skips local verify is refused by the hub | The hub's refusal names the rules it breaks | Pass |
79+
| A client that skips local verify is refused by the hub | The origin still refuses the unlanded sha | Pass |
80+
| Audit: the log, the evidence, and the origin agree | The audit replays the log and finds no problems | Pass |
81+
| Audit: the log, the evidence, and the origin agree | The audit lists both lands | Pass |
82+
| Audit: the log, the evidence, and the origin agree | The audit catches a main the contract did not accept | Pass |
83+
84+
## Run it yourself
85+
86+
```bash
87+
# Both suites, and this page
88+
python3 tests/network/agent-git-landing/report.py
89+
90+
# Or each on its own
91+
(cd rust && cargo test -p modality-common --features model-governance --lib landing_tests)
92+
tests/network/agent-git-landing/test.sh
93+
```

‎docs/tutorials/agent-git-landing.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ reviewer's signature, however it is prompted.
1919
The network test `tests/network/agent-git-landing` runs everything below
2020
against a local hub. The tool, model and rules are in
2121
`examples/agent-git-landing`.
22+
Every check and its latest result is listed on
23+
[Test Results](agent-git-landing-results.md).
2224

2325
## The pieces
2426

‎rust/modality-node/src/status_server.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,8 @@ async fn api_contract_handler(
277277
id: String,
278278
source: NodeStatusSource,
279279
) -> Result<impl warp::Reply, warp::Rejection> {
280+
// Contracts are stored under base58; a pasted ID may be in any form.
281+
let id = modality_common::peer_id::normalize_peer_id(&id);
280282
let mgr = source.datastore.lock().await;
281283
match explorer::inspect_contract(&mgr, &id)
282284
.await
@@ -294,6 +296,8 @@ async fn api_commits_handler(
294296
id: String,
295297
source: NodeStatusSource,
296298
) -> Result<impl warp::Reply, warp::Rejection> {
299+
// Contracts are stored under base58; a pasted ID may be in any form.
300+
let id = modality_common::peer_id::normalize_peer_id(&id);
297301
let mgr = source.datastore.lock().await;
298302
match explorer::list_commits(&mgr, &id)
299303
.await
@@ -311,6 +315,8 @@ async fn api_replay_handler(
311315
id: String,
312316
source: NodeStatusSource,
313317
) -> Result<impl warp::Reply, warp::Rejection> {
318+
// Contracts are stored under base58; a pasted ID may be in any form.
319+
let id = modality_common::peer_id::normalize_peer_id(&id);
314320
let mgr = source.datastore.lock().await;
315321
match explorer::replay_contract(&mgr, &id, None).await {
316322
Ok(Some(replay)) => Ok(cors_json_status(StatusCode::OK, replay)),

‎rust/modality-node/src/templates/mod.rs‎

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,32 @@ fn link_role_label(label: &str) -> String {
6262
.join("+")
6363
}
6464

65+
/// A node or contract ID as shown: the standard Modality form when it is a
66+
/// peer ID, other text as is. Links and anchors keep the ID as stored.
67+
fn shown_id(id: &str) -> String {
68+
modality_common::peer_id::id_value(id)
69+
}
70+
71+
/// [`shown_id`], shortened: a peer ID from the front (the Modality form ends
72+
/// the same way for every ID), anything else from the middle.
73+
fn short_id(id: &str, keep: usize) -> String {
74+
let shown = shown_id(id);
75+
if modality_common::peer_id::parse_peer_id(id).is_ok() && shown.len() > keep * 2 {
76+
format!("{}...", &shown[..keep * 2])
77+
} else {
78+
truncate_middle(&shown, keep)
79+
}
80+
}
81+
6582
fn peer_code_link(href: &str, peer_id: &str, keep: Option<usize>) -> String {
6683
let shown = match keep {
67-
Some(keep) => truncate_middle(peer_id, keep),
68-
None => peer_id.to_string(),
84+
Some(keep) => short_id(peer_id, keep),
85+
None => shown_id(peer_id),
6986
};
7087
format!(
7188
r#"<a href="{}" title="{}"><code>{}</code></a>"#,
7289
esc(href),
73-
esc(peer_id),
90+
esc(&shown_id(peer_id)),
7491
esc(&shown)
7592
)
7693
}
@@ -125,18 +142,19 @@ fn render_block_status_row(block: &BlockStatus) -> String {
125142

126143
/// Template for a peer row in the peers table
127144
pub fn render_peer_row(peer_id: &str) -> String {
128-
format!("<tr><td><code>{}</code></td></tr>", peer_id)
145+
format!("<tr><td><code>{}</code></td></tr>", esc(&shown_id(peer_id)))
129146
}
130147

131148
/// Template for a peer row with status URL
132149
pub fn render_peer_row_with_url(peer_id: &str, status_url: Option<&str>) -> String {
133150
if let Some(url) = status_url {
134151
format!(
135152
r#"<tr><td><code>{}</code></td><td><a href="{}" target="_blank">Status</a></td></tr>"#,
136-
peer_id, url
153+
esc(&shown_id(peer_id)),
154+
url
137155
)
138156
} else {
139-
format!("<tr><td><code>{}</code></td><td>-</td></tr>", peer_id)
157+
format!("<tr><td><code>{}</code></td><td>-</td></tr>", esc(&shown_id(peer_id)))
140158
}
141159
}
142160

@@ -159,11 +177,11 @@ pub fn render_peer_row_with_metadata(
159177
format!(
160178
r#"<a href="{}" target="_blank" rel="noopener" title="{}"><code>{}</code></a>"#,
161179
esc(url),
162-
esc(peer_id),
163-
esc(peer_id)
180+
esc(&shown_id(peer_id)),
181+
esc(&shown_id(peer_id))
164182
)
165183
} else {
166-
format!("<code>{}</code>", esc(peer_id))
184+
format!("<code>{}</code>", esc(&shown_id(peer_id)))
167185
};
168186

169187
format!(
@@ -476,7 +494,7 @@ pub fn render_prefix_cert_row(cert: &PrefixCertStatus) -> String {
476494
&contract,
477495
&format!(
478496
"<code>{}</code>",
479-
esc(&truncate_middle(&cert.source_contract, 10))
497+
esc(&short_id(&cert.source_contract, 10))
480498
)
481499
),
482500
xref(
@@ -569,7 +587,7 @@ pub fn render_status_from_snapshot(status: &NodeStatus) -> String {
569587
connected_peers: status.connected_peers,
570588
total_miner_blocks: status.total_miner_blocks,
571589
cumulative_difficulty: status.cumulative_difficulty,
572-
peerid: status.peerid.clone(),
590+
peerid: shown_id(&status.peerid),
573591
network_name: status.network_name.clone(),
574592
role_display: link_role_label(&status.role_display),
575593
role_chips_html: render_role_chips(&status.active_roles),
@@ -718,6 +736,26 @@ pub struct StatusPageVars {
718736
pub prefix_certs_html: String,
719737
}
720738

739+
#[cfg(test)]
740+
mod id_display_tests {
741+
use super::*;
742+
743+
const BASE58: &str = "12D3KooW9pte76rpnggcLYkFaawuTEs5DC5axHkg3cK3cewGxxHd";
744+
const MODALITY: &str = "imqi74tdhtmdyqjol7kx3ddwo7ejsms6rcmzlob5g5dmvnrocbaacjeaiajaazfab";
745+
746+
#[test]
747+
fn peer_ids_show_in_the_modality_form_but_link_by_base58() {
748+
let link = peer_code_link(&format!("/nodes#node-{BASE58}"), BASE58, Some(4));
749+
assert!(link.contains(&format!("href=\"/nodes#node-{BASE58}\"")), "{link}");
750+
assert!(link.contains(&format!("title=\"{MODALITY}\"")), "{link}");
751+
assert!(link.contains("<code>imqi74td...</code>"), "{link}");
752+
assert_eq!(shown_id(BASE58), MODALITY);
753+
assert_eq!(short_id(MODALITY, 4), "imqi74td...");
754+
assert_eq!(shown_id("c_0123456789abcdef"), "c_0123456789abcdef");
755+
assert!(render_peer_row(BASE58).contains(MODALITY));
756+
}
757+
}
758+
721759
#[cfg(test)]
722760
fn sample_vars() -> StatusPageVars {
723761
StatusPageVars {

0 commit comments

Comments
 (0)