feat: upgrade panproto to v0.22.0 — field transforms on formatted strings panproto v0.22.0 fixes ComputeField transforms on string fields with format annotations (at-uri, datetime). Previously these were stored as child vertices and invisible to field transforms; now they're accessible as extra_fields. This makes at_uri_extract_did/at_uri_extract_name work correctly for Tangled issue/pull repo URIs, producing repoDid and repoName via panproto's expression language — no manual decomposition needed. Also parses from body vertex (:body) instead of record vertex, so field transforms are applied at the correct schema level. Added test: tangled_issue_transform_decomposes_repo_uri verifies the full parse → lift → to_json pipeline produces repoDid/repoName.

Author: Aaron Steven White
Commit c6bfa57d1672df298ea0401efbc3405ec59db0ba
Parent: bbdd3bdf41
Structural diff unavailable

These commits were pushed via plain git push, so no pre-parsed schemas are available. Install git-remote-cospan and re-push via panproto:// to see scope-level changes, breaking change detection, and semantic diffs.

brew install panproto/tap/git-remote-cospan
4 files changed +116 -70
@@ -436,7 +436,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
436436 
437437 [[package]]
438438 name = "cospan-appview"
439-version = "0.6.0"
439+version = "0.6.3"
440440 dependencies = [
441441  "anyhow",
442442  "async-trait",
@@ -482,7 +482,7 @@ dependencies = [
482482 
483483 [[package]]
484484 name = "cospan-codegen"
485-version = "0.6.0"
485+version = "0.6.3"
486486 dependencies = [
487487  "anyhow",
488488  "panproto-check",
@@ -499,7 +499,7 @@ dependencies = [
499499 
500500 [[package]]
501501 name = "cospan-node"
502-version = "0.6.0"
502+version = "0.6.3"
503503 dependencies = [
504504  "anyhow",
505505  "async-trait",
@@ -1310,7 +1310,7 @@ dependencies = [
13101310  "libc",
13111311  "percent-encoding",
13121312  "pin-project-lite",
1313- "socket2 0.6.3",
1313+ "socket2 0.5.10",
13141314  "system-configuration",
13151315  "tokio",
13161316  "tower-service",
@@ -2001,8 +2001,8 @@ dependencies = [
20012001 
20022002 [[package]]
20032003 name = "panproto-check"
2004-version = "0.20.0"
2005-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2004+version = "0.22.0"
2005+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
20062006 dependencies = [
20072007  "panproto-gat",
20082008  "panproto-lens",
@@ -2016,8 +2016,8 @@ dependencies = [
20162016 
20172017 [[package]]
20182018 name = "panproto-core"
2019-version = "0.20.0"
2020-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2019+version = "0.22.0"
2020+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
20212021 dependencies = [
20222022  "panproto-check",
20232023  "panproto-gat",
@@ -2032,8 +2032,8 @@ dependencies = [
20322032 
20332033 [[package]]
20342034 name = "panproto-expr"
2035-version = "0.20.0"
2036-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2035+version = "0.22.0"
2036+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
20372037 dependencies = [
20382038  "rustc-hash",
20392039  "serde",
@@ -2042,8 +2042,8 @@ dependencies = [
20422042 
20432043 [[package]]
20442044 name = "panproto-expr-parser"
2045-version = "0.20.0"
2046-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2045+version = "0.22.0"
2046+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
20472047 dependencies = [
20482048  "chumsky",
20492049  "logos",
@@ -2052,8 +2052,8 @@ dependencies = [
20522052 
20532053 [[package]]
20542054 name = "panproto-gat"
2055-version = "0.20.0"
2056-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2055+version = "0.22.0"
2056+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
20572057 dependencies = [
20582058  "panproto-expr",
20592059  "rustc-hash",
@@ -2063,8 +2063,8 @@ dependencies = [
20632063 
20642064 [[package]]
20652065 name = "panproto-git"
2066-version = "0.20.0"
2067-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2066+version = "0.22.0"
2067+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
20682068 dependencies = [
20692069  "git2",
20702070  "miette",
@@ -2081,8 +2081,8 @@ dependencies = [
20812081 
20822082 [[package]]
20832083 name = "panproto-grammars"
2084-version = "0.20.0"
2085-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2084+version = "0.22.0"
2085+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
20862086 dependencies = [
20872087  "cc",
20882088  "serde",
@@ -2093,8 +2093,8 @@ dependencies = [
20932093 
20942094 [[package]]
20952095 name = "panproto-inst"
2096-version = "0.20.0"
2097-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2096+version = "0.22.0"
2097+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
20982098 dependencies = [
20992099  "bumpalo",
21002100  "panproto-expr",
@@ -2109,8 +2109,8 @@ dependencies = [
21092109 
21102110 [[package]]
21112111 name = "panproto-io"
2112-version = "0.20.0"
2113-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2112+version = "0.22.0"
2113+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
21142114 dependencies = [
21152115  "bumpalo",
21162116  "memchr",
@@ -2132,8 +2132,8 @@ dependencies = [
21322132 
21332133 [[package]]
21342134 name = "panproto-lens"
2135-version = "0.20.0"
2136-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2135+version = "0.22.0"
2136+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
21372137 dependencies = [
21382138  "panproto-expr",
21392139  "panproto-gat",
@@ -2149,8 +2149,8 @@ dependencies = [
21492149 
21502150 [[package]]
21512151 name = "panproto-mig"
2152-version = "0.20.0"
2153-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2152+version = "0.22.0"
2153+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
21542154 dependencies = [
21552155  "panproto-expr",
21562156  "panproto-gat",
@@ -2163,8 +2163,8 @@ dependencies = [
21632163 
21642164 [[package]]
21652165 name = "panproto-parse"
2166-version = "0.20.0"
2167-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2166+version = "0.22.0"
2167+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
21682168 dependencies = [
21692169  "memchr",
21702170  "miette",
@@ -2182,8 +2182,8 @@ dependencies = [
21822182 
21832183 [[package]]
21842184 name = "panproto-project"
2185-version = "0.20.0"
2186-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2185+version = "0.22.0"
2186+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
21872187 dependencies = [
21882188  "blake3",
21892189  "globset",
@@ -2202,8 +2202,8 @@ dependencies = [
22022202 
22032203 [[package]]
22042204 name = "panproto-protocols"
2205-version = "0.20.0"
2206-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2205+version = "0.22.0"
2206+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
22072207 dependencies = [
22082208  "blake3",
22092209  "panproto-gat",
@@ -2217,8 +2217,8 @@ dependencies = [
22172217 
22182218 [[package]]
22192219 name = "panproto-schema"
2220-version = "0.20.0"
2221-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2220+version = "0.22.0"
2221+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
22222222 dependencies = [
22232223  "panproto-expr",
22242224  "panproto-gat",
@@ -2230,8 +2230,8 @@ dependencies = [
22302230 
22312231 [[package]]
22322232 name = "panproto-vcs"
2233-version = "0.20.0"
2234-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2233+version = "0.22.0"
2234+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
22352235 dependencies = [
22362236  "blake3",
22372237  "panproto-check",
@@ -2251,8 +2251,8 @@ dependencies = [
22512251 
22522252 [[package]]
22532253 name = "panproto-xrpc"
2254-version = "0.20.0"
2255-source = "git+https://github.com/panproto/panproto.git?tag=v0.20.0#2ded0cd8b8027b86432d5b20e7860664b81a6430"
2254+version = "0.22.0"
2255+source = "git+https://github.com/panproto/panproto.git?tag=v0.22.0#9614c9e266727261af4b5a36896241469a30b15a"
22562256 dependencies = [
22572257  "hex",
22582258  "miette",
@@ -2500,7 +2500,7 @@ dependencies = [
25002500  "quinn-udp",
25012501  "rustc-hash",
25022502  "rustls",
2503- "socket2 0.6.3",
2503+ "socket2 0.5.10",
25042504  "thiserror",
25052505  "tokio",
25062506  "tracing",
@@ -2537,7 +2537,7 @@ dependencies = [
25372537  "cfg_aliases",
25382538  "libc",
25392539  "once_cell",
2540- "socket2 0.6.3",
2540+ "socket2 0.5.10",
25412541  "tracing",
25422542  "windows-sys 0.60.2",
25432543 ]
@@ -16,22 +16,22 @@ repository = "https://github.com/cospan-dev/cospan"
1616 # panproto — all from git because panproto-grammars needs vendored C sources
1717 # (too large for crates.io). All crates must come from the same source to avoid
1818 # type mismatches between git and crates.io versions of panproto-schema etc.
19-panproto-core = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0" }
20-panproto-vcs = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0" }
21-panproto-schema = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0" }
22-panproto-check = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0" }
23-panproto-lens = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0" }
24-panproto-protocols = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0" }
25-panproto-io = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0" }
26-panproto-inst = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0" }
27-panproto-gat = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0" }
28-panproto-mig = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0" }
29-panproto-expr = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0" }
30-panproto-expr-parser = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0" }
31-panproto-xrpc = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0" }
32-panproto-parse = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0", features = ["group-all"] }
33-panproto-git = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0" }
34-panproto-project = { git = "https://github.com/panproto/panproto.git", tag = "v0.20.0" }
19+panproto-core = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0" }
20+panproto-vcs = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0" }
21+panproto-schema = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0" }
22+panproto-check = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0" }
23+panproto-lens = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0" }
24+panproto-protocols = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0" }
25+panproto-io = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0" }
26+panproto-inst = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0" }
27+panproto-gat = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0" }
28+panproto-mig = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0" }
29+panproto-expr = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0" }
30+panproto-expr-parser = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0" }
31+panproto-xrpc = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0" }
32+panproto-parse = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0", features = ["group-all"] }
33+panproto-git = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0" }
34+panproto-project = { git = "https://github.com/panproto/panproto.git", tag = "v0.22.0" }
3535 git2 = "0.20"
3636 
3737 # Web framework
@@ -147,18 +147,7 @@ impl RecordTransformer {
147147         record: &serde_json::Value,
148148     ) -> Option<Result<serde_json::Value>> {
149149         let morphism = self.tangled_morphisms.get(tangled_nsid)?;
150-        let result = apply_morphism(morphism, record);
151-        if tangled_nsid == "sh.tangled.repo.issue" {
152-            if let Ok(ref json) = result {
153-                tracing::info!(
154-                    has_repo_did = json.get("repoDid").is_some(),
155-                    has_repo = json.get("repo").is_some(),
156-                    keys = ?json.as_object().map(|o| o.keys().collect::<Vec<_>>()),
157-                    "ISSUE transform output"
158-                );
159-            }
160-        }
161-        Some(result)
150+        Some(apply_morphism(morphism, record))
162151     }
163152 }
164153 
@@ -169,7 +158,9 @@ fn apply_projection(
169158     nsid: &str,
170159     record: &serde_json::Value,
171160 ) -> Result<serde_json::Value> {
172-    let instance = panproto_inst::parse::parse_json(schema, nsid, record)
161+    // Jetstream delivers the record body, so parse from the body vertex.
162+    let body_vertex = format!("{nsid}:body");
163+    let instance = panproto_inst::parse::parse_json(schema, &body_vertex, record)
173164         .map_err(|e| anyhow::anyhow!("parse {nsid}: {e:?}"))?;
174165 
175166     let lifted = lift_wtype_sigma(compiled, schema, &instance)
@@ -183,8 +174,11 @@ fn apply_morphism(
183174     mapping: &CompiledInterop,
184175     record: &serde_json::Value,
185176 ) -> Result<serde_json::Value> {
177+    // Jetstream delivers the record body (not the full ATProto record wrapper),
178+    // so parse from the body vertex, not the record vertex.
179+    let body_vertex = format!("{}:body", mapping.tangled_nsid);
186180     let instance =
187-        panproto_inst::parse::parse_json(&mapping.tangled_schema, &mapping.tangled_nsid, record)
181+        panproto_inst::parse::parse_json(&mapping.tangled_schema, &body_vertex, record)
188182             .map_err(|e| anyhow::anyhow!("parse {}: {e:?}", mapping.tangled_nsid))?;
189183 
190184     let lifted =
@@ -104,3 +104,55 @@ fn all_lexicon_files_found() {
104104         files.len()
105105     );
106106 }
107+
108+#[test]
109+fn tangled_issue_transform_decomposes_repo_uri() {
110+    let root = workspace_root();
111+    let lexicons_dir = root.join("packages/lexicons");
112+    let morphisms = cospan_codegen::tangled_interop::compile_all_morphisms(&lexicons_dir).unwrap();
113+
114+    let issue_morphism = morphisms
115+        .iter()
116+        .find(|m| m.tangled_nsid == "sh.tangled.repo.issue")
117+        .expect("should have sh.tangled.repo.issue morphism");
118+
119+    // Simulate a Tangled issue record
120+    let record = serde_json::json!({
121+        "$type": "sh.tangled.repo.issue",
122+        "repo": "at://did:plc:abc123/sh.tangled.repo/myrepo",
123+        "title": "Test issue",
124+        "body": "Test body",
125+        "createdAt": "2026-01-01T00:00:00Z"
126+    });
127+
128+    // Parse against the Tangled schema (body vertex, not record vertex)
129+    let body_vertex = format!("{}:body", issue_morphism.tangled_nsid);
130+    let instance = panproto_inst::parse::parse_json(
131+        &issue_morphism.tangled_schema,
132+        &body_vertex,
133+        &record,
134+    )
135+    .expect("should parse Tangled issue");
136+
137+    // Apply morphism (includes field transforms)
138+    let lifted = panproto_mig::lift_wtype_sigma(
139+        &issue_morphism.compiled,
140+        &issue_morphism.cospan_schema,
141+        &instance,
142+    )
143+    .expect("should lift");
144+
145+    // Emit JSON
146+    let output = panproto_inst::parse::to_json(&issue_morphism.cospan_schema, &lifted);
147+
148+    eprintln!("Transform output: {}", serde_json::to_string_pretty(&output).unwrap());
149+
150+    // The repo AT-URI should be decomposed into repoDid + repoName
151+    assert!(
152+        output.get("repoDid").is_some(),
153+        "should have repoDid field, got keys: {:?}",
154+        output.as_object().map(|o| o.keys().collect::<Vec<_>>())
155+    );
156+    assert_eq!(output["repoDid"], "did:plc:abc123");
157+    assert_eq!(output["repoName"], "myrepo");
158+}
cospan · schematic version control on atproto built on AT Protocol