Skip to content

Commit 96d7edb

Browse files
Fix CI failures in PR #566
1 parent 08e2bcc commit 96d7edb

38 files changed

Lines changed: 1062 additions & 498 deletions

‎bots/rhodibot/canon/pin.toml‎

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
# reviewable commit.
1111
#
1212
# To re-pin: copy the new criteria file over `rsr-criteria-v2.a2ml`, update the
13-
# three version fields, the digest, and the `categories`, `criteria` and
13+
# version fields, the digest, and the `categories`, `criteria` and
1414
# `weight_sum` shape fields below, then run `cargo test`. The canon's own
1515
# arithmetic is re-checked on parse, so a half-copied file fails rather than
1616
# silently losing criteria.
1717
#
1818
# One discrepancy to be aware of, recorded rather than resolved here: the
19-
# release in `canon.lock` is 2.0.4 while the criteria file's own `[meta]
20-
# version` still reads 2.0.0-draft. Both are quoted below so the pin describes
19+
# release in `canon.lock` is 2.1.1 while the criteria file's own `[meta]
20+
# version` reads 2.1.0-draft. Both are quoted below so the pin describes
2121
# the artefact rather than a story about it. `canon.lock` also cannot be pinned
2222
# by commit: its `commit` field is still the all-zero placeholder with the
2323
# comment "fill at release".
@@ -26,17 +26,17 @@
2626
repo = "hyperpolymath/standards"
2727
path = "0-canon/rsr/rsr-criteria-v2.a2ml"
2828
slot = "criteria"
29-
canon_version = "2.0.4"
30-
criteria_version = "2.0.0-draft"
31-
released = "2026-09-17"
32-
pinned = "2026-09-19"
33-
sha256 = "37cb5f679b414f6ee99c6bb62c460fd5349ff7d50cc1dab25b1e1a8e8d3c7bb9"
29+
canon_version = "2.1.1"
30+
criteria_version = "2.1.0-draft"
31+
released = "2026-09-19"
32+
pinned = "2026-09-22"
33+
sha256 = "6a5aa8857bd0d0d58ef48827938ca17c251b6b854dacf59d306388d61694d82a"
3434

3535
# The shape of the pinned revision, checked after parsing as well as by digest.
3636
# A rule set that silently shrinks is the failure mode that matters: these
3737
# numbers make shrinking a test failure rather than a quieter scorecard.
3838
categories = 11
39-
criteria = 74
39+
criteria = 78
4040
weight_sum = 88
4141

4242
# The release's second artefact: the gate table. It says which capabilities a
@@ -49,10 +49,10 @@ weight_sum = 88
4949
repo = "hyperpolymath/standards"
5050
path = ".machine_readable/template-capability-gates.toml"
5151
slot = "gates"
52-
version = "0.2.0"
53-
released = "2026-09-17"
54-
pinned = "2026-09-19"
55-
sha256 = "b65ce75438c42d01bedf0325b9a97f1a575064866158b4340d7427fca911dd1b"
52+
version = "0.3.0"
53+
released = "2026-09-19"
54+
pinned = "2026-09-22"
55+
sha256 = "e70efd2f53c9445e30da4baf770366f04a4a84ffd844a01426e587e565b53e6a"
5656

5757
# The shape of the pinned table. A vocabulary that shrank would leave profiles
5858
# declaring the lost words unparseable, and criteria gated on them permanently

‎bots/rhodibot/canon/rsr-criteria-v2.a2ml‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,24 @@
1818

1919
[meta]
2020
spec = "rhodium-standard-repositories"
21-
version = "2.0.0-draft"
21+
version = "2.1.0-draft"
2222
status = "draft" # draft | stable ; MUST NOT be cited as ratified until §Ratification passes
2323
supersedes = "1.0.0"
24-
date = "2026-07-03"
24+
date = "2026-09-19"
2525
authority = "RSR-SPEC-v2.adoc"
2626
normative-oracle = "hypatia:rsr-conformance" # the ONE checker; all others are non-normative (see [oracle])
2727
dialect = "a2ml-record" # dogfoods a2ml/RECORD-DIALECT-SPEC.adoc
28+
#
29+
# 2.1.0-draft (2026-09-19): ADDITIVE MINOR (rule-minor: may add criteria
30+
# or capability gates; must not make a previously-conforming repo
31+
# non-conforming at the same tier). Adds the julia-gated criteria
32+
# 5.2.3-5.2.6. Rationale: the capability vocabulary named `julia` with
33+
# no criteria to gate on, so a repo declaring `julia` + `library` was
34+
# scored with nothing language-shaped at all - the 33-repo .jl estate
35+
# was invisible to the oracle by construction. All four criteria gate
36+
# on `julia`, so no repo that does not declare `julia` has its
37+
# applicable set change: the MINOR's non-regression obligation holds
38+
# by the gate itself. Owner ruling 2026-09-19: approved.
2839

2940
[versioning]
3041
# Fixes the v1.0 "immutable forever" model, which estate reality already broke.
@@ -167,6 +178,10 @@ criteria = [
167178
{ id = "5.1.6", name = "no-node-npm", desc = "No Node/npm/bun runtime deps (use Deno)", tier = "bronze", gate = "universal", detect = "cicd_rules/nodejs_detected", template_ref = ".github/workflows/runtime-policy.yml" },
168179
{ id = "5.2.1", name = "spark-ready", desc = "Rust projects designed to admit SPARK/Ada modules", tier = "rhodium", gate = "rust", detect = "manual", template_ref = "-" },
169180
{ id = "5.2.2", name = "proofs-clean", desc = "No believe_me / sorry / Admitted in load-bearing proofs", tier = "gold", gate = "formal-proofs", detect = "proof_obligation/no_holes", template_ref = "verification/" },
181+
{ id = "5.2.3", name = "julia-package", desc = "Project.toml: stable uuid (derived or assigned, never regenerated), [compat] closure incl. the julia floor and bounds for every extra, licence consistent with LICENSE", tier = "silver", gate = "julia", detect = "rsr-conformance/julia_package", template_ref = "archetypes/julia-library/overlay/Project.toml.in" },
182+
{ id = "5.2.4", name = "julia-ci", desc = "Julia CI workflow with a version matrix that includes the [compat] julia floor, running Pkg.test()", tier = "bronze", gate = "julia", detect = "rsr-conformance/julia_ci", template_ref = "archetypes/julia-library/overlay/.github/workflows/julia-ci.yml.in" },
183+
{ id = "5.2.5", name = "julia-aqua", desc = "Aqua in [extras]+[targets] and green in CI (package-shape / compat / ambiguity gate); Aqua.test_all failure blocks", tier = "gold", gate = "julia", detect = "rsr-conformance/julia_aqua", template_ref = "archetypes/julia-library/overlay/test/runtests.jl" },
184+
{ id = "5.2.6", name = "julia-docs", desc = "Documenter build wired for a julia repo that declares docs-site (docs/Project.toml + docs/make.jl + docs/src/ + build workflow); declared docs-site without a build is a VESTIGIAL-class defect the gate must catch", tier = "silver", gate = "julia", detect = "rsr-conformance/julia_docs", template_ref = "archetypes/julia-library/overlay/.github/workflows/julia-docs.yml.in" },
170185
]
171186

172187
[[category]]

‎bots/rhodibot/canon/template-capability-gates.toml‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# Arrays are kept single-line so the checker can parse them with grep.
1111

1212
[meta]
13-
version = "0.2.0"
13+
version = "0.3.0"
1414
policy = "0-canon/TEMPLATE-APPLICABILITY-POLICY.adoc"
1515

1616
[capabilities]
@@ -39,6 +39,14 @@ paths = ["README.adoc", "EXPLAINME.adoc|docs/EXPLAINME.adoc", "LICENSE", "SECURI
3939
"Cargo.lock" = "rust"
4040
"src/**/*.rs" = "rust"
4141
".github/workflows/rust-ci.yml" = "rust"
42+
# Julia library modules (the julia-library archetype's overlay; the
43+
# #634 reference variant measured 2026-08-25: the language-specific
44+
# delta is exactly these files). test/ (singular) is the Julia
45+
# convention; rust uses tests/ and is ungated here by design.
46+
"Project.toml" = "julia"
47+
"test/" = "julia"
48+
".github/workflows/julia-ci.yml" = "julia"
49+
".github/workflows/julia-docs.yml" = "julia"
4250
"src/interface/ffi/" = "ffi"
4351
"abi.ipkg" = "abi"
4452
"src/interface/abi/|src/interface/Abi/" = "abi"

‎bots/rhodibot/src/canon.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ mod tests {
797797
fn parses_the_vendored_canon() {
798798
let canon = vendored();
799799
assert_eq!(canon.categories.len(), 11, "the canon has 11 categories");
800-
assert_eq!(canon.criterion_count(), 74);
800+
assert_eq!(canon.criterion_count(), 78);
801801
assert_eq!(canon.weight_sum(), 88);
802802
assert_eq!(canon.declared_weight_sum, Some(88));
803803
assert_eq!(canon.tiers.bronze, 75);
@@ -813,7 +813,7 @@ mod tests {
813813
assert_eq!(pin.source.repo, "hyperpolymath/standards");
814814
assert_eq!(pin.source.sha256, digest_of(VENDORED_CRITERIA));
815815
assert_eq!(pin.source.categories, 11);
816-
assert_eq!(pin.source.criteria, 74);
816+
assert_eq!(pin.source.criteria, 78);
817817
assert_eq!(pin.source.weight_sum, 88);
818818
}
819819

@@ -848,7 +848,7 @@ mod tests {
848848
.criteria()
849849
.filter(|c| c.capability().is_some())
850850
.count();
851-
assert_eq!(gated, 26, "criteria gated on a declared capability");
851+
assert_eq!(gated, 30, "criteria gated on a declared capability");
852852
}
853853

854854
// ---- fail-closed ---------------------------------------------------------
@@ -959,7 +959,7 @@ criteria = [
959959

960960
#[test]
961961
fn a_canon_without_a_version_is_rejected() {
962-
let source = VENDORED_CRITERIA.replacen("version = \"2.0.0-draft\"", "", 1);
962+
let source = VENDORED_CRITERIA.replacen("version = \"2.1.0-draft\"", "", 1);
963963
let error = Canon::parse(&source).expect_err("a versionless canon must not parse");
964964
assert!(
965965
format!("{error:#}").contains("no [meta] version"),

‎bots/rhodibot/src/canon/profile.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ version = \"2.0.4\"
491491
#[test]
492492
fn the_vendored_gate_table_has_the_expected_shape() {
493493
let table = gates();
494-
assert_eq!(table.version(), "0.2.0");
494+
assert_eq!(table.version(), "0.3.0");
495495
assert_eq!(
496496
table.known_count(),
497497
28,

‎bots/rhodibot/src/canon/report.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -440,9 +440,9 @@ mod tests {
440440
CanonReport::build("acme/widgets", &canon(), &gates(), &files(COMPLIANT), None)
441441
.expect("the report builds");
442442

443-
// The vendored canon: 74 criteria, 26 gated on a capability.
444-
assert_eq!(report.criteria, 74);
445-
assert_eq!(report.not_applicable, 26);
443+
// The vendored canon: 78 criteria, 30 gated on a capability.
444+
assert_eq!(report.criteria, 78);
445+
assert_eq!(report.not_applicable, 30);
446446
assert_eq!(report.scored, 22);
447447
assert_eq!(report.not_file_questions, 26);
448448
assert_eq!(
@@ -630,7 +630,7 @@ mod tests {
630630

631631
let value = serde_json::to_value(&report).expect("serialises");
632632
assert_eq!(value["subject"], "acme/widgets");
633-
assert_eq!(value["criteria"], 74);
633+
assert_eq!(value["criteria"], 78);
634634
assert!(value["counts"]["deprecated"].as_u64().unwrap() > 0);
635635
assert!(
636636
value["findings"]

‎bots/rhodibot/tests/canon_lockstep.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ fn the_rule_set_the_pilot_is_quoted_from() {
6767
let canon = Canon::vendored().expect("the canon parses");
6868

6969
assert_eq!(canon.categories.len(), 11, "weighted categories");
70-
assert_eq!(canon.criterion_count(), 74, "criteria in total");
70+
assert_eq!(canon.criterion_count(), 78, "criteria in total");
7171
assert_eq!(
7272
canon.weight_sum(),
7373
88,
@@ -77,7 +77,7 @@ fn the_rule_set_the_pilot_is_quoted_from() {
7777
let bronze = canon.criteria().filter(|c| c.tier == Tier::Bronze).count();
7878
let gold = canon.criteria().filter(|c| c.tier == Tier::Gold).count();
7979
let rhodium = canon.criteria().filter(|c| c.tier == Tier::Rhodium).count();
80-
assert_eq!((bronze, gold, rhodium), (17, 29, 4));
80+
assert_eq!((bronze, gold, rhodium), (18, 30, 4));
8181

8282
// The honest half of the coverage story: the canon says outright that these
8383
// have no automated detection. A tool that reported them as passing would
@@ -91,7 +91,7 @@ fn the_rule_set_the_pilot_is_quoted_from() {
9191
.criteria()
9292
.filter(|c| c.capability().is_some())
9393
.count();
94-
assert_eq!(gated, 26);
94+
assert_eq!(gated, 30);
9595

9696
// What a repository with no declared capabilities is measured on at bronze.
9797
assert_eq!(canon.universal_criteria_up_to(Tier::Bronze).count(), 17);
@@ -214,7 +214,7 @@ fn capability_gates_split_the_file_presence_criteria() {
214214

215215
assert_eq!(
216216
scored.len() + inapplicable.len(),
217-
31,
217+
33,
218218
"the number of criteria whose description names files changed"
219219
);
220220
assert!(

‎dashboard/src/main.rs‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,11 @@ async fn report_handler(
175175

176176
match format.to_lowercase().as_str() {
177177
"html" => (StatusCode::OK, [("content-type", "text/html")], report),
178-
"json" => (StatusCode::OK, [("content-type", "application/json")], report),
178+
"json" => (
179+
StatusCode::OK,
180+
[("content-type", "application/json")],
181+
report,
182+
),
179183
_ => (StatusCode::OK, [("content-type", "text/plain")], report),
180184
}
181185
}
@@ -211,10 +215,7 @@ async fn websocket_handler(
211215
}
212216

213217
/// Handle WebSocket connection
214-
async fn websocket_connection(
215-
mut socket: axum::extract::ws::WebSocket,
216-
state: AppState,
217-
) {
218+
async fn websocket_connection(mut socket: axum::extract::ws::WebSocket, state: AppState) {
218219
use axum::extract::ws::Message;
219220
use tokio::time::{interval, Duration};
220221

‎robot-repo-automaton/src/catalog.rs‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ impl ErrorCatalog {
114114
/// Convert from S-expression value
115115
fn from_sexpr(value: &Value) -> Result<Self> {
116116
// Expect (define error-catalog '(...))
117-
let _list = value.as_cons()
117+
let _list = value
118+
.as_cons()
118119
.ok_or_else(|| Error::CatalogParse("Expected list at top level".into()))?;
119120

120121
// Navigate to the catalog content
@@ -163,7 +164,8 @@ impl ErrorCatalog {
163164
if sym == "define" {
164165
// Third element should be the quoted list
165166
if let Some(quoted) = items[2].as_cons() {
166-
let quoted_items: Vec<&Value> = quoted.iter().map(|c| c.car()).collect();
167+
let quoted_items: Vec<&Value> =
168+
quoted.iter().map(|c| c.car()).collect();
167169
if !quoted_items.is_empty() {
168170
if let Some(quote_sym) = quoted_items[0].as_symbol() {
169171
if quote_sym == "quote" && quoted_items.len() > 1 {

‎robot-repo-automaton/src/confidence.rs‎

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -308,21 +308,36 @@ fn is_protected_file(target: &str) -> bool {
308308

309309
// Exact protected filenames (case-insensitive check on basename)
310310
let protected_names = [
311-
"TODO.md", "TODO.adoc", "TODO.txt",
312-
"BLOCKERS.md", "BLOCKERS.adoc",
313-
"CHANGELOG.md", "CHANGELOG.adoc",
314-
"README.md", "README.adoc", "README.rst",
315-
"TOPOLOGY.md", "TOPOLOGY.adoc",
316-
"ARCHITECTURE.md", "ARCHITECTURE.adoc",
317-
"ROADMAP.md", "ROADMAP.adoc",
311+
"TODO.md",
312+
"TODO.adoc",
313+
"TODO.txt",
314+
"BLOCKERS.md",
315+
"BLOCKERS.adoc",
316+
"CHANGELOG.md",
317+
"CHANGELOG.adoc",
318+
"README.md",
319+
"README.adoc",
320+
"README.rst",
321+
"TOPOLOGY.md",
322+
"TOPOLOGY.adoc",
323+
"ARCHITECTURE.md",
324+
"ARCHITECTURE.adoc",
325+
"ROADMAP.md",
326+
"ROADMAP.adoc",
318327
];
319-
if protected_names.iter().any(|&p| p.eq_ignore_ascii_case(basename)) {
328+
if protected_names
329+
.iter()
330+
.any(|&p| p.eq_ignore_ascii_case(basename))
331+
{
320332
return true;
321333
}
322334

323335
// Protected extensions — checkpoint and manifest files
324336
let protected_extensions = [".scm", ".a2ml"];
325-
if protected_extensions.iter().any(|ext| basename.ends_with(ext)) {
337+
if protected_extensions
338+
.iter()
339+
.any(|ext| basename.ends_with(ext))
340+
{
326341
return true;
327342
}
328343

@@ -630,7 +645,10 @@ mod tests {
630645

631646
assert_eq!(config.classify_fix(&issue, &fix), ConfidenceLevel::High);
632647
// And the decision should be AutoApply
633-
assert!(matches!(config.decide(&issue, &fix), FixDecision::AutoApply));
648+
assert!(matches!(
649+
config.decide(&issue, &fix),
650+
FixDecision::AutoApply
651+
));
634652
}
635653

636654
#[test]
@@ -654,11 +672,13 @@ mod tests {
654672
assert_eq!(
655673
config.classify_fix(&issue, &fix),
656674
ConfidenceLevel::Medium,
657-
"Expected Medium at confidence={}", confidence
675+
"Expected Medium at confidence={}",
676+
confidence
658677
);
659678
assert!(
660679
matches!(config.decide(&issue, &fix), FixDecision::Propose { .. }),
661-
"Expected Propose at confidence={}", confidence
680+
"Expected Propose at confidence={}",
681+
confidence
662682
);
663683
}
664684
}
@@ -674,7 +694,8 @@ mod tests {
674694
assert_eq!(
675695
config.classify_fix(&issue, &fix),
676696
ConfidenceLevel::Low,
677-
"Expected Low at confidence={}", confidence
697+
"Expected Low at confidence={}",
698+
confidence
678699
);
679700
}
680701
}
@@ -686,16 +707,25 @@ mod tests {
686707
// High confidence Delete -> AutoApply
687708
let issue_high = make_issue("TEST", 0.97);
688709
let fix = make_fix(FixAction::Delete, "stale.txt");
689-
assert!(matches!(config.decide(&issue_high, &fix), FixDecision::AutoApply));
710+
assert!(matches!(
711+
config.decide(&issue_high, &fix),
712+
FixDecision::AutoApply
713+
));
690714

691715
// Medium confidence Delete -> Propose
692716
let issue_medium = make_issue("TEST", 0.80);
693-
assert!(matches!(config.decide(&issue_medium, &fix), FixDecision::Propose { .. }));
717+
assert!(matches!(
718+
config.decide(&issue_medium, &fix),
719+
FixDecision::Propose { .. }
720+
));
694721

695722
// Low confidence Delete -> also Propose (not Skip, since Low >= Low)
696723
// (Skip only occurs when the action is somehow forced below the propose floor,
697724
// which is not reachable in current logic — Low always Proposes)
698725
let issue_low = make_issue("TEST", 0.30);
699-
assert!(matches!(config.decide(&issue_low, &fix), FixDecision::Propose { .. }));
726+
assert!(matches!(
727+
config.decide(&issue_low, &fix),
728+
FixDecision::Propose { .. }
729+
));
700730
}
701731
}

0 commit comments

Comments
 (0)