diff --git a/knot2/crates/knot-lexicons/Cargo.toml b/knot2/crates/knot-lexicons/Cargo.toml index a47088037..4d9595525 100644 --- a/knot2/crates/knot-lexicons/Cargo.toml +++ b/knot2/crates/knot-lexicons/Cargo.toml @@ -20,14 +20,13 @@ url = { workspace = true } [build-dependencies] anyhow = { workspace = true } jacquard-lexicon = { workspace = true, features = ["codegen"] } +serde_json = { workspace = true } walkdir = { workspace = true } [features] -default = ["sh_tangled"] +default = ["com_atproto", "org_tangled"] com_atproto = [] -com_bad_example = [] org_tangled = [] -sh_tangled = ["com_atproto", "org_tangled"] streaming = [] [lints.clippy] diff --git a/knot2/crates/knot-lexicons/build.rs b/knot2/crates/knot-lexicons/build.rs index cbda511ff..a54e71e37 100644 --- a/knot2/crates/knot-lexicons/build.rs +++ b/knot2/crates/knot-lexicons/build.rs @@ -66,6 +66,16 @@ fn stage_lexicons(src: &Path, dst: &Path) -> Result<()> { }) }) .try_for_each(|entry| -> Result<()> { + let staged_namespace = + std::fs::read(entry.path()).context("read lexicon for staging")?; + let staged_namespace = serde_json::from_slice::(&staged_namespace) + .context("parse lexicon for staging")?; + let staged_namespace = staged_namespace["id"].as_str().unwrap_or_default(); + if !staged_namespace.starts_with("com.atproto.") + && !staged_namespace.starts_with("org.tangled.") + { + return Ok(()); + } let rel = entry.path().strip_prefix(src).context("strip src prefix")?; let target = dst.join(rel); if let Some(parent) = target.parent() { diff --git a/knot2/crates/knot-lexicons/src/lib.rs b/knot2/crates/knot-lexicons/src/lib.rs index ec7ee242d..17afab4ac 100644 --- a/knot2/crates/knot-lexicons/src/lib.rs +++ b/knot2/crates/knot-lexicons/src/lib.rs @@ -8,134 +8,37 @@ mod _lex; pub use _lex::*; -mod scan { - const RECORD_OBJECT: &str = "record: LexRecordRecord::Object(LexObject {"; - - const fn run(hay: &str, pat: &str, at: usize, lo: usize, hi: usize) -> bool { - match hi - lo { - 0 | 1 => hi == lo || hay.as_bytes()[at + lo] == pat.as_bytes()[lo], - span => run(hay, pat, at, lo, lo + span / 2) && run(hay, pat, at, lo + span / 2, hi), - } - } - - const fn find(hay: &str, pat: &str, lo: usize, hi: usize) -> Option { - match hi - lo { - 1 if lo + pat.len() <= hay.len() && run(hay, pat, lo, 0, pat.len()) => Some(lo), - 0 | 1 => None, - span => match find(hay, pat, lo, lo + span / 2) { - Some(at) => Some(at), - None => find(hay, pat, lo + span / 2, hi), - }, - } - } - - pub const fn count(hay: &str, pat: &str, from: usize) -> usize { - match find(hay, pat, from, hay.len()) { - None => 0, - Some(at) => 1 + count(hay, pat, at + 1), - } - } - - pub const fn in_record(hay: &str, pat: &str) -> bool { - match find(hay, RECORD_OBJECT, 0, hay.len()) { - None => false, - Some(at) => find(hay, pat, at + RECORD_OBJECT.len(), hay.len()).is_some(), - } - } -} - -macro_rules! deprecated_collection { - ($nsid:literal, $bindings:literal) => { - const _: () = { - let bindings = include_str!($bindings); - assert!( - scan::count(bindings, "/// DEPRECATED: ", 0) > 0, - concat!( - $nsid, - " lost defs.main.description, so the Rust doc drops the notice" - ) - ); - assert!( - scan::in_record(bindings, "DEPRECATED: "), - concat!( - $nsid, - " lost record.description, so the Go doc drops the notice" - ) - ); - assert!( - scan::count(bindings, "key: Some(CowStr::new_static(\"tid\"))", 0) == 1, - concat!( - $nsid, - " no longer keys records by tid, so every existing rkey is wrong" - ) - ); - }; - }; -} -deprecated_collection!("sh.tangled.knot.member", "_lex/sh_tangled/knot/member.rs"); -deprecated_collection!( - "sh.tangled.repo.collaborator", - "_lex/sh_tangled/repo/collaborator.rs" -); - #[cfg(test)] mod tests { use jacquard_common::DefaultStr; - use jacquard_common::types::{ - collection::Collection, - string::{Datetime, Did}, - }; + use jacquard_common::types::{collection::Collection, string::Datetime}; - use crate::{ - scan::count, - sh_tangled::{knot, repo}, - }; + use crate::org_tangled::{graph, knot}; const WHEN: &str = "2026-06-11T00:00:00Z"; - const NSIDS: [&str; 4] = [ + const NSIDS: [&str; 2] = [ ::NSID, - ::NSID, - ::NSID, - ::NSID, + ::NSID, ]; - const _: () = assert!( - count( - include_str!("_lex/sh_tangled/knot.rs"), - "\npub mod member", - 0 - ) + count( - include_str!("_lex/sh_tangled/repo.rs"), - "\npub mod collaborator", - 0 - ) == NSIDS.len(), - "a generated member or collaborator collection is missing from NSIDS" - ); #[test] - fn deprecating_a_collection_leaves_the_shape_a_consumer_already_indexed() { + fn a_grant_record_is_the_bare_offer_shape() { let member = knot::member::Member:: { created_at: Datetime::raw_str(WHEN), - domain: "knot.nel.pet".into(), - subject: Did::new_owned("did:plc:lyna").expect("literal did parses"), extra_data: None, }; - let collaborator = repo::collaborator::Collaborator:: { + let collaborator = graph::collaborator::Collaborator:: { created_at: Datetime::raw_str(WHEN), - repo: Did::new_owned("did:plc:repo").expect("literal did parses"), - subject: Did::new_owned("did:plc:lyna").expect("literal did parses"), extra_data: None, }; assert_eq!( serde_json::json!([member, collaborator]), serde_json::json!([ - { "$type": "sh.tangled.knot.member", "createdAt": WHEN, - "domain": "knot.nel.pet", "subject": "did:plc:lyna" }, - { "$type": "sh.tangled.repo.collaborator", "createdAt": WHEN, - "repo": "did:plc:repo", "subject": "did:plc:lyna" } + { "$type": "org.tangled.knot.member", "createdAt": WHEN }, + { "$type": "org.tangled.graph.collaborator", "createdAt": WHEN } ]), - "the notice is prose on a description, so a member or collaborator already in \ - somebody's index still decodes as the fact it always was" + "a grant record is the bare offer keyed by its subject and holds no other fact" ); } diff --git a/knot2/lexicons/repo/setContributionPolicy.json b/knot2/lexicons/repo/setContributionPolicy.json deleted file mode 100644 index 4505e56d4..000000000 --- a/knot2/lexicons/repo/setContributionPolicy.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "lexicon": 1, - "id": "sh.tangled.repo.setContributionPolicy", - "defs": { - "main": { - "type": "procedure", - "description": "Set who may contribute issues, comments, labels, reactions and pulls to repository this knot hosts. Repository owner and knot's admins may call it. Repository takes knot-wide default at creation.", - "input": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": [ - "repo", - "contributionPolicy" - ], - "properties": { - "repo": { - "type": "string", - "format": "did", - "description": "DID of repository whose policy changes" - }, - "contributionPolicy": { - "type": "string", - "enum": [ - "anyone", - "members", - "collaborators" - ], - "description": "anyone: any account with DID may contribute. Members: accounts on this knot's member roll. Collaborators: owner and repository's collaborators." - } - } - } - } - } - } -} diff --git a/lexicons/org/tangled/actor/defs.json b/lexicons/org/tangled/actor/defs.json new file mode 100644 index 000000000..931f9d696 --- /dev/null +++ b/lexicons/org/tangled/actor/defs.json @@ -0,0 +1,42 @@ +{ + "lexicon": 1, + "id": "org.tangled.actor.defs", + "defs": { + "profileViewBasic": { + "type": "object", + "required": ["did", "handle"], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string", + "format": "handle" + }, + "avatar": { + "type": "string", + "format": "uri" + }, + "viewer": { + "type": "ref", + "ref": "#viewerState" + } + } + }, + "viewerState": { + "type": "object", + "description": "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests.", + "properties": { + "following": { + "type": "string", + "format": "at-uri" + }, + "followedBy": { + "type": "string", + "format": "at-uri" + } + } + } + } +} diff --git a/lexicons/org/tangled/git/defs.json b/lexicons/org/tangled/git/defs.json new file mode 100644 index 000000000..8f38c754b --- /dev/null +++ b/lexicons/org/tangled/git/defs.json @@ -0,0 +1,235 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.defs", + "defs": { + "signature": { + "type": "object", + "required": [ + "name", + "email", + "when" + ], + "properties": { + "name": { + "type": "string", + "description": "Person name" + }, + "email": { + "type": "string", + "description": "Person email" + }, + "when": { + "type": "string", + "format": "datetime", + "description": "Timestamp of the signature" + } + } + }, + "commit": { + "type": "object", + "required": [ + "hash", + "author", + "committer", + "message", + "tree" + ], + "properties": { + "hash": { + "type": "string", + "description": "Commit Object ID" + }, + "author": { + "type": "ref", + "ref": "#signature" + }, + "committer": { + "type": "ref", + "ref": "#signature" + }, + "message": { + "type": "string" + }, + "tree": { + "type": "string", + "description": "Tree Object ID" + } + } + }, + "submodule": { + "type": "object", + "required": [ + "name", + "url" + ], + "properties": { + "name": { + "type": "string", + "description": "Submodule name" + }, + "url": { + "type": "string", + "description": "Submodule repository URL" + }, + "branch": { + "type": "string", + "description": "Branch to track in the submodule" + } + } + }, + "fileDiff": { + "type": "object", + "required": [ + "lhsSrc", + "rhsSrc", + "hunks", + "hasSyntacticChanges" + ], + "properties": { + "lhsSrc": { + "type": "ref", + "ref": "#diffSrc" + }, + "rhsSrc": { + "type": "ref", + "ref": "#diffSrc" + }, + "hunks": { + "type": "array", + "items": { + "type": "ref", + "ref": "#diffHunk" + } + }, + "hasByteChanges": { + "type": "ref", + "ref": "#byteChanges", + "description": "Byte count per side, present only when the two sides differ" + }, + "hasSyntacticChanges": { + "type": "boolean" + } + } + }, + "diffSrc": { + "type": "object", + "description": "One side of a file diff.", + "required": [ + "path", + "oid", + "size", + "isBinary", + "isSubmodule" + ], + "properties": { + "path": { + "type": "string" + }, + "oid": { + "type": "string", + "description": "Blob oid, or the commit oid for a submodule" + }, + "size": { + "type": "integer", + "minimum": 0 + }, + "isBinary": { + "type": "boolean" + }, + "isSubmodule": { + "type": "boolean" + }, + "content": { + "type": "string", + "description": "Inlined content. Used on interdiff's LHS." + } + } + }, + "diffHunk": { + "type": "object", + "required": [ + "novelLhs", + "novelRhs", + "lines" + ], + "properties": { + "novelLhs": { + "type": "array", + "description": "0-based line numbers holding novel content on the left side", + "items": { + "type": "integer", + "minimum": 0 + } + }, + "novelRhs": { + "type": "array", + "description": "0-based line numbers holding novel content on the right side", + "items": { + "type": "integer", + "minimum": 0 + } + }, + "lines": { + "type": "array", + "items": { + "type": "ref", + "ref": "#linePair" + } + } + } + }, + "linePair": { + "type": "object", + "description": "One aligned line pair; a side is absent when that side has no line.", + "properties": { + "lhs": { + "type": "integer", + "minimum": 0 + }, + "rhs": { + "type": "integer", + "minimum": 0 + } + } + }, + "byteChanges": { + "type": "object", + "required": [ + "lhs", + "rhs" + ], + "properties": { + "lhs": { + "type": "integer", + "minimum": 0 + }, + "rhs": { + "type": "integer", + "minimum": 0 + } + } + }, + "diffStat": { + "type": "object", + "required": [ + "insertions", + "deletions", + "filesChanged" + ], + "properties": { + "insertions": { + "type": "integer", + "minimum": 0 + }, + "deletions": { + "type": "integer", + "minimum": 0 + }, + "filesChanged": { + "type": "integer", + "minimum": 0 + } + } + } + } +} diff --git a/lexicons/org/tangled/git/deleteBranch.json b/lexicons/org/tangled/git/deleteBranch.json new file mode 100644 index 000000000..d4e6d84be --- /dev/null +++ b/lexicons/org/tangled/git/deleteBranch.json @@ -0,0 +1,30 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.deleteBranch", + "defs": { + "main": { + "type": "procedure", + "description": "Delete a branch on this repository", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "repo", + "branch" + ], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the repository" + }, + "branch": { + "type": "string" + } + } + } + } + } + } +} diff --git a/lexicons/org/tangled/git/getArchive.json b/lexicons/org/tangled/git/getArchive.json new file mode 100644 index 000000000..3254606ef --- /dev/null +++ b/lexicons/org/tangled/git/getArchive.json @@ -0,0 +1,63 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.getArchive", + "defs": { + "main": { + "type": "query", + "parameters": { + "type": "params", + "required": [ + "repo", + "ref", + "prefix" + ], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the repository" + }, + "ref": { + "type": "string", + "description": "Git reference (branch, tag, or commit SHA)" + }, + "format": { + "type": "string", + "description": "Archive format", + "enum": [ + "tar.gz", + "zip" + ], + "default": "tar.gz" + }, + "prefix": { + "type": "string", + "description": "Prefix for files in the archive" + } + } + }, + "output": { + "encoding": "*/*", + "description": "Binary archive data" + }, + "errors": [ + { + "name": "RepoNotFound", + "description": "Repository not found or access denied" + }, + { + "name": "RefNotFound", + "description": "Git reference not found" + }, + { + "name": "InvalidRequest", + "description": "Invalid request parameters" + }, + { + "name": "ArchiveTooLarge", + "description": "Archive exceeds the size limit" + } + ] + } + } +} diff --git a/lexicons/org/tangled/git/getBlame.json b/lexicons/org/tangled/git/getBlame.json new file mode 100644 index 000000000..9f7462f62 --- /dev/null +++ b/lexicons/org/tangled/git/getBlame.json @@ -0,0 +1,139 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.getBlame", + "defs": { + "main": { + "type": "query", + "description": "Line-by-line attribution of a file at a ref.", + "parameters": { + "type": "params", + "required": ["repo", "ref", "path"], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the repository" + }, + "ref": { + "type": "string", + "description": "Git revision (branch, tag, or commit Object ID)" + }, + "path": { + "type": "string", + "description": "Path of the file to blame" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["commit", "commits", "runs"], + "properties": { + "commit": { + "type": "string", + "description": "The resolved commit Object ID" + }, + "commits": { + "type": "array", + "description": "Unique commits referenced by blame runs", + "items": { + "type": "ref", + "ref": "#commit" + } + }, + "runs": { + "type": "array", + "description": "Complete, gap-free one-based line ranges sorted by start", + "items": { + "type": "ref", + "ref": "#run" + } + } + } + } + }, + "errors": [ + { + "name": "RepoNotFound", + "description": "Repo is not registered on this knot" + }, + { + "name": "RefNotFound", + "description": "The given ref doesn't resolve in this repo" + }, + { + "name": "FileNotFound", + "description": "The given path doesn't exist at the given ref" + }, + { + "name": "BlameTooLarge", + "description": "The file is too large to blame" + }, + { + "name": "BlameTimeout", + "description": "Blame computation timed out" + }, + { + "name": "InvalidRequest", + "description": "Invalid request parameters" + } + ] + }, + "commit": { + "type": "object", + "required": ["sha", "subject", "authoredAt", "author"], + "properties": { + "sha": { + "type": "string", + "description": "Commit Object ID" + }, + "subject": { + "type": "string", + "description": "First line of the commit message" + }, + "authoredAt": { + "type": "string", + "format": "datetime", + "description": "When the change was authored" + }, + "author": { + "type": "ref", + "ref": "#author" + } + } + }, + "author": { + "type": "object", + "required": ["name", "email"], + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, + "run": { + "type": "object", + "required": ["start", "count", "commit"], + "properties": { + "start": { + "type": "integer", + "minimum": 1, + "description": "First line of the run, one-based" + }, + "count": { + "type": "integer", + "minimum": 1, + "description": "Number of lines in the run" + }, + "commit": { + "type": "string", + "description": "The commit Object ID of the entry in commits this run is attributed to" + } + } + } + } +} diff --git a/lexicons/org/tangled/git/getBlob.json b/lexicons/org/tangled/git/getBlob.json new file mode 100644 index 000000000..b8ddd745e --- /dev/null +++ b/lexicons/org/tangled/git/getBlob.json @@ -0,0 +1,36 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.getBlob", + "defs": { + "main": { + "type": "query", + "parameters": { + "type": "params", + "required": [ + "repo", + "path" + ], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the repository" + }, + "ref": { + "type": "string", + "description": "Git reference (branch, tag, or commit SHA)", + "default": "HEAD" + }, + "path": { + "type": "string", + "description": "Path within the repository tree" + } + } + }, + "output": { + "encoding": "*/*", + "description": "raw blob served in octet-stream" + } + } + } +} diff --git a/lexicons/org/tangled/git/getBranch.json b/lexicons/org/tangled/git/getBranch.json new file mode 100644 index 000000000..aec0e87d4 --- /dev/null +++ b/lexicons/org/tangled/git/getBranch.json @@ -0,0 +1,76 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.getBranch", + "defs": { + "main": { + "type": "query", + "parameters": { + "type": "params", + "required": [ + "repo", + "name" + ], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the repository" + }, + "name": { + "type": "string", + "description": "Branch name to get information for" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "name", + "hash", + "message", + "author", + "totalCommits" + ], + "properties": { + "name": { + "type": "string", + "description": "Branch name" + }, + "hash": { + "type": "string", + "description": "Latest commit hash on this branch" + }, + "message": { + "type": "string", + "description": "Latest commit message" + }, + "author": { + "type": "ref", + "ref": "org.tangled.git.defs#signature" + }, + "totalCommits": { + "type": "integer", + "description": "Number of commits reachable from this branch" + } + } + } + }, + "errors": [ + { + "name": "RepoNotFound", + "description": "Repository not found or access denied" + }, + { + "name": "BranchNotFound", + "description": "Branch not found" + }, + { + "name": "InvalidRequest", + "description": "Invalid request parameters" + } + ] + } + } +} diff --git a/lexicons/org/tangled/git/getDiff.json b/lexicons/org/tangled/git/getDiff.json new file mode 100644 index 000000000..1e9777c81 --- /dev/null +++ b/lexicons/org/tangled/git/getDiff.json @@ -0,0 +1,67 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.getDiff", + "defs": { + "main": { + "type": "query", + "description": "Full diff between two revisions of a repository.", + "parameters": { + "type": "params", + "required": ["repo", "base", "head"], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the repository" + }, + "base": { + "type": "string", + "description": "Base revision (branch, tag, or commit Object ID)" + }, + "head": { + "type": "string", + "description": "Head revision (branch, tag, or commit Object ID)" + }, + "paths": { + "type": "string", + "description": "Only include diffs touching these paths, comma-separated" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["diffs"], + "properties": { + "diffs": { + "type": "array", + "items": { + "type": "ref", + "ref": "org.tangled.git.defs#fileDiff" + } + }, + "stats": { + "type": "ref", + "ref": "org.tangled.git.defs#diffStat" + } + } + } + }, + "errors": [ + { + "name": "RepoNotFound", + "description": "Repo is not registered on this knot" + }, + { + "name": "RefNotFound", + "description": "A given revision doesn't resolve in this repo" + }, + { + "name": "InvalidRequest", + "description": "Invalid request parameters" + } + ] + } + } +} diff --git a/lexicons/org/tangled/git/getEntry.json b/lexicons/org/tangled/git/getEntry.json new file mode 100644 index 000000000..4a9350c85 --- /dev/null +++ b/lexicons/org/tangled/git/getEntry.json @@ -0,0 +1,95 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.getEntry", + "defs": { + "main": { + "type": "query", + "description": "get metadata of blob by ref and path", + "parameters": { + "type": "params", + "required": [ + "repo", + "path" + ], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the repository" + }, + "ref": { + "type": "string", + "description": "Git revision (branch, tag, or commit Object ID)", + "default": "HEAD" + }, + "path": { + "type": "string", + "description": "path of the entity" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "name", + "mode", + "oid", + "size" + ], + "properties": { + "name": { + "type": "string", + "description": "The file name" + }, + "mode": { + "type": "string", + "enum": [ + "0040000", + "0100644", + "0100755", + "0120000", + "0160000" + ] + }, + "oid": { + "type": "string" + }, + "size": { + "type": "integer", + "description": "Blob size" + }, + "lastCommit": { + "type": "ref", + "ref": "org.tangled.git.defs#commit" + }, + "submodule": { + "type": "ref", + "ref": "org.tangled.git.defs#submodule", + "description": "Submodule information if path is a submodule" + } + } + } + }, + "errors": [ + { + "name": "RepoNotFound", + "description": "Repository not found or access denied" + }, + { + "name": "RefNotFound", + "description": "Git reference not found" + }, + { + "name": "EntryNotFound", + "description": "Entry not found" + }, + { + "name": "InvalidRequest", + "description": "Invalid request parameters" + } + ] + } + } +} diff --git a/lexicons/org/tangled/git/getTag.json b/lexicons/org/tangled/git/getTag.json new file mode 100644 index 000000000..e14b1feab --- /dev/null +++ b/lexicons/org/tangled/git/getTag.json @@ -0,0 +1,76 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.getTag", + "defs": { + "main": { + "type": "query", + "parameters": { + "type": "params", + "required": [ + "repo", + "tag" + ], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the repository" + }, + "tag": { + "type": "string", + "description": "Name of tag, such as v1.3.0" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "name", + "oid", + "message", + "author" + ], + "properties": { + "name": { + "type": "string", + "description": "Tag name" + }, + "oid": { + "type": "string", + "description": "Latest commit oid on this tag" + }, + "message": { + "type": "string", + "description": "Latest commit message" + }, + "author": { + "type": "ref", + "ref": "org.tangled.git.defs#signature" + }, + "annotation": { + "type": "ref", + "ref": "org.tangled.git.listTags#annotation", + "description": "Present only for annotated tags" + } + } + } + }, + "errors": [ + { + "name": "RepoNotFound", + "description": "Repository not found or access denied" + }, + { + "name": "RefNotFound", + "description": "Git reference not found" + }, + { + "name": "InvalidRequest", + "description": "Invalid request parameters" + } + ] + } + } +} diff --git a/lexicons/org/tangled/git/getTree.json b/lexicons/org/tangled/git/getTree.json new file mode 100644 index 000000000..16f2f3e37 --- /dev/null +++ b/lexicons/org/tangled/git/getTree.json @@ -0,0 +1,105 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.getTree", + "defs": { + "main": { + "type": "query", + "parameters": { + "type": "params", + "required": [ + "repo" + ], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the repository" + }, + "ref": { + "type": "string", + "description": "Git revision (branch, tag, or commit Object ID)", + "default": "HEAD" + }, + "path": { + "type": "string", + "description": "Path within the repository tree", + "default": "" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "entries" + ], + "properties": { + "entries": { + "type": "array", + "items": { + "type": "ref", + "ref": "#treeEntry" + } + }, + "lastCommit": { + "type": "ref", + "ref": "org.tangled.git.defs#commit" + } + } + } + }, + "errors": [ + { + "name": "RepoNotFound", + "description": "Repository not found or access denied" + }, + { + "name": "RefNotFound", + "description": "Git reference not found" + }, + { + "name": "PathNotFound", + "description": "Path not found in repository tree" + }, + { + "name": "InvalidRequest", + "description": "Invalid request parameters" + } + ] + }, + "treeEntry": { + "type": "object", + "required": [ + "name", + "mode", + "size" + ], + "properties": { + "name": { + "type": "string", + "description": "Relative file or directory name" + }, + "mode": { + "type": "string", + "description": "File mode", + "enum": [ + "0040000", + "0100644", + "0100755", + "0120000", + "0160000" + ] + }, + "size": { + "type": "integer", + "description": "File size in bytes" + }, + "lastCommit": { + "type": "ref", + "ref": "org.tangled.git.defs#commit" + } + } + } + } +} diff --git a/lexicons/org/tangled/git/listBranches.json b/lexicons/org/tangled/git/listBranches.json new file mode 100644 index 000000000..257688731 --- /dev/null +++ b/lexicons/org/tangled/git/listBranches.json @@ -0,0 +1,105 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.listBranches", + "defs": { + "main": { + "type": "query", + "parameters": { + "type": "params", + "required": [ + "repo" + ], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the repository" + }, + "limit": { + "type": "integer", + "description": "Maximum number of branches to return", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { + "type": "string", + "description": "Pagination cursor" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "branches", + "total" + ], + "properties": { + "branches": { + "type": "array", + "items": { + "type": "ref", + "ref": "#branchInfo" + } + }, + "cursor": { + "type": "string" + }, + "total": { + "type": "integer", + "description": "Total number of branches in the repository" + }, + "defaultBranch": { + "type": "string", + "description": "Name of the default branch" + } + } + } + }, + "errors": [ + { + "name": "RepoNotFound", + "description": "Repository not found or access denied" + }, + { + "name": "InvalidRequest", + "description": "Invalid request parameters" + } + ] + }, + "branchInfo": { + "type": "object", + "required": [ + "name", + "oid", + "message", + "author", + "committer" + ], + "properties": { + "name": { + "type": "string", + "description": "Branch name" + }, + "oid": { + "type": "string", + "description": "Latest commit oid on this branch" + }, + "message": { + "type": "string", + "description": "Latest commit message" + }, + "author": { + "type": "ref", + "ref": "org.tangled.git.defs#signature" + }, + "committer": { + "type": "ref", + "ref": "org.tangled.git.defs#signature" + } + } + } + } +} diff --git a/lexicons/org/tangled/git/listCommits.json b/lexicons/org/tangled/git/listCommits.json new file mode 100644 index 000000000..fce2f29ca --- /dev/null +++ b/lexicons/org/tangled/git/listCommits.json @@ -0,0 +1,79 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.listCommits", + "defs": { + "main": { + "type": "query", + "description": "List commit history reachable from a ref, newest first. The ref may be any full ref name, including refs/cobs/*.", + "parameters": { + "type": "params", + "required": ["repo", "ref"], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the repository" + }, + "ref": { + "type": "string", + "description": "Git revision (branch, tag, commit Object ID, or full ref name)" + }, + "path": { + "type": "string", + "description": "Only list commits touching this path", + "default": "" + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50, + "description": "Maximum number of commits to return in this page" + }, + "cursor": { + "type": "string", + "description": "Commit Object ID of the last commit in the previous page" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["commits"], + "properties": { + "commits": { + "type": "array", + "items": { + "type": "ref", + "ref": "org.tangled.git.defs#commit" + } + }, + "cursor": { + "type": "string", + "description": "Cursor for the next page, absent when the last page is reached" + } + } + } + }, + "errors": [ + { + "name": "RepoNotFound", + "description": "Repo is not registered on this knot" + }, + { + "name": "RefNotFound", + "description": "The given ref doesn't resolve in this repo" + }, + { + "name": "PathNotFound", + "description": "The given path doesn't exist at the given ref" + }, + { + "name": "InvalidRequest", + "description": "Invalid request parameters" + } + ] + } + } +} diff --git a/lexicons/org/tangled/git/listLanguages.json b/lexicons/org/tangled/git/listLanguages.json new file mode 100644 index 000000000..07ca67c37 --- /dev/null +++ b/lexicons/org/tangled/git/listLanguages.json @@ -0,0 +1,107 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.listLanguages", + "defs": { + "main": { + "type": "query", + "description": "Language breakdown of a repository's tree at a ref.", + "parameters": { + "type": "params", + "required": ["repo"], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the repository" + }, + "ref": { + "type": "string", + "description": "Git revision (branch, tag, or commit Object ID)", + "default": "HEAD" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["ref", "languages"], + "properties": { + "ref": { + "type": "string", + "description": "The ref the breakdown was computed at" + }, + "languages": { + "type": "array", + "items": { + "type": "ref", + "ref": "#language" + } + }, + "totalSize": { + "type": "integer", + "minimum": 0, + "description": "Total bytes across counted files" + }, + "totalFiles": { + "type": "integer", + "minimum": 0, + "description": "Total counted files" + } + } + } + }, + "errors": [ + { + "name": "RepoNotFound", + "description": "Repo is not registered on this knot" + }, + { + "name": "RefNotFound", + "description": "The given ref doesn't resolve in this repo" + }, + { + "name": "InvalidRequest", + "description": "Invalid request parameters" + } + ] + }, + "language": { + "type": "object", + "required": ["name", "size", "percentage"], + "properties": { + "name": { + "type": "string", + "description": "Language name" + }, + "size": { + "type": "integer", + "minimum": 0, + "description": "Bytes counted for this language" + }, + "percentage": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Share of total size, 0-100" + }, + "fileCount": { + "type": "integer", + "minimum": 0, + "description": "Files counted for this language" + }, + "color": { + "type": "string", + "description": "Render color for this language" + }, + "extensions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "File extensions recognized as this language" + } + } + } + } +} diff --git a/lexicons/org/tangled/git/listRefs.json b/lexicons/org/tangled/git/listRefs.json new file mode 100644 index 000000000..4dcb7b03d --- /dev/null +++ b/lexicons/org/tangled/git/listRefs.json @@ -0,0 +1,100 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.listRefs", + "defs": { + "main": { + "type": "query", + "description": "List every ref & its commit SHA for a git repo, equivalent to git ls-remote. Gives the full ref state of a repo.", + "parameters": { + "type": "params", + "required": ["repo"], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the git repo as minted by the knot" + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 100, + "description": "Maximum number of refs to return in this page" + }, + "cursor": { + "type": "string", + "description": "Pagination cursor" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["refs"], + "properties": { + "refs": { + "type": "array", + "items": { "type": "ref", "ref": "#ref" }, + "maxLength": 1000 + }, + "cursor": { + "type": "string", + "description": "Cursor for the next page, absent when the last page is reached" + }, + "defaultBranch": { + "type": "ref", + "ref": "#defaultBranch" + } + } + } + }, + "errors": [ + { + "name": "RepoNotFound", + "description": "Repo is not registered on this knot" + }, + { + "name": "InvalidRequest", + "description": "Invalid request parameters" + } + ] + }, + "ref": { + "type": "object", + "required": ["ref", "sha"], + "properties": { + "ref": { + "type": "string", + "description": "Full ref name, eg. refs/heads/main or refs/tags/v1.0", + "maxGraphemes": 256, + "maxLength": 2560 + }, + "sha": { + "type": "string", + "description": "Object SHA the ref points at. Width depends on the repo's git object-format.", + "minLength": 40, + "maxLength": 128 + } + } + }, + "defaultBranch": { + "type": "object", + "required": ["ref"], + "properties": { + "ref": { + "type": "string", + "description": "Default branch ref name that HEAD points at, eg. refs/heads/main.", + "maxGraphemes": 256, + "maxLength": 2560 + }, + "head": { + "type": "string", + "description": "Commit SHA at the tip of the default branch, for reconciling against a last-known state. Width depends on the repo's git object-format.", + "minLength": 40, + "maxLength": 128 + } + } + } + } +} diff --git a/lexicons/org/tangled/git/listTags.json b/lexicons/org/tangled/git/listTags.json new file mode 100644 index 000000000..142e4bbb9 --- /dev/null +++ b/lexicons/org/tangled/git/listTags.json @@ -0,0 +1,117 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.listTags", + "defs": { + "main": { + "type": "query", + "parameters": { + "type": "params", + "required": [ + "repo" + ], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the repository" + }, + "limit": { + "type": "integer", + "description": "Maximum number of tags to return", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { + "type": "string", + "description": "Pagination cursor" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "tags", + "total" + ], + "properties": { + "tags": { + "type": "array", + "items": { + "type": "ref", + "ref": "#tagInfo" + } + }, + "cursor": { + "type": "string" + }, + "total": { + "type": "integer", + "description": "Total number of tags in the repository" + } + } + } + }, + "errors": [ + { + "name": "RepoNotFound", + "description": "Repository not found or access denied" + }, + { + "name": "InvalidRequest", + "description": "Invalid request parameters" + } + ] + }, + "tagInfo": { + "type": "object", + "required": [ + "name", + "oid", + "message", + "author" + ], + "properties": { + "name": { + "type": "string", + "description": "Tag name" + }, + "oid": { + "type": "string", + "description": "Latest commit oid on this tag" + }, + "message": { + "type": "string", + "description": "Latest commit message" + }, + "author": { + "type": "ref", + "ref": "org.tangled.git.defs#signature" + }, + "annotation": { + "type": "ref", + "ref": "#annotation", + "description": "Present only for annotated tags" + } + } + }, + "annotation": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "tagger": { + "type": "ref", + "ref": "org.tangled.git.defs#signature" + }, + "message": { + "type": "string", + "description": "Tag message" + } + } + } + } +} diff --git a/lexicons/org/tangled/git/mergeCheck.json b/lexicons/org/tangled/git/mergeCheck.json new file mode 100644 index 000000000..9e0de5799 --- /dev/null +++ b/lexicons/org/tangled/git/mergeCheck.json @@ -0,0 +1,85 @@ +{ + "lexicon": 1, + "id": "org.tangled.git.mergeCheck", + "defs": { + "main": { + "type": "procedure", + "description": "Check whether a patch record merges cleanly into its target branch, without writing anything.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["repo", "patch"], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the repository" + }, + "patch": { + "type": "string", + "format": "cid", + "description": "Record key of the org.tangled.review.patch record to check" + }, + "branch": { + "type": "string", + "description": "Target branch. Defaults to the ticket's targetBranch, falling back to the repo's default branch." + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["isConflicted", "conflicts"], + "properties": { + "isConflicted": { + "type": "boolean", + "description": "Whether the merge has unresolved conflicts" + }, + "conflicts": { + "type": "array", + "items": { + "type": "ref", + "ref": "#conflict" + } + }, + "message": { + "type": "string", + "description": "Additional detail, e.g. unrelated histories" + } + } + } + }, + "errors": [ + { + "name": "RepoNotFound", + "description": "Repo is not registered on this knot" + }, + { + "name": "PatchNotFound", + "description": "No org.tangled.review.patch record exists under the given key" + }, + { + "name": "InvalidRequest", + "description": "Invalid request parameters" + } + ] + }, + "conflict": { + "type": "object", + "required": ["filename", "reason"], + "properties": { + "filename": { + "type": "string", + "description": "Path of the conflicted file" + }, + "reason": { + "type": "string", + "description": "Why the file conflicts" + } + } + } + } +} diff --git a/lexicons/org/tangled/knot/block.json b/lexicons/org/tangled/knot/block.json new file mode 100644 index 000000000..6c8795911 --- /dev/null +++ b/lexicons/org/tangled/knot/block.json @@ -0,0 +1,23 @@ +{ + "lexicon": 1, + "id": "org.tangled.knot.block", + "defs": { + "main": { + "type": "record", + "description": "Ban of an account from this knot, w/ the banned DID as rkey.", + "key": "any", + "record": { + "type": "object", + "required": [ + "createdAt" + ], + "properties": { + "createdAt": { + "type": "string", + "format": "datetime" + } + } + } + } + } +} diff --git a/lexicons/org/tangled/repo/defs.json b/lexicons/org/tangled/repo/defs.json index 0a53bdd4a..a0919c960 100644 --- a/lexicons/org/tangled/repo/defs.json +++ b/lexicons/org/tangled/repo/defs.json @@ -15,6 +15,31 @@ "properties": { "uri": { "type": "string", "format": "uri", "description": "git-clone uri of the repo" } } + }, + "repoViewBasic": { + "type": "object", + "required": ["did", "owner", "slug", "createdAt"], + "properties": { + "did": { "type": "string", "format": "did" }, + "owner": { "type": "ref", "ref": "org.tangled.actor.defs#profileViewBasic" }, + "slug": { "type": "string" }, + "description": { "type": "string" }, + "createdAt": { "type": "string", "format": "datetime" }, + "forkCount": { "type": "integer" }, + "starCount": { "type": "integer" }, + "openIssueCount": { "type": "integer" }, + "openPullCount": { "type": "integer" }, + "viewer": { "type": "ref", "ref": "#viewerState" }, + "isStarred": { "type": "boolean" } + } + }, + "viewerState": { + "type": "object", + "description": "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.", + "properties": { + "fork": { "type": "string", "format": "at-uri" }, + "star": { "type": "string", "format": "at-uri" } + } } } } diff --git a/lexicons/org/tangled/repo/deleteRepo.json b/lexicons/org/tangled/repo/deleteRepo.json index 8dbfbbcc1..681865fab 100644 --- a/lexicons/org/tangled/repo/deleteRepo.json +++ b/lexicons/org/tangled/repo/deleteRepo.json @@ -14,6 +14,10 @@ "repo": { "type": "string", "format": "did" + }, + "force": { + "type": "boolean", + "description": "Skip the check for a declaration record on the owner's PDS. Knot admins only." } } } diff --git a/lexicons/org/tangled/repo/forkSync.json b/lexicons/org/tangled/repo/forkSync.json new file mode 100644 index 000000000..96dd82aa9 --- /dev/null +++ b/lexicons/org/tangled/repo/forkSync.json @@ -0,0 +1,28 @@ +{ + "lexicon": 1, + "id": "org.tangled.repo.forkSync", + "defs": { + "main": { + "type": "procedure", + "description": "Fetch the upstream of a fork into this repository, updating the remote-tracking ref for the given branch.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["repo", "branch"], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the fork repository" + }, + "branch": { + "type": "string", + "description": "Branch to fetch from the upstream" + } + } + } + } + } + } +} diff --git a/lexicons/org/tangled/repo/listRecordVersions.json b/lexicons/org/tangled/repo/listRecordVersions.json new file mode 100644 index 000000000..c4bd9c137 --- /dev/null +++ b/lexicons/org/tangled/repo/listRecordVersions.json @@ -0,0 +1,94 @@ +{ + "lexicon": 1, + "id": "org.tangled.repo.listRecordVersions", + "defs": { + "main": { + "type": "query", + "description": "Every version of a record this knot hosts, oldest first. A version's bytes are fetched by content id through com.atproto.sync.getBlocks.", + "parameters": { + "type": "params", + "required": ["uri"], + "properties": { + "uri": { + "type": "string", + "format": "at-uri", + "description": "Record in a repo this knot hosts" + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 500, + "default": 100 + }, + "cursor": { + "type": "string", + "description": "Pagination cursor" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri", "versions"], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "versions": { + "type": "array", + "items": { + "type": "ref", + "ref": "#version" + } + }, + "cursor": { + "type": "string", + "description": "Cursor for the next page, left out when the page already reaches the newest version" + } + } + } + }, + "errors": [ + { + "name": "InvalidRequest", + "description": "The uri isn't an at-uri, or the collection isn't versioned" + }, + { + "name": "NotFound", + "description": "No such record on this knot" + }, + { + "name": "ProjectionWarming", + "description": "The record's change history is still being folded; retry shortly" + } + ] + }, + "version": { + "type": "object", + "required": ["changeId", "editor", "createdAt"], + "properties": { + "changeId": { + "type": "string", + "description": "Object ID of the git change that produced this version" + }, + "editor": { + "type": "string", + "format": "did", + "description": "The account that authored the change" + }, + "createdAt": { + "type": "string", + "format": "datetime", + "description": "When this knot wrote the commit this version rides in. Not the record's own `createdAt`." + }, + "cid": { + "type": "string", + "format": "cid", + "description": "Content id of this version. Missing when the version was removed or the change is a deletion." + } + } + } + } +} diff --git a/lexicons/org/tangled/repo/manifest.json b/lexicons/org/tangled/repo/manifest.json index 179dff1a3..c3f263f64 100644 --- a/lexicons/org/tangled/repo/manifest.json +++ b/lexicons/org/tangled/repo/manifest.json @@ -48,6 +48,17 @@ "org.tangled.repo.defs#sourceExternal" ] }, + "defaultBranch": { + "type": "string", + "description": "Default branch name without the refs/heads/ prefix. The knot will move HEAD to match when this field changes.", + "examples": ["main", "master"], + "maxLength": 244 + }, + "contributionPolicy": { + "type": "string", + "knownValues": ["anyone", "members", "collaborators"], + "description": "Who may open tickets and patches on this repo. anyone = any DID, members = the knot's member roll, collaborators = the owner plus repo collaborators. Absent means the knot-wide default." + }, "createdAt": { "type": "string", "format": "datetime" diff --git a/lexicons/org/tangled/repo/reserveKey.json b/lexicons/org/tangled/repo/reserveKey.json new file mode 100644 index 000000000..5112e02b0 --- /dev/null +++ b/lexicons/org/tangled/repo/reserveKey.json @@ -0,0 +1,41 @@ +{ + "lexicon": 1, + "id": "org.tangled.repo.reserveKey", + "defs": { + "main": { + "type": "procedure", + "description": "Reserve a repo identity under a DID the caller brings. The knot seals and holds a signing key for the repo DID; the caller publishes the matching public key in that DID's document before calling org.tangled.repo.createRepo with the same DID.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["repoDid"], + "properties": { + "repoDid": { + "type": "string", + "format": "did", + "description": "The caller-minted DID to reserve" + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["repoDid", "key"], + "properties": { + "repoDid": { + "type": "string", + "format": "did" + }, + "key": { + "type": "string", + "description": "Multibase-encoded public signing key the knot holds for this repo identity" + } + } + } + } + } + } +} diff --git a/lexicons/org/tangled/review/mergePatch.json b/lexicons/org/tangled/review/mergePatch.json new file mode 100644 index 000000000..48e3ac0c2 --- /dev/null +++ b/lexicons/org/tangled/review/mergePatch.json @@ -0,0 +1,72 @@ +{ + "lexicon": 1, + "id": "org.tangled.review.mergePatch", + "defs": { + "main": { + "type": "procedure", + "description": "Merge a patch into its target branch. The merged status is written in the same critical section as the branch update.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["repo", "patch"], + "properties": { + "repo": { + "type": "string", + "format": "did", + "description": "DID of the repository" + }, + "patch": { + "type": "string", + "format": "cid", + "description": "Record key of the org.tangled.review.patch record to merge" + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["tip", "merged"], + "properties": { + "tip": { + "type": "string", + "description": "Commit Object ID the target branch ended on; the current tip when no move was needed" + }, + "merged": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + }, + "description": "Patch records merged by this call" + } + } + } + }, + "errors": [ + { + "name": "MergeConflict", + "description": "The merge has unresolved conflicts" + }, + { + "name": "ClosedTicket", + "description": "The patch's ticket is closed" + }, + { + "name": "AlreadyMerged", + "description": "The patch is already merged" + }, + { + "name": "BranchMoved", + "description": "The target branch kept moving during the merge; a boot pass finishes the move once the branch is back at the old tip" + }, + { + "name": "NoSuchBranch", + "description": "The target branch doesn't exist" + } + ] + } + } +} diff --git a/lexicons/org/tangled/track/defs.json b/lexicons/org/tangled/track/defs.json index 37c46126f..3577dc726 100644 --- a/lexicons/org/tangled/track/defs.json +++ b/lexicons/org/tangled/track/defs.json @@ -32,12 +32,12 @@ }, "repo": { "type": "ref", - "ref": "sh.tangled.repo.defs#repoViewBasic" + "ref": "org.tangled.repo.defs#repoViewBasic" }, "author": { "type": "ref", "description": "Last editor of the ticket record (`x-tngl-editor`).", - "ref": "sh.tangled.actor.defs#profileViewBasic" + "ref": "org.tangled.actor.defs#profileViewBasic" }, "title": { "type": "union",