diff --git a/Cargo.lock b/Cargo.lock index a6b3273..62245c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,7 +5,7 @@ version = 4 [[package]] name = "agent-ix-semantic-schema" version = "0.3.0" -source = "git+https://github.com/agent-ix/filament-core-data?branch=main#df5e55796db57a62e5b5984c8ec60d70049cebe0" +source = "git+https://github.com/agent-ix/filament-core-data?branch=main#45a927b258c297159567e4be6a40b6f466a42516" [[package]] name = "ahash" @@ -30,6 +30,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anstream" version = "1.0.0" @@ -119,7 +125,16 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ - "bit-vec", + "bit-vec 0.6.3", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", ] [[package]] @@ -128,12 +143,24 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +[[package]] +name = "borrow-or-share" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" + [[package]] name = "bstr" version = "1.12.1" @@ -244,6 +271,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + [[package]] name = "deranged" version = "0.5.8" @@ -276,6 +309,15 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +dependencies = [ + "serde", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -298,7 +340,18 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" dependencies = [ - "bit-set", + "bit-set 0.5.3", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "fancy-regex" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301f5bf187b3c295fce6468d3875037a0bccc5f6b151c63cac2f85babf21912" +dependencies = [ + "bit-set 0.8.0", "regex-automata", "regex-syntax", ] @@ -324,12 +377,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "fluent-uri" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" +dependencies = [ + "borrow-or-share", + "ref-cast", + "serde", +] + [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -349,6 +419,16 @@ dependencies = [ "num", ] +[[package]] +name = "fraction" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e246562084dde8ebbcc943b261c406ce4f68e5032ec28029a251a47d6a295500" +dependencies = [ + "num", + "num-bigint", +] + [[package]] name = "futures-core" version = "0.3.32" @@ -408,9 +488,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 5.3.0", "wasip2", + "wasm-bindgen", ] [[package]] @@ -451,7 +533,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -459,6 +541,11 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] [[package]] name = "heck" @@ -661,8 +748,8 @@ dependencies = [ "anyhow", "base64", "bytecount", - "fancy-regex", - "fraction", + "fancy-regex 0.13.0", + "fraction 0.15.4", "getrandom 0.2.17", "iso8601", "itoa", @@ -679,6 +766,59 @@ dependencies = [ "uuid", ] +[[package]] +name = "jsonschema" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a806f80c1f5560431009ce5ec29b59d38f950e0cd7db5f1b8925e6d8104e21" +dependencies = [ + "ahash", + "bytecount", + "data-encoding", + "email_address", + "fancy-regex 0.19.2", + "fraction 0.17.0", + "getrandom 0.3.4", + "itoa", + "jsonschema-regex", + "jsonschema-value", + "num-cmp", + "num-traits", + "percent-encoding", + "referencing", + "regex", + "serde", + "serde_json", + "strum", + "unicode-general-category", + "uuid-simd", +] + +[[package]] +name = "jsonschema-regex" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb862addfa7782108933abcf842fe25334523a9df5b89ea4d9620e3dd7b42181" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "jsonschema-value" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a05cd404c5ff6e2731dbbf7e290a27417750acfb59e329560a1d0af384c93fb0" +dependencies = [ + "ahash", + "bytecount", + "fraction 0.17.0", + "getrandom 0.3.4", + "num-cmp", + "num-traits", + "serde_json", + "zmij", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -758,6 +898,12 @@ version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +[[package]] +name = "micromap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" + [[package]] name = "nom" version = "8.0.0" @@ -798,9 +944,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", @@ -879,6 +1025,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + [[package]] name = "parking_lot" version = "0.12.5" @@ -988,7 +1140,7 @@ dependencies = [ "glob", "ix-cli-kit", "ix-trace-rs", - "jsonschema", + "jsonschema 0.18.3", "predicates", "quire-qualify", "quire-rs", @@ -1041,14 +1193,14 @@ dependencies = [ [[package]] name = "quire-rs" -version = "0.52.1" -source = "git+https://github.com/agent-ix/quire-rs?rev=12371baca61edd6aef71a38464b7933ce6d6cebe#12371baca61edd6aef71a38464b7933ce6d6cebe" +version = "0.53.0" +source = "git+https://github.com/agent-ix/quire-rs?rev=ce172a7ddcec21815f852c06fa5a0e340b79e57b#ce172a7ddcec21815f852c06fa5a0e340b79e57b" dependencies = [ "agent-ix-semantic-schema", "globset", "ignore", "indexmap", - "jsonschema", + "jsonschema 0.56.0", "loom", "quire-rust-extraction", "rayon", @@ -1063,7 +1215,7 @@ dependencies = [ [[package]] name = "quire-rust-extraction" version = "0.1.0" -source = "git+https://github.com/agent-ix/quire-rs?rev=12371baca61edd6aef71a38464b7933ce6d6cebe#12371baca61edd6aef71a38464b7933ce6d6cebe" +source = "git+https://github.com/agent-ix/quire-rs?rev=ce172a7ddcec21815f852c06fa5a0e340b79e57b#ce172a7ddcec21815f852c06fa5a0e340b79e57b" dependencies = [ "quire-code-parse", ] @@ -1118,6 +1270,43 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "referencing" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3b4a92fac7e28c27de3ad26df2ecb9e652f227b258e845af034052e5b22c96c" +dependencies = [ + "ahash", + "fluent-uri", + "getrandom 0.3.4", + "hashbrown 0.17.1", + "itoa", + "micromap", + "parking_lot", + "percent-encoding", + "serde_json", +] + [[package]] name = "regex" version = "1.12.3" @@ -1132,9 +1321,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -1308,6 +1497,27 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "syn" version = "2.0.117" @@ -1580,6 +1790,12 @@ dependencies = [ "tree-sitter-language", ] +[[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -1628,6 +1844,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "uuid-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "valuable" version = "0.1.1" @@ -1640,6 +1866,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "wait-timeout" version = "0.2.1" @@ -2016,6 +2248,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index f770c4b..ce72db0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ path = "src/main.rs" [dependencies] ix-cli-kit = { git = "https://github.com/agent-ix/ix-cli-kit", branch = "main", features = ["self-update"] } -quire-rs = { version = "=0.52.1", git = "https://github.com/agent-ix/quire-rs", rev = "12371baca61edd6aef71a38464b7933ce6d6cebe" } +quire-rs = { version = "=0.53.0", git = "https://github.com/agent-ix/quire-rs", rev = "ce172a7ddcec21815f852c06fa5a0e340b79e57b" } clap = { version = "4", features = ["derive"] } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/src/commands/validate.rs b/src/commands/validate.rs index 7f7e411..0242082 100644 --- a/src/commands/validate.rs +++ b/src/commands/validate.rs @@ -158,6 +158,7 @@ pub fn run(ctx: &Ctx, args: Args) -> anyhow::Result<()> { // Scans only glossary-bearing docs — never a full-corpus parse. let project_terms = quire_rs::glossary_terms_from_path(&scope); let lexicon = registry.lexicon_with(&project_terms); + let bundle_package = repo_bundle_package(&scope); let mut failures = 0usize; let mut warned = 0usize; @@ -219,9 +220,21 @@ pub fn run(ctx: &Ctx, args: Args) -> anyhow::Result<()> { // Composed type+object validation (FR-032-AC-11..13): the registry // is available, so resolve the frontmatter `object:` archetype too. - let result = quire_rs::validate_document_in_registry_with_lexicon( - ®istry, archetype, &text, &lexicon, - ); + // quire-rs FR-076: relationship targets qualify under the document's + // bundle package (`/`). Without one every relationships + // table reports `no-bundle-package` and no row is checked. + let result = match bundle_package.as_deref() { + Some(package) => quire_rs::validate_document_in_bundle( + ®istry, + archetype, + &text, + package, + Some(&lexicon), + ), + None => quire_rs::validate_document_in_registry_with_lexicon( + ®istry, archetype, &text, &lexicon, + ), + }; let outcome = surface_result(ctx, &label, &result); if outcome.had_errors { failures += 1; @@ -410,6 +423,34 @@ fn surface_bundle(ctx: &Ctx, report: &BundleReport) { } } +/// The repository identity (`/`) taken from the scope's `origin` +/// git remote, or `None` when the scope is not a git checkout with a remote. +fn repo_bundle_package(scope: &Path) -> Option { + let output = std::process::Command::new("git") + .arg("-C") + .arg(scope) + .args(["remote", "get-url", "origin"]) + .output() + .ok()?; + if !output.status.success() { + return None; + } + parse_remote_package(String::from_utf8_lossy(&output.stdout).trim()) +} + +/// Extract `/` from an https, ssh or scp-style git remote URL. +fn parse_remote_package(url: &str) -> Option { + let url = url.trim_end_matches('/').trim_end_matches(".git"); + let path = match url.split_once("://") { + Some((_, rest)) => rest.split_once('/')?.1, + None => url.split_once(':')?.1, + }; + let mut parts = path.rsplit('/'); + let repo = parts.next().filter(|p| !p.is_empty())?; + let org = parts.next().filter(|p| !p.is_empty())?; + Some(format!("{org}/{repo}")) +} + pub(crate) fn load_registry(ctx: &Ctx, args: &Args, scope: &Path) -> anyhow::Result { if !args.module.is_empty() { return super::load_module_set_registry(ctx, &args.module); @@ -761,6 +802,9 @@ fn validation_remedy(reason: ValidationReason) -> &'static str { ValidationReason::Semantic => { "correct the declaration named by the cited `semantic.*` diagnostic" } + ValidationReason::Regex => { + "change the cited value so it matches the locator's declared regex" + } } } @@ -792,3 +836,26 @@ mod tests { } } } + +#[cfg(test)] +mod bundle_package_tests { + use super::*; + + // PLAT-50: the bundle package comes from the origin remote in any URL form. + #[test] + fn remote_urls_yield_org_and_repo() { + for url in [ + "https://github.com/agent-ix/quire-cli.git", + "https://github.com/agent-ix/quire-cli", + "git@github.com:agent-ix/quire-cli.git", + "ssh://git@github.com/agent-ix/quire-cli.git", + ] { + assert_eq!( + parse_remote_package(url).as_deref(), + Some("agent-ix/quire-cli"), + "{url}" + ); + } + assert_eq!(parse_remote_package("not-a-url"), None); + } +}