3 Commits
Author SHA1 Message Date
portal release 0cf70502fc Match portal v0.3.42
Publish release / publish (push) Successful in 25s
Test / test (push) Successful in 29s
2026-09-23 14:35:49 +02:00
portal release fb66694b85 Match portal v0.3.41
Publish release / publish (push) Successful in 25s
Test / test (push) Successful in 1m6s
2026-09-23 11:55:10 +02:00
Bendik Aagaard LynghaugandClaude Fable 5.1 959e5f6ef0 check: compile and validate the access policy; --access prints the matrix
Test / test (push) Successful in 29s
Every check now compiles the content into portal's Cedar policy and
fails when it does not validate against the schema - a rule the
schema cannot express would grant nothing at runtime. `--access`
prints the matrix: action, resource, who, and the state move where it
applies. Pinned to portal v0.3.40, where the policy lives.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
2026-09-23 11:48:33 +02:00
5 changed files with 33 additions and 9 deletions
+6 -2
View File
@@ -3,9 +3,13 @@
One line per change, grouped by the release that shipped it. Newest
first. Each release names the portal tag it is built against.
## 0.3.40 (2026-09-23) - portal v0.3.40
## 0.3.42 (2026-09-23) - portal v0.3.42
- Matches portal v0.3.40 (released by portal's publish job).
- Matches portal v0.3.42 (released by portal's publish job).
## 0.3.41 (2026-09-23) - portal v0.3.41
- Matches portal v0.3.41 (released by portal's publish job).
## 0.3.39 (2026-09-23) - portal v0.3.39
Generated
+3 -3
View File
@@ -2196,7 +2196,7 @@ checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0"
[[package]]
name = "iris"
version = "0.3.40"
version = "0.3.42"
dependencies = [
"anyhow",
"async-nats",
@@ -3303,8 +3303,8 @@ dependencies = [
[[package]]
name = "portal"
version = "0.3.40"
source = "git+https://project.uhhm.no/uhhm/portal.git?tag=v0.3.40#44d5ef368371b7d0d004d9bb0c89a9db267fcfe6"
version = "0.3.42"
source = "git+https://project.uhhm.no/uhhm/portal.git?tag=v0.3.42#6d60f8b9a343ad3645de5055b15bf6b836685f0e"
dependencies = [
"anyhow",
"arc-swap",
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "iris"
version = "0.3.40"
version = "0.3.42"
edition = "2021"
description = "The iris over a portal site: nothing from a content repo comes through until it checks out"
license = "MIT"
@@ -10,7 +10,7 @@ repository = "https://project.uhhm.no/uhhm/iris"
# The site's own content types and loaders, so iris reads a repo exactly
# the way the running site does. Pinned to the portal release it
# matches; bumping this tag is what a new iris release is.
portal = { git = "https://project.uhhm.no/uhhm/portal.git", tag = "v0.3.40", features = ["ssr"] }
portal = { git = "https://project.uhhm.no/uhhm/portal.git", tag = "v0.3.42", features = ["ssr"] }
anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
+21 -1
View File
@@ -23,6 +23,7 @@ pub async fn run(argv: Vec<String>) -> anyhow::Result<()> {
let mut min_accuracy: f64 = 0.0;
let mut skim = false;
let mut sim_out: Option<String> = None;
let mut show_access = false;
while let Some(arg) = args.next() {
match arg.as_str() {
@@ -33,6 +34,7 @@ pub async fn run(argv: Vec<String>) -> anyhow::Result<()> {
"--needs-tasks" => tasks_out = args.next(),
"--skim" => skim = true,
"--needs-sim" => sim_out = args.next(),
"--access" => show_access = true,
"--needs-score" => answers_in = args.next(),
"--min-accuracy" => {
min_accuracy = args.next().and_then(|v| v.parse().ok()).unwrap_or(min_accuracy)
@@ -62,7 +64,7 @@ pub async fn run(argv: Vec<String>) -> anyhow::Result<()> {
(questions, aggregates_map, site, needs_raw)
} else {
eprintln!(
"usage: iris check (--repo <gitea-url> [--branch main] [--subdir questions] | --path <local-dir>) [--needs-tasks <out.jsonl> [--skim]] [--needs-sim <model.json>] [--needs-score <answers.jsonl> [--min-accuracy 0.8]]"
"usage: iris check (--repo <gitea-url> [--branch main] [--subdir questions] | --path <local-dir>) [--needs-tasks <out.jsonl> [--skim]] [--needs-sim <model.json>] [--access] [--needs-score <answers.jsonl> [--min-accuracy 0.8]]"
);
std::process::exit(2);
};
@@ -91,6 +93,24 @@ pub async fn run(argv: Vec<String>) -> anyhow::Result<()> {
questions.len(),
aggregates_map.len()
);
// The access policy the site will compile from this content.
// It must type-check against portal's schema: a rule the
// schema cannot express would grant nothing at runtime.
let policy = portal::access::Policy::from_content(&questions, &aggregates_map);
if let Err(e) = policy.validate() {
eprintln!("FAIL: access policy does not validate: {e}");
std::process::exit(1);
}
println!("OK: access policy, {} rule(s), validates", policy.rules.len());
if show_access {
println!("\n{:<16} {:<44} {:<40} {}", "ACTION", "RESOURCE", "WHO", "WHEN");
let mut rows = policy.rules.clone();
rows.sort_by(|a, b| (&a.resource, &a.action, &a.who).cmp(&(&b.resource, &b.action, &b.who)));
for r in rows {
println!("{:<16} {:<44} {:<40} {}", r.action, r.resource, r.who, r.when);
}
println!();
}
check_needs(
needs_raw.as_deref(),
&questions,
+1 -1
View File
@@ -44,7 +44,7 @@ async fn main() -> anyhow::Result<()> {
Some(flag) if flag.starts_with("--") => check::run(argv).await,
_ => {
eprintln!(
"iris {v} - matches portal v{v}\n\nusage:\n iris check (--repo <gitea-url> [--branch main] [--subdir questions] | --path <dir>) [--needs-tasks <out.jsonl> [--skim]] [--needs-sim <model.json>] [--needs-score <answers.jsonl> [--min-accuracy 0.8]]\n iris replay --path <dir> --nats <url> (--cases <cases.jsonl> | --report-only)",
"iris {v} - matches portal v{v}\n\nusage:\n iris check (--repo <gitea-url> [--branch main] [--subdir questions] | --path <dir>) [--needs-tasks <out.jsonl> [--skim]] [--needs-sim <model.json>] [--access] [--needs-score <answers.jsonl> [--min-accuracy 0.8]]\n iris replay --path <dir> --nats <url> (--cases <cases.jsonl> | --report-only)",
v = env!("CARGO_PKG_VERSION")
);
std::process::exit(2)